Трейдеры на финансовом рынке обрабатывают большие объемы информации и принимают решения максимально быстро, чтобы не упустить возможность и избежать рисков. Получить преимущество можно, если умеешь хотя бы немного программировать. Это особенно важно там, где время — деньги.

Я Александр Парфенов, бэкенд-разработчик в Тинькофф Инвестициях и автор InvestAPI SDK для языка Go. Расскажу о том, как автоматизировать торговые стратегии при помощи Tinkoff INVEST API и языка Go.

Интервальный алгоритм торговли

Алготрейдинг — процесс торговли на бирже, при котором трейдер прибегает к помощи разных средств автоматизации для достижения прибыли. Мои коллеги уже рассказывали о том, как писать торговых роботов:

Можно разработать свою стратегию или взять готовую и по ней реализовать робота, торгующего в автоматическом режиме. Именно этим мы и займемся.

Рассмотрим интервальный алгоритм. Есть бумаги, цены которых часто ходят в рамках коридора. Цель интервального бота — найти подходящие бумаги и значения границ этого коридора, чтобы автоматически покупать по нижней границе и продавать по верхней.

Японская свеча — вид интервального графика и технический индикатор, применяемый для отображения изменений биржевых котировок акций, цен на сырье.

Алгоритм можно разделить на несколько логических этапов

Подготовка — анализ финансовых инструментов на основе исторических котировок, выбор подходящих для торговли и расчет интервала цены для каждого инструмента. Например, можем посмотреть на подходящий график минутных свечей, разница между максимальной и минимальной ценой составляет около 1,5%. В таком случае наша стратегия будет работать хорошо.

График минутных свечей
График минутных свечей

Для начала определяем начальный перечень инструментов, которые потенциально доступны для торговли, например k акций. Для каждого из k инструментов рассматриваем его исторические минутные свечи за n дней и находим такой интервал цены, при котором достигается максимум функции volatility.

volatility = width * crosses

Где width — ширина интервала в процентах, crosses — количество свечей, которые этот интервал пересекают. Свеча пересекает интервал, если high-price свечи выше верхней границы интервала, а low-price ниже нижней границы интервала. На выходе получаем отсортированные по убыванию значения volatility, список всех k инструментов, берем топ t лучших из них и начинаем торговать.

Торговля запуск непрерывной торговли инструментами и пересчет интервала с учетом новых свечей.

Демонстрация сделок по интервалу. Желтая и зеленая линии — границы интервала, по которым совершаются сделки, а красная линия — stop-loss-цена
Демонстрация сделок по интервалу. Желтая и зеленая линии — границы интервала, по которым совершаются сделки, а красная линия — stop-loss-цена

Теперь необходимо в реальном времени покупать и продавать t инструментов по границам их интервалов и с некоторой периодичностью пересчитывать эти интервалы. 

Для защиты от резкого падения цены открытых позиций бот умеет выставлять stop-loss-заявки на продажу и stop-limit-заявки на покупку. А про полностью автоматизированный процесс была статья в нашем блоге: 

Пример сделок на интервалах

Посмотрим на реальном инструменте график цен закрытия минутных свечей и два интервала.

Ширина интервала 0,11%
Ширина интервала 0,11%

Фиолетовая пунктирная линия (3652) — цена, которая встречается чаще всего в этом наборе, назовем ее M. Синие точки, соединенные пунктирной линией, — сделки по покупке или продаже инструмента.

В первом случае определим границы интервала так: low = 3650, high = 3654, ширина первого интервала составляет около 0,11% от M, что позволяет совершить пять сделок с общим профитом в 20 ₽.

Во втором случае определим границы интервала так: low = 3648, high = 3656, ширина второго интервала составляет около 0,22% от M, что позволяет провести три сделки с общим профитом в 24 ₽.

Ширина интервала 0,22%
Ширина интервала 0,22%

