ВАЖНО: данная статья создана исключительно в образовательных целях. Торговля на бирже связана с большим риском и может привести к значительным убыткам.
Первая часть статьи доступна по ссылке.
Введение (и напоминание)
В предыдущей части статьи мы разобрали, как написать простейшего торгового бота на python
с использованием Binance API. Стратегия, которую мы использовали, основывалась на статичном коридоре цены на паре ETHUSDT. Такая стратегия крайне редко применима на практике и в нашем случае служила только в качестве примера, однако, она проиллюстрировала важные методы при торговле с реальными стратегиями.
Кратко напомним основные шаги стратегии из предыдущей части статьи:
Запросить последнюю цену актива на бирже (без разделения на BID или ASK в простейшем случае);
Если мы вышли за верхнюю границу коридора: закрыть лонг и открыть шорт;
Если мы вышли за нижнюю границу коридора: закрыть шорт и открыть лонг.
Теперь, используя алгоритм выше, мы можем напомнить общую структуру реализации данной стратегии на языке python
:
# imports and functions are omitted here as they stay exactly the same
# flags to track the exchanges to avoid duplicated entries
in_short = False
in_long = False
# main loop
while True:
# get latest price for the symbol
latest_price = client.futures_symbol_ticker(symbol=symbol)['price']
latest_price = float(latest_price)
# check the condition (these conditions could be substitued by any other)
if "<condintion>":
# if we are in long position, close it
if in_long:
long_close(symbol=symbol, quantity=1)
in_long = False
# if we are not in short position, open it
if not in_short:
short_open(symbol=symbol, quantity=1)
in_short = True
elif "<other_contition>":
# if we are in long position, close it
if not in_long:
long_open(symbol=symbol, quantity=1)
in_long = True
# if we are in short position, close it
if in_short:
short_close(symbol=symbol, quantity=1)
in_short = False
# 1 second sleep
time.sleep(1)
То есть, если заменить условия <condition>
и <other_condition>
на некоторые реальные условия, то код выше может служить основой для полноценной торговой стратегии.
Базовые (статистические) стратегии
Переходя к настоящим торговым стратегиям на одном активе (торговля несколькими активами заслуживает отдельного серьезного разговора) хочется выделить три основных направления в статистических стратегиях (статистическими стратегиями будем называть те стратегии, которые основываются только на данных о предыдущей цене и объемах торговли и не учитывают "внешних" факторов влияющих на рынок):
Пересечение скользящих средних (moving average crossover): стратегия рассматривает две скользящие средние с разными периодами и входит в лонг, когда линия с меньшим периодом пересекает линию с бОльшим периодом снизу вверх и входит в шорт, наоборот, при пересечении линии с меньшим периодом линии с бОльшим периодом сверху вниз.
Динамический коридор цены (channel breakout): стратегия рассматривает одну скользящую среднюю а также коридор цены, верхняя граница которого соответствует максимуму цены за предыдущие n свеч, а нижняя -- минимуму за предыдущие n свеч.
-
Линии Боллинджера (Bollinger bands): стратегия также строит коридор цены, но по другим правилам.
Верхняя граница коридора = скользящая средняя + 2 стандартных отклонения цены;
Нижняя граница коридора = скользящая средняя - 2 стандартных отклонения цены.
Правила для входа и выхода стратегии определяются следующим образом:
При пересечении цены верхней линии Боллинджера (снизу вверх) открывается шорт. Шорт закрывается при пересечении ценой скользящей средней (сверху вниз).
При пересечении цены нижней линии Боллинджера (сверху вниз) открывается лонг. Лонг закрывается при пересечении ценой скользящей средней (снизу вверх).
Линии Боллинджера
В данной статье мы подробно разберем линии Боллинджера (реализация других стратегий, после освоения линий Боллинджера, не должна составить труда).
Начнем с определения:
Линии (полосы) Боллинджера (Bollinger bands) — инструмент технического анализа финансовых рынков, отражающий текущие отклонения цены акции, товара или валюты.
Основное предположение стратегии состоит в том, что при значительном отклонении цены от скользящей средней, она вернется назад. Преимущество линий Боллинджера -- простота их вычисления. Представим, что мы имеем данные о ценах закрытия 5 минутных свечей некоторого актива (см таблицу ниже), тогда мы можем вычислить линии Боллинджера используя следующие формулы:
В таблице представлены цены закрытия а также значения скользящих средних и отклонений, а также значения линий Боллиндджера:
Время закрытия |
Цена закрытия |
Скользящая средняя с периодом 3 свечи (15 минут) |
Стандартное отклонение с периодом 3 свечи (15 минут) |
Боллинджер верхняя граница |
Боллинджер верхняя граница |
12:00 |
100.0 |
NaN |
NaN |
NaN |
NaN |
12:05 |
100.1 |
NaN |
NaN |
NaN |
NaN |
12:10 |
100.2 |
100.1 |
0.1 |
100.3 |
99.9 |
12:15 |
100.1 |
100.133333 |
0.57735 |
100.248803 |
100.017863 |
12:25 |
100.3 |
100.2 |
0.1 |
100.4 |
100.0 |
12:30 |
100.5 |
100.3 |
0.2 |
100.7 |
99.9 |
12:35 |
100.2 |
100.333333 |
0.152753 |
100.638838 |
100.027828 |
12:40 |
100.0 |
100.233333 |
0.251661 |
100.736656 |
99.730011 |
12:45 |
99.5 |
99.9 |
0.360555 |
100.621110 |
99.178890 |
На рисунке 1 ниже представлены линии Боллинджера (синий и фиолетовый цвета), а также скользящая средняя (оранжевый цвет). Помимо линий Боллинджера представлены точки входа и выхода из сделок (согласно стратегии). Они отмечены вертикальными линиями. Вертикальная пунктирная линия соответствует моменту входа в сделку, а вертикальная сплошная линия -- моменту выхода из сделки. Число рядом с подписью внизу сделки обозначает ее доход (убыток) в процентах.
Имплементация на Python
Теперь мы можем использовать теорию выше для имплементации стратегии на языке python
. Существенное отличие текущей стратегии от стратегии статичного коридора цены в первой части статьи состоит в том, что теперь мы хотим получать цену актива только лишь в момент закрытия свечи, то есть, каждые 5 минут (константу 5 минут
можно легко изменять).
Вычисление линий Боллинджера
Код ниже показывает, как можно вычислять линии Боллинджера для интервалов в 5 минут с периодом 3 свечи (15 минут):
import numpy as np
import time
from datetime import datetime
from binance.client import Client
# list to keep all closing prices
prices = []
# lists to keep all stats needed
moving_average_values = []
bollinger_band_high_values = []
bollinger_band_low_values = []
# flags to track the exchanges to avoid duplicated entries
in_short = False
in_long = False
# period of Bollinger bands
period = 3
last_time = ""
# main loop
while True:
current_time = datetime.now()
# if current candlestick has closed and current time is not equal to the
# latest record (to avoid duplicated price records)
if current_time.minute % 5 == 0 and current_time.strftime('%Y-%m-%d %H:%M') != last_time:
last_time = current_time.strftime('%Y-%m-%d %H:%M')
# get latest price for the symbol
latest_price = client.futures_symbol_ticker(symbol=symbol)['price']
latest_price = float(latest_price)
prices.append(latest_price)
# print latest price with current time
print(current_time, latest_price)
# calculate moving average and deviation
ma = np.mean(prices[-period:])
moving_average_values.append(ma)
std = np.std(prices[-period:], ddof=1)
# calculate Bollinger bands
bb_high = ma + 2 * std
bb_low = ma - 2 * std
bollinger_band_high_values.append(bb_high)
bollinger_band_low_values.append(bb_low)
Правила входа (выхода) в сделки
Когда мы имеем значения линий Боллинджера и скользящей средней, мы можем перейти к имплементации самой стратегии. Для начала разберем условия входа в сделку:
# check if current closing price is higher than the upper band
# but previous closing price is lower than the upper band
# this indicates that the crossing occured on current candlestick
if prices[-2] < bollinger_band_high_values[-2] and prices[-1] > bollinger_band_high_values[-1]:
# if we are not in short position, open it
if not in_short:
short_open(symbol=symbol, quantity=1)
in_short = True
# check if current closing price is lower than the lower band
# but previous closing price is higher than the lower band
# this indicates that the crossing occured on current candlestick
if prices[-1] < bollinger_band_low_values[-1] and prices[-2] > bollinger_band_low_values[-2]:
# if we are not in long position, open it
if not in_long:
long_open(symbol=symbol, quantity=1)
in_long = True
Теперь разберем условия выхода из сделки:
# check if current closing price is lower than the moving average
# but previous closing price is higher than the moving average
# this indicates that the crossing occured on current candlestick
if prices[-2] > moving_average_values[-2] and prices[-1] < moving_average_values[-1]:
if in_short:
short_close(symbol=symbol, quantity=1)
in_short = False
# vice versa
if prices[-2] < moving_average_values[-2] and prices[-1] > moving_average_values[-1]:
if in_long:
long_close(symbol=symbol, quantity=1)
in_long = False
Наконец, соберем все кусочки вместе и получим полноценную стратегию!
import numpy as np
import time
from datetime import datetime
from binance.client import Client
# exchange operations:
def short_open(symbol, quantity):
client.futures_create_order(symbol=symbol,
side='SELL',
type='MARKET',
quantity=quantity)
def short_close(symbol, quantity):
client.futures_create_order(symbol=symbol,
side='BUY',
type='MARKET',
quantity=quantity)
def long_open(symbol, quantity):
client.futures_create_order(symbol=symbol,
side='BUY',
type='MARKET',
quantity=quantity)
def long_close(symbol, quantity):
client.futures_create_order(symbol=symbol,
side='SELL',
type='MARKET',
quantity=quantity)
# initialize Client with your API keys
api_key = "<your_api_key>"
api_secret = "<your_private_api_key>"
client = Client(api_key, api_secret)
# traded symbol
symbol = "ETHUSDT"
# list to keep all closing prices
prices = []
# lists to keep all stats needed
moving_average_values = []
bollinger_band_high_values = []
bollinger_band_low_values = []
# flags to track the exchanges to avoid duplicated entries
in_short = False
in_long = False
# period of Bollinger bands
period = 3
last_time = ""
# main loop
while True:
current_time = datetime.now()
# if current candlestick has closed and current time is not equal to the
# latest record (to avoid duplicated price records)
if current_time.minute % 5 == 0 and current_time.strftime('%Y-%m-%d %H:%M') != last_time:
last_time = current_time.strftime('%Y-%m-%d %H:%M')
# get latest price for the symbol
latest_price = client.futures_symbol_ticker(symbol=symbol)['price']
latest_price = float(latest_price)
prices.append(latest_price)
# print latest price with current time
print(current_time, latest_price)
# calculate moving average and deviation
ma = np.mean(prices[-period:])
moving_average_values.append(ma)
std = np.std(prices[-period:], ddof=1)
# calculate Bollinger bands
bb_high = ma + 2 * std
bb_low = ma - 2 * std
bollinger_band_high_values.append(bb_high)
bollinger_band_low_values.append(bb_low)
# descion of the strategy
# short exchanges
if prices[-2] < bollinger_band_high_values[-2] and prices[-1] > bollinger_band_high_values[-1]:
if not in_short:
short_open(symbol=symbol, quantity=1)
in_short = True
if prices[-2] > moving_average_values[-2] and prices[-1] < moving_average_values[-1]:
if in_short:
short_close(symbol=symbol, quantity=1)
in_short = False
# long conditions
if prices[-1] < bollinger_band_low_values[-1] and prices[-2] > bollinger_band_low_values[-2]:
if not in_long:
long_open(symbol=symbol, quantity=1)
in_long = True
if prices[-2] < moving_average_values[-2] and prices[-1] > moving_average_values[-1]:
if in_long:
long_close(symbol=symbol, quantity=1)
in_long = False
time.sleep(1)
Спасибо за прочтение!
Комментарии (26)
Shamanische
26.12.2023 19:21+3# if current candlestick closed if datetime.now().minute % 5 == 0: # get latest price for the symbol latest_price = client.futures_symbol_ticker(symbol=symbol)['price'] latest_price = float(latest_price) prices.append(latest_price)
Вот это условие будет в течение 60 секунд добавлять цены в массив и запрашивать клиентский API, и еще, нужно проверять не текущее время суток, а время изменения цены, иначе, предположим, если рынок "замрет" на час, массив будет наполнен протухшими ценами
Alexander_Shulzhenko Автор
26.12.2023 19:21Да, Вы абсолютно правы, сейчас исправлю. Пытался упростить код, но допустил глупую ошибку :)
UPD: исправлено, спасибо за Ваше замечание!
Shamanische
26.12.2023 19:21лучше запрашивать готовые чарты, очень много мелочей нужно учитывать, например, бар может открыться с опозданием на несколько минут, или на несколько минут раньше закрыться, или вообще может быть пропуск бара.
более правильный подход - запросить снапшот баров, подписаться на асинхронные обновления и дополнять снапшот, тогда у вас будет всегда актуальный набор данныхAlexander_Shulzhenko Автор
26.12.2023 19:21Да, это правда, это можно сделать с помощью функции Klines, описанной в первой части статьи, однако Binance (по крайней мере по моему опыту) крайне редко ломает свечи
milssky
26.12.2023 19:21+1У вас очень много бесполезного дублирования кода.
'%Y-%m-%d %H:%M'
Прекрасно выносится в константы. Это удобно, расположено в одном месте.
prices[-2] < bollinger_band_high_values[-2] and prices[-1] > bollinger_band_high_values[-1]
Вот эти чудовища с индексами. Получите их один раз из своих контейнеров перед условиями. Аналогично со вторыми с конца.
Четыре функции вверху, которых на самом деле одна с разными параметрами. Ну или две. Исследуйте partial из functools или просто тупо через равно уберите дублирование кода :)
Я не очень вникал в предметную область, но недостаточно ли собирать за одну итерацию цикла текущее время? :)
Так же, ваш код не готов к ошибкам. Что будет если binance приляжет на секунду или запрос от вашего client будет неудачным? Наверняка будет выброшено исключение, которые остановит эту программу. И ее придется запустить заново ручками.Alexander_Shulzhenko Автор
26.12.2023 19:21В тексте указано, что для простоты функции дублированы, поскольку открыть шорт и закрыть лонг для рынка фьючерсов -- это одно и то же (на этот счет даже написан комментарий, возможно, в первой части статьи). В то же время обработка всех исключений и ошибок сделает код более объемным и трудным для понимания. Задача сделать код оптимальным по количеству символов также не стояла :) Зато, надеюсь, некоторые дублирования помогут читателю лучше понять суть алгоритма
EPIDEMIASH
26.12.2023 19:21-1А зачем, если бинанс не для россии?
Alexander_Shulzhenko Автор
26.12.2023 19:21В целом, биржи используют очень похожие API, поэтому перепрофилировать код для работы с другой биржей не должно быть очень сложно :)
milssky
26.12.2023 19:21Профилирование -- это про другое ;) от вашего кода была бы польза, если бы алгоритм этот был отделен от всей мишуры. Вот это был бы прорыв, самолёты бы начали строить быстрее и все такое :) а сейчас...
dyadyaSerezha
26.12.2023 19:21И главный вопрос - оно того стоит?)
Alexander_Shulzhenko Автор
26.12.2023 19:21Смотря, в чем измерять)
dyadyaSerezha
26.12.2023 19:21+1В дополнительных манях в месяц)
Alexander_Shulzhenko Автор
26.12.2023 19:21Красота торговых роботов в том, что они предоставляют безграничный простор для творчества, поэтому успех алгоритма зависит от Вас :)
omgiafs
26.12.2023 19:21+2Автоматизированная рулетка.
Я не очень сообразителен, поэтому мне в своё время понадобилось что-то около недели знакомства с предметом, чтобы понять, что весь этот "технический анализ" - это попытка увидеть будущее, основываясь на графиках прошлого. Причем ладно бы был известен список факторов, влияющих на результат, и их можно было бы как-то мониторить, но там результат зависит от буквально всего. Лично я бы предпочёл иметь в друзьях не самого крутого "технического аналитика", а любимого сыночку верхних работников ЦБ. В первом случае ты пытаешься угадать будущее, во втором - ты его знаешь.
Alexander_Shulzhenko Автор
26.12.2023 19:21Вы во многом правы) Технический анализ -- это действительно "предсказание" будущего на основе некоторых статистик из прошлого (в нашем случае -- скользящего среднего и отклонения). Весь технический анализ держится примерно на том, что рынок ведет себя "согласно некоторому паттерну", который и может описываться правилами технического анализа. И иногда это правда, поскольку, если углубиться в теорию случайных процессов, первое что Вы увидите -- это (скорее всего), что цена актива (P_t) не стационарный процесс, а значит не имеет свойства "отката" (mean-reverting property). Однако локально (на этом и держится тех анализ), возможно, это и имеет место быть...
Но, на моем опыте, техническому анализу очень мешают всплески волатильности (и резкие движения (скачки) цены), которые ведут к значительным потерям. По поводу предсказания всплесков волатильности можете посмотреть мой репозиторий с моделью)
https://github.com/AlexanderShulzhenko/Short-term-Volatility-PredictiondyadyaSerezha
26.12.2023 19:21Репориторий с любой прекрасной моделью не говорит ни о чем. Значение имеет только одно - насколько он лучше рынка по прибыли.
Alexander_Shulzhenko Автор
26.12.2023 19:21Так я и не утверждал ничего с помощью репозитория. Единственный посыл был в том, что "вот можно пытаться предсказывать всплески волатильности с помощью каких-то признаков"
dyadyaSerezha
26.12.2023 19:21Не могу не согласиться полностью, назвав это до кучи гаданием на кофейной гуще. Не в смысле примененной теории, а в смысле конечного результата.
Уверен, что большинство проф.трейдеров имеют все эти анализы нв 10 порядков круче, глубже и ширше, но где-то читал про исследование о том, что половина проф.трейдеров имеют итоговый процент роста их фондов меньше среднего по рынку (брать случайные акции или по индексу) - что и должно быть - если кто-то выигрывает, то только за счёт того, что кто-то проигрывает.
omgiafs
26.12.2023 19:21Так вся биржевая торговля - игра с нулевой суммой. Особенно - бинарные опционы, их в части европейских государств признали просто азартной игрой. Все пытаются именно что выиграть у остальных. При этом тратя кучу ресурсов на, фактически, перераспределенние ресурсов. Меня в том числе и это оттолкнуло от всей биржевой торговли, потому что никакой пользы обществу ты не приносишь этой деятельностью. Да и себе тоже, по большому счёту. Стал богаче (конкретно ты - ой, вряд ли), отобрав у другого такого же лудомана. Просто жажда наживы и всё. Поэтому вокруг этой темы куча инфоцыган, у которых в ЦА, судя по картинкам и текстам в ТГ, аудитория подросткового и чуть выше возраста, так как истории успешного успеха у каждого второго на фото - "с пачками деняк в салоне бэхи". Для лудоманов постарше предлагают Баффета, сдабривая это книжками тех единиц, которые пришли к успеху. Те сотни и тысячи, чьи деньги успешным единицам перетекли в результате "торговли", увы, книг не написали.
Чисто технически вся эта "торговля" - задача интересная, к тому же азарт наживы подогревает интерес. Но лично я, узнав про суть всей движухи, по этическим причинам заниматься этой гадостью не буду. Для меня деньги "пахнут". Потому я неконкурентоспособный и сознательно выбрал проигрыш в эволюционной борьбе.
IvanTheCrazy
26.12.2023 19:21А почему бы не использовать talib для вычисления тех же полос боллинджера? Одна строчка и готово
Alexander_Shulzhenko Автор
26.12.2023 19:21Спасибо что упомянули talib, хотел написать об этой библиотеке в следующей части. Конечно, можно использовать talib для простоты, однако, я хотел показать именно техническую сторону вычисления индикаторов
nnh
26.12.2023 19:21+1А как этот API решает вопросы контроля? В коде есть магические команды, типа:
client.futures_create_order
Но, это же деньги. Значит контроль должен быть. И по своему опыту, говоря о любых скриптах такого рода, первое о чем надо говорить - это не стратегии, а том как это все заставить работать постоянно, с восстановлением состояния после падений, контролем исполнения команд и т.д. И это будет большая часть кода. Но постоянно вижу скрипты где этот вопрос вообще не разбирается, а есть только алгоритмы принятия решений. Хотя они по большей части бессмысленны.
milssky
Судя по комментам в коде, его писал chatGPT =)
Alexander_Shulzhenko Автор
Код от начала и до конца (и даже все комментарии) написаны мной лично