Поддержать проект: https://yoomoney.ru/to/410015019068268
В прошлой статье я рассказал про опенсорс разработку — платформу TKSBrokerAPI — это Python API для работы с Tinkoff Open API через REST протокол. Также этим инструментом можно пользоваться из консоли или встраивать его в CI/CD-системы, для автоматизации рутинных операций на бирже, через брокера Тинькофф Инвестиции.
С помощью TKSBrokerAPI вы можете реализовать на языке Python любой торговый сценарий. В сегодняшней статье я хочу показать для разработчиков некоторые возможности этого инструмента, на примере абстрактного торгового сценария.
Неважно, какую основную систему принятия торговых решений о покупке или продаже вы используете. Это может быть технический анализ, нейросети, парсинг отчётов или слежение за сделками других трейдеров. Но всё равно вам потребуется выполнять базовые торговые операции: получать рыночные данные, выставлять лимитные и стоп-ордера, открывать и закрывать сделки по рынку. Модуль TKSBrokerAPI будет выступать как посредник между кодом с логикой торгов и сервисной инфраструктурой брокера, а также выполнять рутинные задачи от вашего имени в брокерском аккаунте.
Схема разработки с помощью TKSBrokerAPI очень простая:
- Вы придумываете гениальный торговый алгоритм.
- Записываете его пошагово в виде некоторого плана или торгового сценария.
- Автоматизируете сценарий на Python при помощи TKSBrokerAPI.
- TKSBrokerAPI берёт на себя всю работу с инфраструктурой брокера Тинькофф Инвестиции.
- Профит!
С чего начать
Проще всего установить TKSBrokerAPI через PyPI:
pip install tksbrokerapi
Либо скачать код проекта TKSBrokerAPI из гитхаб-репозитория.
Перед тем, как использовать API, вам нужно создать себе аккаунт у брокера Тинькофф Инвестиции. Для аутентификации через API вам также понадобятся токен и идентификатор счёта пользователя. Полная документация по всем доступным свойствам и методам TKSBrokerAPI находится по ссылке. Соответствие консольных ключей и методов также можно посмотреть в документации в разделе "Основные возможности".
Важное замечание: модуль TKSBrokerAPI не предназначен для высокочастотной (HFT) торговли, из-за системы динамического формирования лимитов для пользователей сервиса TINKOFF INVEST API (подробнее по ссылке). В среднем, это 50-300 запросов в секунду, в зависимости от их типа, что очень мало для требований к скоростям HFT (но есть несколько рекомендаций по ускорению исполнения поручений). Однако вы вполне можете использовать API для автоматизации своих интрадей, кратко-, средне- и долгосрочных торговых стратегий.
Пример реализации абстрактного сценария
Так как функциональность TKSBrokerAPI достаточно обширная, мне не хочется акцентировать внимание на конкретных торговых сценариях, а лишь указать некоторые возможности для их автоматизации. Давайте рассмотрим один сценарий, основанный на сравнении объёмов текущих покупок и продаж в биржевом стакане, и реализуем его при помощи API, без использования дополнительных методов технического анализа.
Действия будут следующие:
- запросить текущий портфель клиента и определить доступные для торговли средства;
- запросить стакан цен с глубиной 20 для выбранных инструментов, например, акции с тикерами `YNDX`, `IBM` и `GOOGLE`;
- если инструмент ранее ещё не был куплен, то проверить:
- если резерв денежных средств (свободный кеш) в валюте инструмента больше, чем 5% от общей стоимости всех инструментов в этой валюте, то проверить:
- если в стакане объёмы на покупку больше объёмов на продажу минимум на 10%, то купить 1 акцию по рынку и выставить тейк-профит как стоп-ордер на 3% выше текущей цены покупки со сроком действия 1 час;
- если инструмент имеется в списке открытых позиций, то проверить:
- если текущая цена уже выше средней цены позиции хотя бы на 2.5%, то выставить отложенный лимитный ордер на весь объём, но ещё чуть-чуть выше (на 0.1%) от текущей цены, чтобы позиция закрылась с профитом с большой вероятностью в течении текущей торговой сессии;
- после всех торговых операций напечатать в консоль текущее состояние портфеля пользователя.
Для понимания примера сохраните и запустите скрипт под спойлером ниже (или скачайте его по ссылке). Не забудьте перед этим подставить свой token и accountId в разделе инициализации в коде. Большая часть кода подробно прокомментирована и даны ссылки на соответствующие методы API.
Пример кода с торговым сценарием
# -*- coding: utf-8 -*- # Author: Timur Gilmullin # --- Секция инициализации: импорты, константы и переменные ------------------------------------------------------------ from datetime import datetime, timedelta from dateutil.tz import tzlocal, tzutc from math import ceil from tksbrokerapi.TKSBrokerAPI import TinkoffBrokerServer, uLogger # основной модуль для выполнения торговых операций uLogger.level = 10 # DEBUG (10) уровень логирования, рекомендованный по умолчанию для `TKSBrokerAPI.log` uLogger.handlers[0].level = 20 # уровень логирования для вывода в консоль STDOUT, INFO (20) рекомендовано по умолчанию start = datetime.now(tzutc()) uLogger.debug("=--=" * 20) uLogger.debug("Trading scenario started at: [{}] UTC, it is [{}] local time".format( start.strftime("%Y-%m-%d %H:%M:%S"), start.astimezone(tzlocal()).strftime("%Y-%m-%d %H:%M:%S"), )) # Установите здесь переменные и константы, необходимые для торговли по вашему алгоритму: TICKERS_LIST_FOR_TRADING = ["YNDX", "IBM", "GOOGL"] # Вы можете задать список инструментов различным образом: перечислить их напрямую или задать как результат некоторой функции фильтрации или скринера RESERVED_MONEY = 0.05 # Доля резервируемых средств (от 0 до 1), не участвующих в торгах, 0.05 (это 5%) по умолчанию LOTS = 1 # Минимальное число лотов для покупки или продажи TP_STOP_DIFF = 0.03 # 3% тейк-профит по умолчанию для стоп-ордеров TP_LIMIT_DIFF = 0.025 # 2.5% тейк-профит по умолчанию для отложенных лимитных ордеров TOLERANCE = 0.001 # Допустимое отклонение текущей рыночной цены от целевой цены установленных ордеров, 0.1% по умолчанию DEPTH_OF_MARKET = 20 # Насколько глубоко запрашивать стакан цен для анализа текущих объёмов торгов, >= 1 VOLUME_DIFF = 0.1 # Достаточная разница в объёмах текущих предложений на покупку и продажу для открытия позиции, 10% по умолчанию # Инициализация основного объекта трейдера, TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.__init__ trader = TinkoffBrokerServer( token="", # Внимание! Установите строку с вашим токеном сюда или используйте переменную окружения `TKS_API_TOKEN` accountId="", # Внимание! Установите строку с вашим accountId сюда или используйте переменную окружения `TKS_ACCOUNT_ID` ) # --- Секция описания торгового сценария ------------------------------------------------------------------------------- for ticker in TICKERS_LIST_FOR_TRADING: uLogger.info("--- Ticker [{}], data analysis...".format(ticker)) # - Шаг 1: запрос текущего портфеля клиента и определение доступных объёмов и валют для торговли # Портфель пользователя. Это словарь с несколькими секциями: {"raw": {...}, "stat": {...}, "analytics": {...}} portfolio = trader.Overview(showStatistics=False) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.Overview uLogger.info("Total portfolio cost: {:.2f} rub; blocked: {:.2f} rub; changes: {}{:.2f} rub ({}{:.2f}%)".format( portfolio["stat"]["portfolioCostRUB"], portfolio["stat"]["blockedRUB"], "+" if portfolio["stat"]["totalChangesRUB"] > 0 else "", portfolio["stat"]["totalChangesRUB"], "+" if portfolio["stat"]["totalChangesPercentRUB"] > 0 else "", portfolio["stat"]["totalChangesPercentRUB"], )) # Сколько денег в различных валютах доступно для торговли? Нужно посчитать (total - blocked). funds = portfolio["stat"]["funds"] # Словарь, например: {"rub": {"total": 10000.99, "totalCostRUB": 10000.99, "free": 1234.56, "freeCostRUB": 1234.56}, "usd": {"total": 250.55, "totalCostRUB": 15375.80, "free": 125.05, "freeCostRUB": 7687.50}, ...} uLogger.info("Available funds free for trading: {}".format("; ".join(["{:.2f} {}".format(funds[currency]["free"], currency) for currency in funds.keys()]))) # - Шаг 2: запрос стакана цен для текущего инструмента trader.ticker = ticker trader.figi = "" # Мы не знаем FIGI для каждого тикера, поэтому указываем здесь пустую строку. В этом случае TKSBrokerAPI определит FIGI автоматически. trader.depth = DEPTH_OF_MARKET # Получаем цены брокера для текущего инструмента: ordersBook = trader.GetCurrentPrices(showPrice=False) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.GetCurrentPrices if not (ordersBook["buy"] and ordersBook["sell"]): uLogger.warning("Not possible to trade an instrument with the ticker [{}]! Try again later.".format(trader.ticker)) else: # - Шаг 3: если инструмент отсутствует в списке текущих открытых позиций пользователя, то проверяем: # - если денежный резерв (свободные деньги) в валюте инструмента больше, чем 5% от общей стоимости # всех инструментов в этой валюте, то проверяем: # - если объёмы покупателей в стакане больше хотя бы на 10% чем объёмы продавцов, тогда покупаем 1 лот инструмента # по рынку и размещаем тейк-профит как стоп-ордер на 3% выше, чем текущая цена покупки, с отменой ордера через 1 час; # Проверяем, есть ли открытые позиции по текущему инструменту, заданному через `ticker`, в портфеле пользователя: isInPortfolio = trader.IsInPortfolio(portfolio) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.IsInPortfolio if not isInPortfolio: uLogger.info("Ticker [{}]: no current open positions with that instrument, checking opens rules...".format(trader.ticker)) # Так как инструмента нет среди открытых позиций, то получаем данные по инструменту и его валюте у брокера: rawIData = trader.SearchByTicker(requestPrice=False, showInfo=False, debug=False) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.SearchByTicker iCurr = rawIData["currency"] # валюта текущего инструмента # Получаем аналитику портфеля: распределение активов по валютам, стоимость ранее купленных активов и доступный свободный остаток: distrByCurr = portfolio["analytics"]["distrByCurrencies"] # распределение активов по валютам, оценка стоимости в рублях assetsCostInRuble = distrByCurr[iCurr]["cost"] # стоимость активов в валюте инструмента, пересчитанная в рубли currencyFreeCostInRuble = funds[iCurr]["freeCostRUB"] # оценка свободных средств, пересчитанная в рублях, для валюты текущего инструмента # Прежде чем совершить сделку, проверяем резервы и разницу объёмов спроса и предложения, в соответствии с заданными параметрами: if currencyFreeCostInRuble / assetsCostInRuble >= RESERVED_MONEY: sumSellers = sum([x["quantity"] for x in ordersBook["buy"]]) # текущий объём предложений продавцов в стакане (у продавцов можно купить) sumBuyers = sum([x["quantity"] for x in ordersBook["sell"]]) # текущий объём предложений покупателей в стакане (покупателям можно продать) if sumBuyers >= sumSellers * (1 + VOLUME_DIFF): # Получаем текущую цену, вычисляем цену потенциального тейк-профита и срок действия для стоп-ордера: currentPriceToBuy = ordersBook["buy"][0]["price"] # первая цена в списке ордеров продавцов и есть актуальная цена, по которой можно купить target = currentPriceToBuy * (1 + TP_STOP_DIFF) # целевая цена для тейк-профита, без учёта шага изменения цены targetStop = ceil(target / rawIData["step"]) * rawIData["step"] # реальная цена тейк-профита для размещения стоп-ордера, с учётом допустимого шага цены localAliveTo = (datetime.now() + timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S") # текущее время + 1 час uLogger.info("Opening BUY position... (Buyers volumes [{}] >= {} * sellers volumes [{}] and current price to buy: [{:.2f} {}])".format( sumBuyers, 1 + VOLUME_DIFF, sumSellers, currentPriceToBuy, iCurr, )) # Открываем BUY позицию по рынку и создаём стоп-ордер по желаемой цене тейк-профита: trader.Buy(lots=LOTS, tp=targetStop, sl=0, expDate=localAliveTo) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.Buy else: uLogger.info("BUY position not opened, because buyers volumes [{}] < {} * sellers volumes [{}]".format(sumBuyers, 1 + VOLUME_DIFF, sumSellers)) else: uLogger.info("BUY position not opened, because the reserves in [{}] will be less than {:.2f}% of free funds".format(iCurr, RESERVED_MONEY * 100)) else: # - Шаг 4: если по инструменту уже была открыта позиция, то проверяем: # - если текущая средняя цена позиции хотя бы на 2.5% выше, чем средняя цена покупки, то размещаем отложенный # лимитный ордер на весь объём позиции по цене на 0.1% выше, чем текущая рыночная цена. Это нужно для того, чтобы позиция # закрылась с профитом, с большой вероятностью в течение текущей торговой сессии. uLogger.info("Ticker [{}]: there is an open position with that instrument, checking closure rules...".format(trader.ticker)) # Получаем информацию по инструменту из списка текущих открытых позиций в портфеле пользователя: iData = trader.GetInstrumentFromPortfolio(portfolio) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.GetInstrumentFromPortfolio # Вычисляем количество доступных лотов для продажи, среднюю цену позиции и текущую рыночную цену инструмента: lotsToSell = iData["volume"] - iData["blocked"] # не заблокированные лоты текущего инструмента, доступные для торговли averagePrice = iData["average"] # средняя цена позиции curPriceToSell = ordersBook["sell"][0]["price"] # первая цена в списке ордеров покупателей и есть актуальная цена, по которой можно продать инструмент # Вычисляем цену с упреждением, по которой можно закрыть позицию, не дожидаясь строгого исполнения по цене тейк-профита: curProfit = (curPriceToSell - averagePrice) / averagePrice # доля изменения между текущей рыночной ценой и средней позицией по инструменту target = curPriceToSell * (1 + TOLERANCE) # достаточная цена для продажи targetLimit = ceil(target / iData["step"]) * iData["step"] # целевая цена + упреждение, для размещения отложенного лимитного-ордера # Проверяем на достаточную разницу в цене для профита: if curProfit >= TP_LIMIT_DIFF: uLogger.info("The current price is [{:.2f} {}], average price is [{:.2f} {}], so profit {:.2f}% more than {:.2f}%. Opening SELL pending limit order...".format( curPriceToSell, iData["currency"], averagePrice, iData["currency"], curProfit * 100, TP_LIMIT_DIFF * 100, )) # Открываем отложенный лимитный SELL ордер: trader.SellLimit(lots=lotsToSell, targetPrice=targetLimit) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.SellLimit else: uLogger.info("SELL order not created, because the current price is [{:.2f} {}], average price is [{:.2f} {}], so profit {:.2f}% less than {:.2f}% target.".format( curPriceToSell, iData["currency"], averagePrice, iData["currency"], curProfit * 100, TP_LIMIT_DIFF * 100, )) # - Шаг 5: запрашиваем и отображаем изменения в портфеле пользователя после всех выполненных операций uLogger.info("--- All trade operations finished. Let's show what we got in the user's portfolio after all trades.") # Текущее состояние портфеля пользователя: trader.Overview(showStatistics=True) # TKSBrokerAPI: https://tim55667757.github.io/TKSBrokerAPI/docs/tksbrokerapi/TKSBrokerAPI.html#TinkoffBrokerServer.Overview # --- Секция финализации торговых операций ----------------------------------------------------------------------------- finish = datetime.now(tzutc()) uLogger.debug("Trading scenario work duration: [{}]".format(finish - start)) uLogger.debug("Trading scenario finished: [{}] UTC, it is [{}] local time".format( finish.strftime("%Y-%m-%d %H:%M:%S"), finish.astimezone(tzlocal()).strftime("%Y-%m-%d %H:%M:%S"), )) uLogger.debug("=--=" * 20)
Пусть вас не пугает кажущаяся сложность и объём кода. На самом деле, его большую часть занимают комментарии с примечаниями и логи, которые можно опустить в реальном проекте, а оставшийся алгоритм получится небольшим.
В следующих статьях я планирую рассказать про новый опенсорс проект, в котором будут храниться готовые торговые шаблоны и стратегии, а также аналитика на Python с использованием модуля TKSBrokerAPI. Их можно будет подключать и использовать как самостоятельно, так и в составе ваших собственных торговых сценариев.