Ширина интервала влияет сразу на два параметра: прибыльность единичной сделки и количество сделок. Задача — подобрать оптимальные значения для получения максимального произведения ширины интервала и количества сделок. Забегая вперед: как показывают тесты, наиболее выгодная ширина интервала находится в диапазоне 0,2—0,6%.

Реализация интервального бота

Реализуем бота на Go, поэтому воспользуемся официальным sdk. В директории examples этого репозитория есть основные сценарии взаимодействия с API, а еще примеры двух торговых роботов, один из которых мы сейчас более детально рассмотрим. Исходный код доступен на GitHub.

Структурная схема интервального робота выглядит так:

Bot реализует сбор и обработку рыночных данных, методы для локальной проверки и конфигурации стратегии и расчет интервалов цен. CandlesStorage заполняется историческими данными один раз перед началом работы бота, потом бот регулярно добавляет в него новые данные. Executor отвечает за выставление торговых поручений на покупку или продажу инструментов по границам интервалов, полученных от Bot’a. Все взаимодействие с Tinkoff INVEST API происходит через пакет investgo
Bot реализует сбор и обработку рыночных данных, методы для локальной проверки и конфигурации стратегии и расчет интервалов цен. CandlesStorage заполняется историческими данными один раз перед началом работы бота, потом бот регулярно добавляет в него новые данные. Executor отвечает за выставление торговых поручений на покупку или продажу инструментов по границам интервалов, полученных от Bot’a. Все взаимодействие с Tinkoff INVEST API происходит через пакет investgo

Найдем интервал цены с максимальным значением волатильности, сделать это можно несколькими способами.

SIMPLEST — полный перебор. Набор свечей за определенный интервал времени имеет максимальное и минимальное значение цены. Нужно перебором найти цену, которая встречается чаще всего. На графике свечей это горизонтальная линия с максимальным количеством пересеченных свечей. Далее от этого значения цены расширяемся минимум до ширины s и более, если значение волатильности не убывает при расширении.

BEST_WIDTH — разница с SIMPLEST в том, что начальная цена для расширения находится не перебором, а берется медиана выборки средних цен набора свечей. Средняя цена свечи = (high+low+open+close)/4.

MATH_STAT — границы интервала находятся как процентили распределения средних цен набора свечей.

Как показывает моя личная практика, вариант № 2 наиболее выгодный. 

На успех стратегии влияет ряд параметров настройки бота.

  • Instruments — список инструментов, по которому будет проводиться анализ и отбор лучших по волатильности. В примере реализован отбор из рублевых фондов и акций московской биржи, также можно задать этот список вручную.

  • PreferredPositionPrice — предпочтительная стоимость открытия позиции в валюте. Например, PreferredPositionPrice = 1000 ₽. При стоимости одного лота 100 ₽ и лотности инструмента 1 будет куплено 10 лотов.

  • MaxPositionPrice — если цена покупки одного лота инструмента выше этого значения, инструмент отбрасывается.

  • MinProfit — минимальный профит для совершения сделки, в нашем случае — минимальная ширина коридора в процентах.

  • TopInstrumentsQuantity — количество лучших по значению максимальной волатильности инструментов, с которыми мы начинаем торговать.

  • DaysToCalculateInterval — количество дней, на которых рассчитывается интервал цен для торговли.

  • StopLossPercent — процент изменения цены для stop-loss-заявки. Например, если StopLossPercent = 1, то при падении цены бумаги на 1% относительно цены открытия позиции выставляется рыночное торговое поручение на продажу.

  • AnalyseLowPercentile — нижний процентиль для расчета интервала. Учитывается только при Analyse = MATH_STAT.

  • AnalyseHighPercentile — верхний процентиль для расчета интервала. Учитывается только при Analyse = MATH_STAT.

  • Analyse — тип анализа исторических свечей при расчете интервала.

Подобрать выгодную конфигурацию можно при помощи бэктеста — специального режима работы бота, при котором алгоритм прогоняется на исторических данных. Например, за последнюю неделю, месяц или даже год. Работа бота ориентирована на торговлю внутри одного дня, результат проверки конкретной конфигурации — это значение среднего процента профита в день.

В роботе реализовано два варианта бэктеста:

  • Проверка конкретной конфигурации на исторических данных. Результат — значение среднего процента профита в день.

  • Генерация множества конфигураций и их проверка на исторических данных. Результат — список отчетов, отсортированный по возрастанию среднего процента профита в день.

Режим работы бэктеста управляется переменной mode, которая может принимать два значения: TEST_WITH_CONFIG (вариант 1) и TEST_WITH_MULTIPLE_CONFIGS (вариант 2).

Пример отчета о проверке конфига: 

report 8:
// Результат 
average day profit percent =  0.416
total profit =  943.944
// Значения конфигурации
analyse = 1
minProfit = 0.6
lowPercentile = 0
highPercentile = 0
days = 1
stopLoss = 200.000

Важно понимать, что проверка на исторических данных не гарантирует таких же результатов в реальном времени.

Запуск бота

Для запуска интервального бота на компьютере должны быть:

Нужно быть клиентом Тинькофф Инвестиций и выпустить токен. Далее следуем инструкции ниже или инструкции из репозитория.

Инструкция по подготовке к запуску бэктеста.

  1. Клонируйте репозиторий:

 git clone https://github.com/tinkoff/invest-api-go-sdk
  1. Перейдите в папку с ботом:

 cd invest-api-go-sdk/examples/interval_bot
  1. Создайте файл config.yaml:

touch "config.yaml"
  1. Заполните его по примеру example.yaml:

AccountId: ""
APIToken: <your_token>
EndPoint: sandbox-invest-public-api.tinkoff.ru:443
AppName: invest-api-go-sdk
DisableResourceExhaustedRetry: false
DisableAllRetry: false
MaxRetries: 3
  1. Загрузите исторические свечи по инструментам. Для этого нужно воспользоваться загрузчиком, который будет по частям запрашивать свечи и сохранять их в sqlite. Можно указать свой набор инструментов, но для быстрого старта рекомендуется просто запустить загрузчик с настройками по умолчанию, и он загрузит все минутные свечи по рублевым фондам и акциям с Московской биржи за последние полгода. Этот процесс займет около 30 минут.

  2. Создайте папку для свечей:

 mkdir candles
  1. Запустите загрузчик:

 go run cmd/candles_downloader/download_candles.go
  1. Запустите бота на песочнице, проде или проверьте бэктест.

После загрузки истории можно приступить к проверке стратегии. Для этого перейдем в файл cmd/backtest/backtest.go, сконфигурируем бэктест и проверим на истории несколько конфигураций.

В конфигурации № 1 используется тип анализа свечей BEST_WIDTH, интервал рассчитывается на основе предыдущих 4 торговых дней, а проверка проходит с 22.05.23 по 22.07.23.

var (
	// Интервал для проверки
	initDate = time.Date(2023, 5, 22, 0, 0, 0, 0, time.Local)
	stopDate = time.Date(2023, 7, 22, 0, 0, 0, 0, time.Local)
	// Критерий для отбора бумаг. Акции, фонды, акции и фонды
	selection = SHARES_AND_ETFS
	// Режим запуска теста, на одном конфиге или перебор сгенерированных конфигов
	mode = TEST_WITH_CONFIG
    ...
	// Конфиг бэктеста для режима TEST_WITH_CONFIG
	configToTest = bot.BacktestConfig{
		Analyse:                 bot.BEST_WIDTH,
		LowPercentile:           0,
		HighPercentile:          0,
		MinProfit:               0.3,
		DaysToCalculateInterval: 4,
		StopLoss:                1.8,
		// Для тарифа "Трейдер" комиссия за сделку с акцией составляет 0.05% от стоимости сделки
		Commission: 0.05,
	}
    ...
)

Сохраним изменения и запустим бэктест:

go run cmd/backtest/backtest.go

Результат: средний профит в день = 0,104%, с учетом комиссии брокера — около 2,4% прибыли в месяц.

Результат бэктеста с конфигурацией № 1
Результат бэктеста с конфигурацией № 1

В конфигурации № 2 используется тип анализа свечей BEST_WIDTH, интервал по-прежнему рассчитывается на основе предыдущих 4 торговых дней, но проверка проходит с 22.02.23 по 22.07.23. Отключены (=1000,0%) stop-loss'ы и DISABLE_INFO_LOGS = false — это позволяет увидеть в консоли подробные сообщения о ходе выполнения программы.

const DISABLE_INFO_LOGS = false

var (
	// Интервал для проверки
	initDate = time.Date(2023, 2, 22, 0, 0, 0, 0, time.Local)
	stopDate = time.Date(2023, 7, 22, 0, 0, 0, 0, time.Local)
	// Критерий для отбора бумаг. Акции, фонды, акции и фонды
	selection = SHARES_AND_ETFS
	// Режим запуска теста, на одном конфиге или перебор сгенерированных конфигов
	mode = TEST_WITH_CONFIG
    ...
	// Конфиг бэктеста для режима TEST_WITH_CONFIG
	configToTest = bot.BacktestConfig{
		Analyse:                 bot.MATH_STAT,
		LowPercentile:           0,
		HighPercentile:          0,
		MinProfit:                0.3,
		DaysToCalculateInterval: 4,
		StopLoss:                1000.0,
		// Для тарифа "Трейдер" комиссия за сделку с акцией составляет 0.05% от стоимости сделки
		Commission: 0.05,
	}
    ...
)

Сохраним изменения и запустим бэктест:

  go run cmd/backtest/backtest.go

Результат: средний профит в день = 0,14%, с учетом комиссии брокера — около 3,2% прибыли в месяц.

Результат бэктеста с конфигурацией № 2
Результат бэктеста с конфигурацией № 2

Изменяя конфигурацию, можно добиться различных значений профита. Как показывает практика, не бывает одного стабильно прибыльного конфига на все случаи. Очень прибыльная стратегия на этой неделе может оказаться убыточной на следующей, поэтому нужно проводить много разных тестов при выборе конфигурации для стратегии.

Статистика и реальные торги

Немного статистики по тестам, которые проводились на одних и тех же данных, а параметры конфигурации не менялись.

Зависимость среднего профита от ширины интервала
Зависимость среднего профита от ширины интервала

По форме кривой сложно определить закономерность, потому что профитность определенного интервала напрямую зависит от графика цены, а он, в свою очередь, непредсказуем. В большинстве случаев нет смысла рассматривать интервал шире 1%.

Зависимость среднего профита от количества дней
Зависимость среднего профита от количества дней

На графике зависимости от количества дней видно, как влияет на средний профит количество дней для расчета интервала. На этот раз зависимость более закономерная, потому что тест проходит внутри одного дня. Зачастую для расчета достаточно пары предыдущих дней, а большее количество начинает мешать.

Не стоит считать, что лучшее значение ширины интервала = 0,7, а лучшее количество дней для расчета = 1, так как нужно обязательно проверять эти параметры вместе в определенный промежуток времени.

Для запуска торгов на реальной бирже необходимо изменить config.yaml:

AccountId: "<id брокерского счета>"
APIToken: <токен с полным доступом>
EndPoint: invest-public-api.tinkoff.ru:443
AppName: invest-api-go-sdk
DisableResourceExhaustedRetry: false
DisableAllRetry: false
MaxRetries: 3

Удобно ID реального брокерского счета получить через графический gRPC-клиент.

В конфигурации используется тип анализа свечей BEST_WIDTH, интервал рассчитывается на основе одного предыдущего дня.

intervalConfig = bot.IntervalStrategyConfig{
	PreferredPositionPrice:  250,
	MaxPositionPrice:        700,
    TopInstrumentsQuantity:  10,
	MinProfit:               0.3,
	DaysToCalculateInterval: 1,
	StopLossPercent:         2.9,
	AnalyseLowPercentile:    0,
	AnalyseHighPercentile:   0,
	Analyse:                 bot.BEST_WIDTH,
	...
}

При запуске бота будет распечатан лог, в котором можно увидеть баланс денежных средств и начальную сумму, которая будет задействована в процессе торговли:

Сообщение на старте бота
Сообщение на старте бота
Итоги реальных торгов
Итоги реальных торгов

Результат: торги проводились в течение половины рабочего дня, профит = 5,1 ₽ (около 0,13%). Значение, близкое к тем, что мы получали ранее при помощи бэктеста.

Заключение

Мы рассмотрели одну из простейших алгоритмических стратегий и ее реализацию на языке Go, используя публичное API Тинькофф Инвестиций. Этот пример позволяет попробовать различные конфигурации стратегии, проанализировать инструменты, модифицировать или оптимизировать алгоритм и многое другое.

Если у вас есть своя стратегия и вы хотите ее автоматизировать, в коде этого робота и директории с примерами собрана базовая инфраструктура для работы с рыночными данными и торговыми поручениями, которая может помочь вам в написании собственного робота.

У нас есть телеграм-чат с разработчиками API — присоединяйтесь или оставляйте свои вопросы в комментариях. Документация по InvestAPI.

Комментарии (7)


  1. chernish2
    05.12.2023 19:46

    Расскажите пожалуйста, если я хочу добиться максимальной скорости обмена данными с вашими endpoints, какие действия следует предпринять? Начать наверное с того, чтобы разместить сервер максимально близко-где именно? И как ещё можно ускорить обмен? В идеале хотелось бы делать десятки запросов в секунду. Спасибо.


    1. comerc
      05.12.2023 19:46
      +3

      https://tinkoff.github.io/investAPI/speedup/

      Тинькофф Инвестиции осуществляют pre-trade контроль рисков. Это значит, что сначала на стороне брокера проверяется достаточность средств для исполнения поручения и позиций для покупки или продажи, соответствие цен и после этого заявка уходит на биржу. 

      Плюс такого подхода — нельзя купить «лишних» бумаг и получить margin call. Но минус — дополнительные задержки при исполнении ордеров, которые в среднем составляют 200—400 мс. 

       Еще у брокера есть ограничение на количество выставленных заявок в единицу времени — на момент написания статьи ограничение составляет 300 поручений в минуту. Поэтому HFT-стратегии, требующие минимальных задержек и большого количества поручений, скорее не подходят для работы через Tinkoff API.


    1. jstalex Автор
      05.12.2023 19:46
      +1

      Здравствуйте!
      Про сетевые задержи и датацентры можно прочитать тут
      Лимитная политика по количеству запросов на пользователя тут
      Задержки при работе через InvestAPI измеряются сотнями миллисекунд, если вы рассчитываете на HFT торговлю, вам это может не подойти.


  1. LaRN
    05.12.2023 19:46

    А есть возможность прогнать эту стратегию на реальных данных, а не исторических. Но при этом в демо режиме, без реальной торговли? Так можно было бы реально её оценить, погоняв пару дней например.


    1. jstalex Автор
      05.12.2023 19:46

      Здравствуйте!
      Да, можно. У InvestAPI есть тестовый контур, чтобы изменить этот параметр нужно в файле config.yaml нужно изменить EndPoint :
      EndPoint: sandbox-invest-public-api.tinkoff.ru:443 - тестовый контур

      EndPoint: invest-public-api.tinkoff.ru:443 - реальный контур
      Однако тестовый контур не полностью отражает ситуацию на реальном рынке, поэтому результат может отличаться.


      1. LaRN
        05.12.2023 19:46

        Спасибо. И ещё тогда вопрос: Как учитывается комиссия брокера в расчёте доходности в этой стратегии. Ведь по факту за эти три сделки можно заплатить 25 р комисси при 24 р дохода.


        1. jstalex Автор
          05.12.2023 19:46

          Если ширина интервала в процентах больше чем процент комиссии*2, то такого не может быть. А в бэктесте через руками задается комиссия. Про 24 рубля это просто пример для наглядности)