Перед прочтением этой статьи — ВАЖНО следующее: основная цель данной статьи заключается в том, чтобы показать как просто можно создать торгового робота, который может торговать российскими акциями или зарубежными акциями. Важно понимать, что создавая бота, вы лично несете ответственность за принимаемые им решения, инвестиционные операции и связанные с ними риски. Я не несу ответственности за решения, которые вы можете принять после прочтения этого материала. И я не даю никаких инвестиционных рекомендаций или советов. Не забывайте, что боты способны принести большие убытки, поэтому используйте их с осторожностью.
В этой статье будем рассматривать Российских брокеров для торговли Российскими акциями, если вы захотите торговать иностранными акциями — то это тоже можно сделать через них же — через СПБ биржу. (код торгового робота не поменяется — поменяется только название тикера — торговой бумаги, которой вы будете торговать).
Чтобы вас долго не мучать с выбором хорошего брокера для торгового бота, я приведу мои решения, которые сформировались после длительной практики по написанию торговых ботов, работающих в live режиме — прямо сейчас торгующих российскими акциями.
список сделок за сегодня, таймфрейм H1
Нужный и важный компонент в разработке торгового бота — это возможность тестирования вашей стратегии на истории, например используя простую библиотеку
Итак приступим! )))
1й вариант — если очень сильно хочется иметь робота, который может торговать практически через любого брокера — то есть очень хорошее решение использовать библиотеку
2й вариант — он немного ограничивает в выборе брокеров, но даёт прекрасную возможность общаться с разработчиками API брокеров, и они!! заметьте быстро и эффективно исправляют косяки) и добавляют функционал — это большой плюс!
Для брокера Финам — API еще в разработке ))) библиотеки
Для брокера Тинькофф — библиотека
* Несколько примеров кода опубликовал в их репозитории —
Для брокера Алор — библиотеки
ОФФТОПИК: Если кому интересно подключение к криптобирже — то я написал свою библиотеку
Итак, выбираем последнего брокера — Алор. )) Если вам интересно увидеть как написать торгового робота для Финам или Тинькофф — как это настроить и сделать, я могу рассказать в отдельной подробной статье, просто голосуйте за это! )))
Создание нового проекта для алго-трейдинга
Открытый терминал проекта
pip install git+
P.S. Пожалуйста, используйте
И наконец у нас есть некоторые зависимости, которые вам нужно так же установить
pip install requests pytz websockets matplotlib
Нам нужны два файла 02 - Symbols.py и Strategy.py из папки BackTraderAlor\DataExamples
02 - Symbols.py и Strategy.py
Копируем их в корень нашего проекта.
помещаем файлы в корень проекта
Также нам понадобится файл конфигурации, он находится в папке AlorPy\Config.py
файл конфигурации
Его тоже копируем в корень проекта в папку my_config(её нужно создать), должно получиться так:
создаем свой файл конфигурации
Перед запуском примера 02 — Symbols.py, необходимо:
1) получить свой API ключ и вписать его в поле RefreshToken;
2) узнать свой UserName и вписать его в поле UserName;
3) узнать значение портфеля для Фондового рынка и вписать его в поле PortfolioStocks
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение;
4) узнать значение портфеля для Срочного рынка и вписать его в поле PortfolioFutures
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение;
5) узнать значение портфеля для Валютного рынка и вписать его в поле PortfolioFx
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение.
Напоминаю, что всё это прописываем в файле my_config\Config.py
И как все эти значения получить, так же прописано в этом же файле.
Остальные подсчета можно заполнить по необходимости, заполненного будет уже достаточно для торговли на основном счете на фондовом рынке.
Такой конфиг примерно получится:
2) Для получения тестового логина/пароля демо счета оставить заявку в Telegram на
3) Зарегистрироваться на
4) Выбрать «Токены для доступа к API».
меняем AlorPy.Config на my_config.Config станет так:
назначение своего конфиг файла
Отключите LiveBars режим, установив LiveBars=False
Теперь запускаем пример для проверки подключения... Должно получиться так:
LifeDars=False, и бары пришли
Как мы видим — тест удался — бары пришли.
Теперь можно приступать к созданию первого торгового робота!!
импорт необходимых_библиотек
класс Индикаторов
класс Стратегии/Торговой системы
# --- основной раздел ---
подключение по API к бирже
задание параметров запуска стратегии
запуск стратегии
получение данных по тикеру/тикерам по API
обработка этих данных стратегией
выставление заявок на покупку/продажу
возврат результатов из стратегии
вывод результатов
Как мы видим, нам осталось реализовать пункты:
Итак основной файл для запуска торговой стратегии называется 02 — Symbols.py, вот его код:
from datetime import date, datetime
from backtrader import Cerebro, TimeFrame
from BackTraderAlor.ALStore import ALStore # Хранилище Alor
from my_config.Config import Config # Файл конфигурации
import Strategy as ts # Торговые системы
# Несколько тикеров для нескольких торговых систем по одному временнОму интервалу
if name == 'main': # Точка входа при запуске этого скрипта
symbols = ('MOEX.SBER', 'MOEX.GAZP', 'MOEX.LKOH', 'MOEX.GMKN',) # Кортеж тикеров
store = ALStore(UserName=Config.UserName, RefreshToken=Config.RefreshToken, Boards=Config.Boards, Accounts=Config.Accounts) # Хранилище Alor
cerebro = Cerebro(stdstats=False) # Инициируем "движок" BackTrader. Стандартная статистика сделок и кривой доходности не нужна
for symbol in symbols: # Пробегаемся по всем тикерам
# data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=1, fromdate=date.today(), LiveBars=False) # Исторические и новые бары тикера с начала сессии
data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=15, fromdate=datetime(2021, 10, 4), LiveBars=False) # Исторические и новые бары тикера с начала сессии
cerebro.adddata(data) # Добавляем тикер
# cerebro.addstrategy(ts.PrintStatusAndBars, name="One Ticker", symbols=('MOEX.SBER',)) # Добавляем торговую систему по одному тикеру
# cerebro.addstrategy(ts.PrintStatusAndBars, name="Two Tickers", symbols=('MOEX.GAZP', 'MOEX.LKOH',)) # Добавляем торговую систему по двум тикерам
cerebro.addstrategy(ts.PrintStatusAndBars, name="All Tickers") # Добавляем торговую систему по всем тикерам
cerebro.run() # Запуск торговой системы
Внесенные в него изменения:
import backtrader as bt
class PrintStatusAndBars(bt.Strategy):
"""
- Отображает статус подключения
- При приходе нового бара отображает его цены/объем
- Отображает статус перехода к новым барам
"""
params = ( # Параметры торговой системы
('name', None), # Название торговой системы
('symbols', None), # Список торгуемых тикеров. По умолчанию торгуем все тикеры
)
def log(self, txt, dt=None):
"""Вывод строки с датой на консоль"""
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата последнего бара первого тикера ТС
print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль
def init(self):
"""Инициализация торговой системы"""
self.isLive = False # Сначала будут приходить исторические данные
def next(self):
"""Приход нового бара тикера"""
# if self.p.name: # Если указали название торговой системы, то будем ждать прихода всех баров
# lastdatetimes = [bt.num2date(data.datetime[0]) for data in self.datas] # Дата и время последнего бара каждого тикера
# if lastdatetimes.count(lastdatetimes[0]) != len(lastdatetimes): # Если дата и время последних баров не идентичны
# return # то еще не пришли все новые бары. Ждем дальше, выходим
# print(self.p.name)
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
if not self.p.symbols or data._name in self.p.symbols: # Если торгуем все тикеры или данный тикер
self.log(f'{data._name} - {bt.TimeFrame.Names[data.p.timeframe]} {data.p.compression} - Open={data.open[0]:.2f}, High={data.high[0]:.2f}, Low={data.low[0]:.2f}, Close={data.close[0]:.2f}, Volume={data.volume[0]:.0f}',
bt.num2date(data.datetime[0]))
def notify_data(self, data, status, *args, **kwargs):
"""Изменение статсуса приходящих баров"""
data_status = data._getstatusname(status) # Получаем статус (только при LiveBars=True)
print(f'{data._name} - {self.p.name} - {data_status}') # Статус приходит для каждого тикера отдельно
self.isLive = data_status == 'LIVE' # В Live режим переходим после перехода первого тикера
Внесенные изменения в него:
контрольный запуск
Видим, что пришли 15-минутные бары и все ОК.
....
24.04.2023 19:45, MOEX.GMKN - Minutes 15 - Open=15676.00, High=15682.00, Low=15672.00, Close=15678.00, Volume=216
24.04.2023 20:00, MOEX.SBER - Minutes 15 - Open=235.23, High=235.23, Low=235.03, Close=235.07, Volume=10064
24.04.2023 20:00, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.00, Low=181.80, Close=181.94, Volume=13728
24.04.2023 20:00, MOEX.LKOH - Minutes 15 - Open=4707.50, High=4707.50, Low=4705.00, Close=4706.00, Volume=1439
24.04.2023 20:00, MOEX.GMKN - Minutes 15 - Open=15672.00, High=15682.00, Low=15672.00, Close=15682.00, Volume=153
24.04.2023 20:15, MOEX.SBER - Minutes 15 - Open=235.03, High=235.11, Low=235.00, Close=235.06, Volume=11403
24.04.2023 20:15, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.30, Low=181.85, Close=182.16, Volume=29052
24.04.2023 20:15, MOEX.LKOH - Minutes 15 - Open=4706.00, High=4714.00, Low=4705.50, Close=4714.00, Volume=2092
24.04.2023 20:15, MOEX.GMKN - Minutes 15 - Open=15682.00, High=15682.00, Low=15676.00, Close=15682.00, Volume=265
24.04.2023 20:30, MOEX.SBER - Minutes 15 - Open=235.06, High=235.11, Low=235.03, Close=235.05, Volume=4995
24.04.2023 20:30, MOEX.GAZP - Minutes 15 - Open=182.16, High=182.23, Low=181.91, Close=181.96, Volume=18775
24.04.2023 20:30, MOEX.LKOH - Minutes 15 - Open=4714.00, High=4714.00, Low=4711.00, Close=4712.00, Volume=642
24.04.2023 20:30, MOEX.GMKN - Minutes 15 - Open=15682.00, High=15694.00, Low=15680.00, Close=15692.00, Volume=440
24.04.2023 20:45, MOEX.SBER - Minutes 15 - Open=235.04, High=235.11, Low=235.02, Close=235.06, Volume=10582
24.04.2023 20:45, MOEX.GAZP - Minutes 15 - Open=181.96, High=181.96, Low=181.70, Close=181.81, Volume=13281
24.04.2023 20:45, MOEX.LKOH - Minutes 15 - Open=4712.00, High=4714.00, Low=4710.00, Close=4711.00, Volume=2349
24.04.2023 20:45, MOEX.GMKN - Minutes 15 - Open=15694.00, High=15696.00, Low=15686.00, Close=15686.00, Volume=180
24.04.2023 21:00, MOEX.SBER - Minutes 15 - Open=235.06, High=235.10, Low=235.01, Close=235.10, Volume=7533
24.04.2023 21:00, MOEX.GAZP - Minutes 15 - Open=181.81, High=181.94, Low=181.75, Close=181.91, Volume=5271
24.04.2023 21:00, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4711.00, Low=4710.00, Close=4711.00, Volume=1955
24.04.2023 21:00, MOEX.GMKN - Minutes 15 - Open=15688.00, High=15694.00, Low=15688.00, Close=15692.00, Volume=137
24.04.2023 21:15, MOEX.SBER - Minutes 15 - Open=235.10, High=235.15, Low=235.09, Close=235.15, Volume=9973
24.04.2023 21:15, MOEX.GAZP - Minutes 15 - Open=181.91, High=181.97, Low=181.82, Close=181.89, Volume=7075
24.04.2023 21:15, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4713.00, Low=4710.50, Close=4712.50, Volume=1519
24.04.2023 21:15, MOEX.GMKN - Minutes 15 - Open=15692.00, High=15698.00, Low=15688.00, Close=15698.00, Volume=229
24.04.2023 21:30, MOEX.SBER - Minutes 15 - Open=235.14, High=235.18, Low=235.11, Close=235.17, Volume=7582
24.04.2023 21:30, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.14, Low=181.86, Close=182.03, Volume=9900
24.04.2023 21:30, MOEX.LKOH - Minutes 15 - Open=4713.00, High=4713.00, Low=4708.50, Close=4709.50, Volume=2170
24.04.2023 21:30, MOEX.GMKN - Minutes 15 - Open=15696.00, High=15698.00, Low=15690.00, Close=15694.00, Volume=73
24.04.2023 21:45, MOEX.SBER - Minutes 15 - Open=235.16, High=235.30, Low=235.13, Close=235.15, Volume=22209
24.04.2023 21:45, MOEX.GAZP - Minutes 15 - Open=182.03, High=182.15, Low=182.01, Close=182.01, Volume=5444
24.04.2023 21:45, MOEX.LKOH - Minutes 15 - Open=4709.50, High=4712.00, Low=4708.00, Close=4710.50, Volume=894
24.04.2023 21:45, MOEX.GMKN - Minutes 15 - Open=15690.00, High=15696.00, Low=15660.00, Close=15694.00, Volume=938
24.04.2023 22:00, MOEX.SBER - Minutes 15 - Open=235.15, High=235.26, Low=235.11, Close=235.22, Volume=11980
24.04.2023 22:00, MOEX.GAZP - Minutes 15 - Open=182.04, High=182.13, Low=181.90, Close=182.11, Volume=10208
24.04.2023 22:00, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4719.00, Low=4709.00, Close=4717.00, Volume=4481
24.04.2023 22:00, MOEX.GMKN - Minutes 15 - Open=15694.00, High=15700.00, Low=15690.00, Close=15700.00, Volume=444
24.04.2023 22:15, MOEX.SBER - Minutes 15 - Open=235.25, High=235.28, Low=235.22, Close=235.22, Volume=8336
24.04.2023 22:15, MOEX.GAZP - Minutes 15 - Open=182.11, High=182.11, Low=181.96, Close=182.05, Volume=5406
24.04.2023 22:15, MOEX.LKOH - Minutes 15 - Open=4718.50, High=4719.00, Low=4716.50, Close=4719.00, Volume=2532
24.04.2023 22:15, MOEX.GMKN - Minutes 15 - Open=15700.00, High=15702.00, Low=15688.00, Close=15700.00, Volume=161
24.04.2023 22:30, MOEX.SBER - Minutes 15 - Open=235.24, High=235.24, Low=235.18, Close=235.20, Volume=3598
24.04.2023 22:30, MOEX.GAZP - Minutes 15 - Open=182.05, High=182.07, Low=181.96, Close=181.99, Volume=6737
24.04.2023 22:30, MOEX.LKOH - Minutes 15 - Open=4718.50, High=4719.00, Low=4716.50, Close=4717.00, Volume=1011
24.04.2023 22:30, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15700.00, Low=15688.00, Close=15698.00, Volume=134
24.04.2023 22:45, MOEX.SBER - Minutes 15 - Open=235.20, High=235.20, Low=235.02, Close=235.08, Volume=14638
24.04.2023 22:45, MOEX.GAZP - Minutes 15 - Open=182.00, High=182.11, Low=181.99, Close=182.06, Volume=3898
24.04.2023 22:45, MOEX.LKOH - Minutes 15 - Open=4718.00, High=4719.00, Low=4714.00, Close=4717.00, Volume=1931
24.04.2023 22:45, MOEX.GMKN - Minutes 15 - Open=15692.00, High=15700.00, Low=15692.00, Close=15692.00, Volume=185
24.04.2023 23:00, MOEX.SBER - Minutes 15 - Open=235.09, High=235.15, Low=235.03, Close=235.10, Volume=9085
24.04.2023 23:00, MOEX.GAZP - Minutes 15 - Open=182.06, High=182.09, Low=182.00, Close=182.03, Volume=7404
24.04.2023 23:00, MOEX.LKOH - Minutes 15 - Open=4716.50, High=4719.00, Low=4715.00, Close=4718.00, Volume=1214
24.04.2023 23:00, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15710.00, Low=15690.00, Close=15690.00, Volume=604
24.04.2023 23:15, MOEX.SBER - Minutes 15 - Open=235.10, High=235.17, Low=235.03, Close=235.14, Volume=12324
24.04.2023 23:15, MOEX.GAZP - Minutes 15 - Open=182.03, High=182.10, Low=181.69, Close=181.86, Volume=19210
24.04.2023 23:15, MOEX.LKOH - Minutes 15 - Open=4717.50, High=4717.50, Low=4714.50, Close=4716.00, Volume=509
24.04.2023 23:15, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15700.00, Low=15688.00, Close=15694.00, Volume=78
Теперь можно приступать к самому интересному - написанию торговой стратегии для робота!
1) устанавливаем, сколько денег у нас на счету и размер комиссии
cerebro.broker.setcash(3000000) # Устанавливаем сколько денег
cerebro.broker.setcommission(commission=0.01) # Установить комиссию
2) результат работы торговой стратегии возвращаем в главный файл и выводим на экран
print('Стоимость портфеля: %.2f' % cerebro.broker.getvalue())
print('Свободные средства: %.2f' % cerebro.broker.get_cash())
пункты 1) и 2) добавляем в файл 02 - Symbols.py
3) если вы захотите включить live режим работы вашей торговой стратегии, то это делается следующими четырьмя строчками ‑!!! но не рекомендую этого делать, т.к. все заявки на покупку и продажу сразу начнут попадать на биржу и будут пытаться выполняться так, как у вас написано в коде!! - если вы пробегаетесь по истории - то и скрипт будет пытаться выставить в рынок по "старой" цене... а текущая цена далеко уже не та... БУДЬТЕ ЗДЕСЬ ВНИМАТЕЛЬНЫ! Для live режима — не пробегайтесь по истории.
exchange = 'MOEX' # Биржа
portfolio = Config.PortfolioStocks # Портфель фондового рынка
broker = store.getbroker(use_positions=False, portfolio=portfolio, exchange=exchange) # Брокер Alor
cerebro.setbroker(broker) # Устанавливаем брокера
Этот код не добавляем! Просто для инфо, как включить live режим.
Класс торговой системы имеет несколько основных методов:
4) В init добавляем:
self.order = None
self.orders_bar_executed = {}
5) В start добавляем:
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
ticker = data._dataname # имя тикера
self.orders_bar_executed[ticker] = 0
6) В next добавляем:
ticker = data._dataname # имя тикера
_close = data.close[0] # текущий close
_low = data.low[0] # текущий low
_high = data.high[0] # текущий high
_open = data.open[0] # текущий close
# Проверка, мы в рынке?
if not self.position:
# Ещё нет... мы МОГЛИ БЫ КУПИТЬ, если бы...
if self.data.close[0] < self.data.close[-1]:
# текущее закрытие меньше предыдущего закрытия
if self.data.close[-1] < self.data.close[-2]:
# ПОКУПАЙ, ПОКУПАЙ, ПОКУПАЙ!!! (с параметрами по умолчанию)
self.log('BUY CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.buy(data=data) # , size=size)
else:
# Уже в рынке? ... мы могли бы продать
try:
# продаём после 5 баров от момента покупки...
if len(self) >= (self.orders_bar_executed[data._name] + 5):
# ПРОДАВАЙ, ПРОДАВАЙ, ПРОДАВАЙ!!! (с параметрами по умолчанию)
self.log('SELL CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.sell(data=data)
except:
print("error...")
7) добавляем функцию notify_trade:
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
8) добавляем функцию notify_order:
def notify_order(self, order):
ticker = order.data._name
size = order.size
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Проверка, мы в рынке?
# Внимание: брокер может отклонить заявку, если недостаточно денег
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
self.bar_executed = len(self)
self.orders_bar_executed[order.data._name] = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Запись: отложенного ордера - нет
self.order = None
пункты 4), 5), 6), 7) и 8) добавляем в файл стратегии Strategy.py
В принципе описание кода достаточно интуитивно показывает смысл стратегии, что мы всегда покупаем и продаем через 5 баров.
Стратегия: мы всегда покупаем и продаем через 5 баров
Итак, код файла 02 - Symbols.py:
from datetime import date, datetime
from backtrader import Cerebro, TimeFrame
from BackTraderAlor.ALStore import ALStore # Хранилище Alor
from my_config.Config import Config # Файл конфигурации
import Strategy as ts # Торговые системы
# Несколько тикеров для нескольких торговых систем по одному временнОму интервалу
if name == 'main': # Точка входа при запуске этого скрипта
symbols = ('MOEX.SBER', 'MOEX.GAZP', 'MOEX.LKOH', 'MOEX.GMKN',) # Кортеж тикеров
store = ALStore(UserName=Config.UserName, RefreshToken=Config.RefreshToken, Boards=Config.Boards, Accounts=Config.Accounts) # Хранилище Alor
cerebro = Cerebro(stdstats=False) # Инициируем "движок" BackTrader. Стандартная статистика сделок и кривой доходности не нужна
cerebro.broker.setcash(3000000) # Устанавливаем сколько денег
cerebro.broker.setcommission(commission=0.01) # Установить комиссию
for symbol in symbols: # Пробегаемся по всем тикерам
# data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=1, fromdate=date.today(), LiveBars=False) # Исторические и новые бары тикера с начала сессии
data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=15, fromdate=datetime(2021, 10, 4), LiveBars=False) # Исторические и новые бары тикера с начала сессии
cerebro.adddata(data) # Добавляем тикер
# cerebro.addstrategy(ts.PrintStatusAndBars, name="One Ticker", symbols=('MOEX.SBER',)) # Добавляем торговую систему по одному тикеру
# cerebro.addstrategy(ts.PrintStatusAndBars, name="Two Tickers", symbols=('MOEX.GAZP', 'MOEX.LKOH',)) # Добавляем торговую систему по двум тикерам
cerebro.addstrategy(ts.PrintStatusAndBars, name="All Tickers") # Добавляем торговую систему по всем тикерам
results = cerebro.run() # Запуск торговой системы
print('Стоимость портфеля: %.2f' % cerebro.broker.getvalue())
print('Свободные средства: %.2f' % cerebro.broker.get_cash())
Итак, код файла Strategy.py
import backtrader as bt
class PrintStatusAndBars(bt.Strategy):
"""
- Отображает статус подключения
- При приходе нового бара отображает его цены/объем
- Отображает статус перехода к новым барам
"""
params = ( # Параметры торговой системы
('name', None), # Название торговой системы
('symbols', None), # Список торгуемых тикеров. По умолчанию торгуем все тикеры
)
def log(self, txt, dt=None):
"""Вывод строки с датой на консоль"""
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата последнего бара первого тикера ТС
print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль
def init(self):
"""Инициализация торговой системы"""
self.isLive = False # Сначала будут приходить исторические данные
self.order = None
self.orders_bar_executed = {}
def start(self):
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
ticker = data._dataname # имя тикера
self.orders_bar_executed[ticker] = 0
def next(self):
"""Приход нового бара тикера"""
# if self.p.name: # Если указали название торговой системы, то будем ждать прихода всех баров
# lastdatetimes = [bt.num2date(data.datetime[0]) for data in self.datas] # Дата и время последнего бара каждого тикера
# if lastdatetimes.count(lastdatetimes[0]) != len(lastdatetimes): # Если дата и время последних баров не идентичны
# return # то еще не пришли все новые бары. Ждем дальше, выходим
# print(self.p.name)
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
if not self.p.symbols or data._name in self.p.symbols: # Если торгуем все тикеры или данный тикер
self.log(f'{data._name} - {bt.TimeFrame.Names[data.p.timeframe]} {data.p.compression} - Open={data.open[0]:.2f}, High={data.high[0]:.2f}, Low={data.low[0]:.2f}, Close={data.close[0]:.2f}, Volume={data.volume[0]:.0f}',
bt.num2date(data.datetime[0]))
ticker = data._dataname # имя тикера
_close = data.close[0] # текущий close
_low = data.low[0] # текущий low
_high = data.high[0] # текущий high
_open = data.open[0] # текущий close
# Проверка, мы в рынке?
if not self.position:
# Ещё нет... мы МОГЛИ БЫ КУПИТЬ, если бы...
if self.data.close[0] < self.data.close[-1]:
# текущее закрытие меньше предыдущего закрытия
if self.data.close[-1] < self.data.close[-2]:
# ПОКУПАЙ, ПОКУПАЙ, ПОКУПАЙ!!! (с параметрами по умолчанию)
self.log('BUY CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.buy(data=data) # , size=size)
else:
# Уже в рынке? ... мы могли бы продать
try:
# продаём после 5 баров от момента покупки...
if len(self) >= (self.orders_bar_executed[data._name] + 5):
# ПРОДАВАЙ, ПРОДАВАЙ, ПРОДАВАЙ!!! (с параметрами по умолчанию)
self.log('SELL CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.sell(data=data)
except:
print("error...")
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
def notify_order(self, order):
ticker = order.data._name
size = order.size
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Проверка, мы в рынке?
# Внимание: брокер может отклонить заявку, если недостаточно денег
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
self.bar_executed = len(self)
self.orders_bar_executed[order.data._name] = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Запись: отложенного ордера - нет
self.order = None
def notify_data(self, data, status, *args, **kwargs):
"""Изменение статуса приходящих баров"""
data_status = data._getstatusname(status) # Получаем статус (только при LiveBars=True)
print(f'{data._name} - {self.p.name} - {data_status}') # Статус приходит для каждого тикера отдельно
self.isLive = data_status == 'LIVE' # В Live режим переходим после перехода первого тикера
Теперь давайте запустим эту стратегию и посмотрим результат!
результат запуска стратегии
Стоимость портфеля: 1 294 456.74
Свободные средства: 45 568 288.40
ЧТО???
Круто? Да? )))
Сразу скажу, что это не грааль — и в коде есть небольшая ошибка, которая приводит к таким ошеломляющим результатам)))
Кому интересно, заснял видео с разоблачением такой сверх доходности, доступно по
Итак, просто пишите код торгового робота, тестируете его на истории, включаете Live режим, и запускаете в работу)
Пару слов обо мне
Программирование для меня это хобби и любимое дело. А так я сертифицированный системный архитектор. Поэтому прошу не особо ругать за код:‑)Выбор брокера и библиотек
Как вы знаете, брокеров много))) но нам нужны те, у которых есть API — программный интерфейс через который наш торговый робот сможет отправлять заявки на покупку и продажу акций.В этой статье будем рассматривать Российских брокеров для торговли Российскими акциями, если вы захотите торговать иностранными акциями — то это тоже можно сделать через них же — через СПБ биржу. (код торгового робота не поменяется — поменяется только название тикера — торговой бумаги, которой вы будете торговать).
Чтобы вас долго не мучать с выбором хорошего брокера для торгового бота, я приведу мои решения, которые сформировались после длительной практики по написанию торговых ботов, работающих в live режиме — прямо сейчас торгующих российскими акциями.
![список сделок за сегодня, таймфрейм H1 список сделок за сегодня, таймфрейм H1](https://habrastorage.org/r/w1560/getpro/habr/upload_files/4ac/8ba/e29/4ac8bae29e16fb3fd7715c2a24f81473.png)
список сделок за сегодня, таймфрейм H1
Нужный и важный компонент в разработке торгового бота — это возможность тестирования вашей стратегии на истории, например используя простую библиотеку
Для просмотра ссылки необходимо нажать
Вход или Регистрация
.Итак приступим! )))
1й вариант — если очень сильно хочется иметь робота, который может торговать практически через любого брокера — то есть очень хорошее решение использовать библиотеку
Для просмотра ссылки необходимо нажать
Вход или Регистрация
в связке с библиотекой
Для просмотра ссылки необходимо нажать
Вход или Регистрация
— использование этих двух библиотек позволит вашему торговому роботу работать с любым брокером, у которого есть возможность предоставить вам торговый терминал Quik. А этот торговый терминал есть у большинства брокеров.Множество примеров по этой связке специально для вас выложилЕсли вам интересно, как это настроить и сделать, я могу рассказать в отдельной подробной статье, просто голосуйте за это! )))
Для просмотра ссылки необходимо нажать
Вход или Регистрация
.2й вариант — он немного ограничивает в выборе брокеров, но даёт прекрасную возможность общаться с разработчиками API брокеров, и они!! заметьте быстро и эффективно исправляют косяки) и добавляют функционал — это большой плюс!
Для брокера Финам — API еще в разработке ))) библиотеки
Для просмотра ссылки необходимо нажать
Вход или Регистрация
+
Для просмотра ссылки необходимо нажать
Вход или Регистрация
Для брокера Тинькофф — библиотека
Для просмотра ссылки необходимо нажать
Вход или Регистрация
* Несколько примеров кода опубликовал в их репозитории —
Для просмотра ссылки необходимо нажать
Вход или Регистрация
которая использует только API ТинькоффДля брокера Алор — библиотеки
Для просмотра ссылки необходимо нажать
Вход или Регистрация
и
Для просмотра ссылки необходимо нажать
Вход или Регистрация
ОФФТОПИК: Если кому интересно подключение к криптобирже — то я написал свою библиотеку
Для просмотра ссылки необходимо нажать
Вход или Регистрация
, она работает так же, т. е. один и тот же код, можно использовать для разных активов, вот про
Для просмотра ссылки необходимо нажать
Вход или Регистрация
.Итак, выбираем последнего брокера — Алор. )) Если вам интересно увидеть как написать торгового робота для Финам или Тинькофф — как это настроить и сделать, я могу рассказать в отдельной подробной статье, просто голосуйте за это! )))
Приступаем к написанию торгового бота
Подготовка окружения
- Устанавливаем последнюю версию
Для просмотра ссылки необходимо нажать Вход или Регистрация;
- Устанавливаем среду разработки
Для просмотра ссылки необходимо нажать Вход или Регистрация;
- Запускаем PyCharm Community;
- В нём создаем новый проект, давайте его назовём alor_trade_robot и укажем что создаем виртуальное окружение Virtualenv, с Python 3.11 => нажимаем "Create";
![Создание нового проекта для алго-трейдинга Создание нового проекта для алго-трейдинга](https://habrastorage.org/r/w1560/getpro/habr/upload_files/25e/7c2/428/25e7c24286057b8ec6cd65ade3525431.png)
Создание нового проекта для алго-трейдинга
- После того, как проект создался и в нём создалось виртуальное окружение, мы стали готовы к установке необходимых библиотек))) Кликаем внизу слева на "Terminal" для открытия терминала, в котором как раз и будем вводить команды установки библиотек;
![Открытый терминал проекта Открытый терминал проекта](https://habrastorage.org/r/w1560/getpro/habr/upload_files/ea8/76f/7a2/ea876f7a20e7af81e6a7e235f1bd7709.png)
Открытый терминал проекта
- Устанавливаем необходимые библиотеки:
В терминале вводим команды для подключения к брокеру Алор по API:
git cloneДля просмотра ссылки необходимо нажать Вход или Регистрация
для интеграции API Алора сДля просмотра ссылки необходимо нажать Вход или Регистрация:
git cloneДля просмотра ссылки необходимо нажать Вход или Регистрация
после этих манипуляций у нас появилось две папки AlorPy и BackTraderAlor
установка AlorPy и BackTraderAlor
Для просмотра ссылки необходимо нажать
Вход или Регистрация
pip install git+
Для просмотра ссылки необходимо нажать
Вход или Регистрация
P.S. Пожалуйста, используйте
Для просмотра ссылки необходимо нажать
Вход или Регистрация
из моего репозитория (так как вы можете размещать в нем свои коммиты).И наконец у нас есть некоторые зависимости, которые вам нужно так же установить
pip install requests pytz websockets matplotlib
Создание конфигурации для торговой стратегии
Чтобы было легче разобраться с этими библиотеками, есть множество примеров внутри этих папок AlorPy и BackTraderAlor.Нам нужны два файла 02 - Symbols.py и Strategy.py из папки BackTraderAlor\DataExamples
![02 - Symbols.py и Strategy.py 02 - Symbols.py и Strategy.py](https://habrastorage.org/r/w1560/getpro/habr/upload_files/c4a/309/f0a/c4a309f0a18d47b15783ea691e30198a.png)
02 - Symbols.py и Strategy.py
Копируем их в корень нашего проекта.
![помещаем файлы в корень проекта помещаем файлы в корень проекта](https://habrastorage.org/r/w1560/getpro/habr/upload_files/2ce/c8f/8b1/2cec8f8b1c6731d19b71e72589e507b0.png)
помещаем файлы в корень проекта
Также нам понадобится файл конфигурации, он находится в папке AlorPy\Config.py
![файл конфигурации файл конфигурации](https://habrastorage.org/r/w1560/getpro/habr/upload_files/2f3/abb/100/2f3abb100dd86a21fcbef43b844628ae.png)
файл конфигурации
Его тоже копируем в корень проекта в папку my_config(её нужно создать), должно получиться так:
![создаем свой файл конфигурации создаем свой файл конфигурации](https://habrastorage.org/r/w1560/getpro/habr/upload_files/f7b/b44/1b6/f7bb441b6c2bbeaf2cf2f25a394fdb00.png)
создаем свой файл конфигурации
Перед запуском примера 02 — Symbols.py, необходимо:
1) получить свой API ключ и вписать его в поле RefreshToken;
2) узнать свой UserName и вписать его в поле UserName;
3) узнать значение портфеля для Фондового рынка и вписать его в поле PortfolioStocks
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение;
4) узнать значение портфеля для Срочного рынка и вписать его в поле PortfolioFutures
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение;
5) узнать значение портфеля для Валютного рынка и вписать его в поле PortfolioFx
— через файл AlorPy\Examples\02 — Accounts.py — получаем это значение.
Напоминаю, что всё это прописываем в файле my_config\Config.py
И как все эти значения получить, так же прописано в этом же файле.
Остальные подсчета можно заполнить по необходимости, заполненного будет уже достаточно для торговли на основном счете на фондовом рынке.
Такой конфиг примерно получится:
![cbf2935864f955f6c8c026e29547d81a.png](https://habrastorage.org/r/w1560/getpro/habr/upload_files/cbf/293/586/cbf2935864f955f6c8c026e29547d81a.png)
Как получить токен Refresh Token
1)
Для просмотра ссылки необходимо нажать
Вход или Регистрация
(можно сделать удаленно!);2) Для получения тестового логина/пароля демо счета оставить заявку в Telegram на
Для просмотра ссылки необходимо нажать
Вход или Регистрация
;3) Зарегистрироваться на
Для просмотра ссылки необходимо нажать
Вход или Регистрация
;4) Выбрать «Токены для доступа к API».
Проверки подключения к Алору через API
В файле 02 — Symbols.py мы должны подключить свой конфиг файл my_config\Config.py для этого открываем его и правим одну строку:![ca14d712f315d23b87f58c233bbb7a9b.png](https://habrastorage.org/r/w1560/getpro/habr/upload_files/ca1/4d7/12f/ca14d712f315d23b87f58c233bbb7a9b.png)
меняем AlorPy.Config на my_config.Config станет так:
![назначение своего конфиг файла назначение своего конфиг файла](https://habrastorage.org/r/w1560/getpro/habr/upload_files/12c/ccb/9d7/12cccb9d7bf004f9f222d936ab235daf.png)
назначение своего конфиг файла
Отключите LiveBars режим, установив LiveBars=False
Теперь запускаем пример для проверки подключения... Должно получиться так:
![LifeDars=False, и бары пришли LifeDars=False, и бары пришли](https://habrastorage.org/r/w1560/getpro/habr/upload_files/a25/07a/77d/a2507a77de5fdc09ca27808331ce533c.png)
LifeDars=False, и бары пришли
Как мы видим — тест удался — бары пришли.
Теперь можно приступать к созданию первого торгового робота!!
Создание торгового робота для торговли акциями
Для создания торгового робота обычно придерживаются некоторой структуры кода, можно сказать шаблона, по которому код работает с торговой стратегией и с данными с рынка по тикеру/тикерам и после отработки выводится некоторый результат.импорт необходимых_библиотек
класс Индикаторов
класс Стратегии/Торговой системы
# --- основной раздел ---
подключение по API к бирже
задание параметров запуска стратегии
запуск стратегии
получение данных по тикеру/тикерам по API
обработка этих данных стратегией
выставление заявок на покупку/продажу
возврат результатов из стратегии
вывод результатов
Как мы видим, нам осталось реализовать пункты:
- «обработка этих данных стратегией»;
- «выставление заявок на покупку/продажу»;
- возврат результатов из стратегии;
- вывод результатов.
Итак основной файл для запуска торговой стратегии называется 02 — Symbols.py, вот его код:
from datetime import date, datetime
from backtrader import Cerebro, TimeFrame
from BackTraderAlor.ALStore import ALStore # Хранилище Alor
from my_config.Config import Config # Файл конфигурации
import Strategy as ts # Торговые системы
# Несколько тикеров для нескольких торговых систем по одному временнОму интервалу
if name == 'main': # Точка входа при запуске этого скрипта
symbols = ('MOEX.SBER', 'MOEX.GAZP', 'MOEX.LKOH', 'MOEX.GMKN',) # Кортеж тикеров
store = ALStore(UserName=Config.UserName, RefreshToken=Config.RefreshToken, Boards=Config.Boards, Accounts=Config.Accounts) # Хранилище Alor
cerebro = Cerebro(stdstats=False) # Инициируем "движок" BackTrader. Стандартная статистика сделок и кривой доходности не нужна
for symbol in symbols: # Пробегаемся по всем тикерам
# data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=1, fromdate=date.today(), LiveBars=False) # Исторические и новые бары тикера с начала сессии
data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=15, fromdate=datetime(2021, 10, 4), LiveBars=False) # Исторические и новые бары тикера с начала сессии
cerebro.adddata(data) # Добавляем тикер
# cerebro.addstrategy(ts.PrintStatusAndBars, name="One Ticker", symbols=('MOEX.SBER',)) # Добавляем торговую систему по одному тикеру
# cerebro.addstrategy(ts.PrintStatusAndBars, name="Two Tickers", symbols=('MOEX.GAZP', 'MOEX.LKOH',)) # Добавляем торговую систему по двум тикерам
cerebro.addstrategy(ts.PrintStatusAndBars, name="All Tickers") # Добавляем торговую систему по всем тикерам
cerebro.run() # Запуск торговой системы
Внесенные в него изменения:
- поменял таймфрейм на M15;
- оставил применение торговой системы/стратегии ко всем тикерам;
- и дату старта получения баров установил на datetime(2021, 10, 4).
import backtrader as bt
class PrintStatusAndBars(bt.Strategy):
"""
- Отображает статус подключения
- При приходе нового бара отображает его цены/объем
- Отображает статус перехода к новым барам
"""
params = ( # Параметры торговой системы
('name', None), # Название торговой системы
('symbols', None), # Список торгуемых тикеров. По умолчанию торгуем все тикеры
)
def log(self, txt, dt=None):
"""Вывод строки с датой на консоль"""
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата последнего бара первого тикера ТС
print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль
def init(self):
"""Инициализация торговой системы"""
self.isLive = False # Сначала будут приходить исторические данные
def next(self):
"""Приход нового бара тикера"""
# if self.p.name: # Если указали название торговой системы, то будем ждать прихода всех баров
# lastdatetimes = [bt.num2date(data.datetime[0]) for data in self.datas] # Дата и время последнего бара каждого тикера
# if lastdatetimes.count(lastdatetimes[0]) != len(lastdatetimes): # Если дата и время последних баров не идентичны
# return # то еще не пришли все новые бары. Ждем дальше, выходим
# print(self.p.name)
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
if not self.p.symbols or data._name in self.p.symbols: # Если торгуем все тикеры или данный тикер
self.log(f'{data._name} - {bt.TimeFrame.Names[data.p.timeframe]} {data.p.compression} - Open={data.open[0]:.2f}, High={data.high[0]:.2f}, Low={data.low[0]:.2f}, Close={data.close[0]:.2f}, Volume={data.volume[0]:.0f}',
bt.num2date(data.datetime[0]))
def notify_data(self, data, status, *args, **kwargs):
"""Изменение статсуса приходящих баров"""
data_status = data._getstatusname(status) # Получаем статус (только при LiveBars=True)
print(f'{data._name} - {self.p.name} - {data_status}') # Статус приходит для каждого тикера отдельно
self.isLive = data_status == 'LIVE' # В Live режим переходим после перехода первого тикера
Внесенные изменения в него:
- в функции def next(self): - закомментил синхронность получения баров - т.к. для моей стратегии это не нужно.
![контрольный запуск контрольный запуск](https://habrastorage.org/r/w1560/getpro/habr/upload_files/092/7ae/e21/0927aee21cad9bcce3152a97e943f918.png)
контрольный запуск
Видим, что пришли 15-минутные бары и все ОК.
....
24.04.2023 19:45, MOEX.GMKN - Minutes 15 - Open=15676.00, High=15682.00, Low=15672.00, Close=15678.00, Volume=216
24.04.2023 20:00, MOEX.SBER - Minutes 15 - Open=235.23, High=235.23, Low=235.03, Close=235.07, Volume=10064
24.04.2023 20:00, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.00, Low=181.80, Close=181.94, Volume=13728
24.04.2023 20:00, MOEX.LKOH - Minutes 15 - Open=4707.50, High=4707.50, Low=4705.00, Close=4706.00, Volume=1439
24.04.2023 20:00, MOEX.GMKN - Minutes 15 - Open=15672.00, High=15682.00, Low=15672.00, Close=15682.00, Volume=153
24.04.2023 20:15, MOEX.SBER - Minutes 15 - Open=235.03, High=235.11, Low=235.00, Close=235.06, Volume=11403
24.04.2023 20:15, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.30, Low=181.85, Close=182.16, Volume=29052
24.04.2023 20:15, MOEX.LKOH - Minutes 15 - Open=4706.00, High=4714.00, Low=4705.50, Close=4714.00, Volume=2092
24.04.2023 20:15, MOEX.GMKN - Minutes 15 - Open=15682.00, High=15682.00, Low=15676.00, Close=15682.00, Volume=265
24.04.2023 20:30, MOEX.SBER - Minutes 15 - Open=235.06, High=235.11, Low=235.03, Close=235.05, Volume=4995
24.04.2023 20:30, MOEX.GAZP - Minutes 15 - Open=182.16, High=182.23, Low=181.91, Close=181.96, Volume=18775
24.04.2023 20:30, MOEX.LKOH - Minutes 15 - Open=4714.00, High=4714.00, Low=4711.00, Close=4712.00, Volume=642
24.04.2023 20:30, MOEX.GMKN - Minutes 15 - Open=15682.00, High=15694.00, Low=15680.00, Close=15692.00, Volume=440
24.04.2023 20:45, MOEX.SBER - Minutes 15 - Open=235.04, High=235.11, Low=235.02, Close=235.06, Volume=10582
24.04.2023 20:45, MOEX.GAZP - Minutes 15 - Open=181.96, High=181.96, Low=181.70, Close=181.81, Volume=13281
24.04.2023 20:45, MOEX.LKOH - Minutes 15 - Open=4712.00, High=4714.00, Low=4710.00, Close=4711.00, Volume=2349
24.04.2023 20:45, MOEX.GMKN - Minutes 15 - Open=15694.00, High=15696.00, Low=15686.00, Close=15686.00, Volume=180
24.04.2023 21:00, MOEX.SBER - Minutes 15 - Open=235.06, High=235.10, Low=235.01, Close=235.10, Volume=7533
24.04.2023 21:00, MOEX.GAZP - Minutes 15 - Open=181.81, High=181.94, Low=181.75, Close=181.91, Volume=5271
24.04.2023 21:00, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4711.00, Low=4710.00, Close=4711.00, Volume=1955
24.04.2023 21:00, MOEX.GMKN - Minutes 15 - Open=15688.00, High=15694.00, Low=15688.00, Close=15692.00, Volume=137
24.04.2023 21:15, MOEX.SBER - Minutes 15 - Open=235.10, High=235.15, Low=235.09, Close=235.15, Volume=9973
24.04.2023 21:15, MOEX.GAZP - Minutes 15 - Open=181.91, High=181.97, Low=181.82, Close=181.89, Volume=7075
24.04.2023 21:15, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4713.00, Low=4710.50, Close=4712.50, Volume=1519
24.04.2023 21:15, MOEX.GMKN - Minutes 15 - Open=15692.00, High=15698.00, Low=15688.00, Close=15698.00, Volume=229
24.04.2023 21:30, MOEX.SBER - Minutes 15 - Open=235.14, High=235.18, Low=235.11, Close=235.17, Volume=7582
24.04.2023 21:30, MOEX.GAZP - Minutes 15 - Open=181.87, High=182.14, Low=181.86, Close=182.03, Volume=9900
24.04.2023 21:30, MOEX.LKOH - Minutes 15 - Open=4713.00, High=4713.00, Low=4708.50, Close=4709.50, Volume=2170
24.04.2023 21:30, MOEX.GMKN - Minutes 15 - Open=15696.00, High=15698.00, Low=15690.00, Close=15694.00, Volume=73
24.04.2023 21:45, MOEX.SBER - Minutes 15 - Open=235.16, High=235.30, Low=235.13, Close=235.15, Volume=22209
24.04.2023 21:45, MOEX.GAZP - Minutes 15 - Open=182.03, High=182.15, Low=182.01, Close=182.01, Volume=5444
24.04.2023 21:45, MOEX.LKOH - Minutes 15 - Open=4709.50, High=4712.00, Low=4708.00, Close=4710.50, Volume=894
24.04.2023 21:45, MOEX.GMKN - Minutes 15 - Open=15690.00, High=15696.00, Low=15660.00, Close=15694.00, Volume=938
24.04.2023 22:00, MOEX.SBER - Minutes 15 - Open=235.15, High=235.26, Low=235.11, Close=235.22, Volume=11980
24.04.2023 22:00, MOEX.GAZP - Minutes 15 - Open=182.04, High=182.13, Low=181.90, Close=182.11, Volume=10208
24.04.2023 22:00, MOEX.LKOH - Minutes 15 - Open=4711.00, High=4719.00, Low=4709.00, Close=4717.00, Volume=4481
24.04.2023 22:00, MOEX.GMKN - Minutes 15 - Open=15694.00, High=15700.00, Low=15690.00, Close=15700.00, Volume=444
24.04.2023 22:15, MOEX.SBER - Minutes 15 - Open=235.25, High=235.28, Low=235.22, Close=235.22, Volume=8336
24.04.2023 22:15, MOEX.GAZP - Minutes 15 - Open=182.11, High=182.11, Low=181.96, Close=182.05, Volume=5406
24.04.2023 22:15, MOEX.LKOH - Minutes 15 - Open=4718.50, High=4719.00, Low=4716.50, Close=4719.00, Volume=2532
24.04.2023 22:15, MOEX.GMKN - Minutes 15 - Open=15700.00, High=15702.00, Low=15688.00, Close=15700.00, Volume=161
24.04.2023 22:30, MOEX.SBER - Minutes 15 - Open=235.24, High=235.24, Low=235.18, Close=235.20, Volume=3598
24.04.2023 22:30, MOEX.GAZP - Minutes 15 - Open=182.05, High=182.07, Low=181.96, Close=181.99, Volume=6737
24.04.2023 22:30, MOEX.LKOH - Minutes 15 - Open=4718.50, High=4719.00, Low=4716.50, Close=4717.00, Volume=1011
24.04.2023 22:30, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15700.00, Low=15688.00, Close=15698.00, Volume=134
24.04.2023 22:45, MOEX.SBER - Minutes 15 - Open=235.20, High=235.20, Low=235.02, Close=235.08, Volume=14638
24.04.2023 22:45, MOEX.GAZP - Minutes 15 - Open=182.00, High=182.11, Low=181.99, Close=182.06, Volume=3898
24.04.2023 22:45, MOEX.LKOH - Minutes 15 - Open=4718.00, High=4719.00, Low=4714.00, Close=4717.00, Volume=1931
24.04.2023 22:45, MOEX.GMKN - Minutes 15 - Open=15692.00, High=15700.00, Low=15692.00, Close=15692.00, Volume=185
24.04.2023 23:00, MOEX.SBER - Minutes 15 - Open=235.09, High=235.15, Low=235.03, Close=235.10, Volume=9085
24.04.2023 23:00, MOEX.GAZP - Minutes 15 - Open=182.06, High=182.09, Low=182.00, Close=182.03, Volume=7404
24.04.2023 23:00, MOEX.LKOH - Minutes 15 - Open=4716.50, High=4719.00, Low=4715.00, Close=4718.00, Volume=1214
24.04.2023 23:00, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15710.00, Low=15690.00, Close=15690.00, Volume=604
24.04.2023 23:15, MOEX.SBER - Minutes 15 - Open=235.10, High=235.17, Low=235.03, Close=235.14, Volume=12324
24.04.2023 23:15, MOEX.GAZP - Minutes 15 - Open=182.03, High=182.10, Low=181.69, Close=181.86, Volume=19210
24.04.2023 23:15, MOEX.LKOH - Minutes 15 - Open=4717.50, High=4717.50, Low=4714.50, Close=4716.00, Volume=509
24.04.2023 23:15, MOEX.GMKN - Minutes 15 - Open=15698.00, High=15700.00, Low=15688.00, Close=15694.00, Volume=78
Теперь можно приступать к самому интересному - написанию торговой стратегии для робота!
Пишем стратегию для торгового робота
Все будем тестировать на истории - делать backtesting для нашей торговой стратегии.1) устанавливаем, сколько денег у нас на счету и размер комиссии
cerebro.broker.setcash(3000000) # Устанавливаем сколько денег
cerebro.broker.setcommission(commission=0.01) # Установить комиссию
2) результат работы торговой стратегии возвращаем в главный файл и выводим на экран
print('Стоимость портфеля: %.2f' % cerebro.broker.getvalue())
print('Свободные средства: %.2f' % cerebro.broker.get_cash())
пункты 1) и 2) добавляем в файл 02 - Symbols.py
3) если вы захотите включить live режим работы вашей торговой стратегии, то это делается следующими четырьмя строчками ‑!!! но не рекомендую этого делать, т.к. все заявки на покупку и продажу сразу начнут попадать на биржу и будут пытаться выполняться так, как у вас написано в коде!! - если вы пробегаетесь по истории - то и скрипт будет пытаться выставить в рынок по "старой" цене... а текущая цена далеко уже не та... БУДЬТЕ ЗДЕСЬ ВНИМАТЕЛЬНЫ! Для live режима — не пробегайтесь по истории.
exchange = 'MOEX' # Биржа
portfolio = Config.PortfolioStocks # Портфель фондового рынка
broker = store.getbroker(use_positions=False, portfolio=portfolio, exchange=exchange) # Брокер Alor
cerebro.setbroker(broker) # Устанавливаем брокера
Этот код не добавляем! Просто для инфо, как включить live режим.
Класс торговой системы имеет несколько основных методов:
- init - итак понятно - здесь инициализируем вспомогательные переменные и индикаторы для потоков данных;
- start - здесь однократно вспомогательным переменным присваиваем значения;
- next - вызывается каждый раз при приходе нового бара по тикеру;
- notify_order - вызывается, когда происходит покупка или продажа;
- notify_trade - вызывается когда меняется статус позиции;
- notify_data - вызывается когда меняется статус прихода бара на live режим.
4) В init добавляем:
self.order = None
self.orders_bar_executed = {}
5) В start добавляем:
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
ticker = data._dataname # имя тикера
self.orders_bar_executed[ticker] = 0
6) В next добавляем:
ticker = data._dataname # имя тикера
_close = data.close[0] # текущий close
_low = data.low[0] # текущий low
_high = data.high[0] # текущий high
_open = data.open[0] # текущий close
# Проверка, мы в рынке?
if not self.position:
# Ещё нет... мы МОГЛИ БЫ КУПИТЬ, если бы...
if self.data.close[0] < self.data.close[-1]:
# текущее закрытие меньше предыдущего закрытия
if self.data.close[-1] < self.data.close[-2]:
# ПОКУПАЙ, ПОКУПАЙ, ПОКУПАЙ!!! (с параметрами по умолчанию)
self.log('BUY CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.buy(data=data) # , size=size)
else:
# Уже в рынке? ... мы могли бы продать
try:
# продаём после 5 баров от момента покупки...
if len(self) >= (self.orders_bar_executed[data._name] + 5):
# ПРОДАВАЙ, ПРОДАВАЙ, ПРОДАВАЙ!!! (с параметрами по умолчанию)
self.log('SELL CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.sell(data=data)
except:
print("error...")
7) добавляем функцию notify_trade:
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
8) добавляем функцию notify_order:
def notify_order(self, order):
ticker = order.data._name
size = order.size
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Проверка, мы в рынке?
# Внимание: брокер может отклонить заявку, если недостаточно денег
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
self.bar_executed = len(self)
self.orders_bar_executed[order.data._name] = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Запись: отложенного ордера - нет
self.order = None
пункты 4), 5), 6), 7) и 8) добавляем в файл стратегии Strategy.py
В принципе описание кода достаточно интуитивно показывает смысл стратегии, что мы всегда покупаем и продаем через 5 баров.
![Стратегия: мы всегда покупаем и продаем через 5 баров Стратегия: мы всегда покупаем и продаем через 5 баров](https://habrastorage.org/r/w1560/getpro/habr/upload_files/516/a1e/bc3/516a1ebc32aa4ebbce6165ce5009549c.png)
Стратегия: мы всегда покупаем и продаем через 5 баров
Итак, код файла 02 - Symbols.py:
from datetime import date, datetime
from backtrader import Cerebro, TimeFrame
from BackTraderAlor.ALStore import ALStore # Хранилище Alor
from my_config.Config import Config # Файл конфигурации
import Strategy as ts # Торговые системы
# Несколько тикеров для нескольких торговых систем по одному временнОму интервалу
if name == 'main': # Точка входа при запуске этого скрипта
symbols = ('MOEX.SBER', 'MOEX.GAZP', 'MOEX.LKOH', 'MOEX.GMKN',) # Кортеж тикеров
store = ALStore(UserName=Config.UserName, RefreshToken=Config.RefreshToken, Boards=Config.Boards, Accounts=Config.Accounts) # Хранилище Alor
cerebro = Cerebro(stdstats=False) # Инициируем "движок" BackTrader. Стандартная статистика сделок и кривой доходности не нужна
cerebro.broker.setcash(3000000) # Устанавливаем сколько денег
cerebro.broker.setcommission(commission=0.01) # Установить комиссию
for symbol in symbols: # Пробегаемся по всем тикерам
# data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=1, fromdate=date.today(), LiveBars=False) # Исторические и новые бары тикера с начала сессии
data = store.getdata(dataname=symbol, timeframe=TimeFrame.Minutes, compression=15, fromdate=datetime(2021, 10, 4), LiveBars=False) # Исторические и новые бары тикера с начала сессии
cerebro.adddata(data) # Добавляем тикер
# cerebro.addstrategy(ts.PrintStatusAndBars, name="One Ticker", symbols=('MOEX.SBER',)) # Добавляем торговую систему по одному тикеру
# cerebro.addstrategy(ts.PrintStatusAndBars, name="Two Tickers", symbols=('MOEX.GAZP', 'MOEX.LKOH',)) # Добавляем торговую систему по двум тикерам
cerebro.addstrategy(ts.PrintStatusAndBars, name="All Tickers") # Добавляем торговую систему по всем тикерам
results = cerebro.run() # Запуск торговой системы
print('Стоимость портфеля: %.2f' % cerebro.broker.getvalue())
print('Свободные средства: %.2f' % cerebro.broker.get_cash())
Итак, код файла Strategy.py
import backtrader as bt
class PrintStatusAndBars(bt.Strategy):
"""
- Отображает статус подключения
- При приходе нового бара отображает его цены/объем
- Отображает статус перехода к новым барам
"""
params = ( # Параметры торговой системы
('name', None), # Название торговой системы
('symbols', None), # Список торгуемых тикеров. По умолчанию торгуем все тикеры
)
def log(self, txt, dt=None):
"""Вывод строки с датой на консоль"""
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата последнего бара первого тикера ТС
print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль
def init(self):
"""Инициализация торговой системы"""
self.isLive = False # Сначала будут приходить исторические данные
self.order = None
self.orders_bar_executed = {}
def start(self):
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
ticker = data._dataname # имя тикера
self.orders_bar_executed[ticker] = 0
def next(self):
"""Приход нового бара тикера"""
# if self.p.name: # Если указали название торговой системы, то будем ждать прихода всех баров
# lastdatetimes = [bt.num2date(data.datetime[0]) for data in self.datas] # Дата и время последнего бара каждого тикера
# if lastdatetimes.count(lastdatetimes[0]) != len(lastdatetimes): # Если дата и время последних баров не идентичны
# return # то еще не пришли все новые бары. Ждем дальше, выходим
# print(self.p.name)
for data in self.datas: # Пробегаемся по всем запрошенным тикерам
if not self.p.symbols or data._name in self.p.symbols: # Если торгуем все тикеры или данный тикер
self.log(f'{data._name} - {bt.TimeFrame.Names[data.p.timeframe]} {data.p.compression} - Open={data.open[0]:.2f}, High={data.high[0]:.2f}, Low={data.low[0]:.2f}, Close={data.close[0]:.2f}, Volume={data.volume[0]:.0f}',
bt.num2date(data.datetime[0]))
ticker = data._dataname # имя тикера
_close = data.close[0] # текущий close
_low = data.low[0] # текущий low
_high = data.high[0] # текущий high
_open = data.open[0] # текущий close
# Проверка, мы в рынке?
if not self.position:
# Ещё нет... мы МОГЛИ БЫ КУПИТЬ, если бы...
if self.data.close[0] < self.data.close[-1]:
# текущее закрытие меньше предыдущего закрытия
if self.data.close[-1] < self.data.close[-2]:
# ПОКУПАЙ, ПОКУПАЙ, ПОКУПАЙ!!! (с параметрами по умолчанию)
self.log('BUY CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.buy(data=data) # , size=size)
else:
# Уже в рынке? ... мы могли бы продать
try:
# продаём после 5 баров от момента покупки...
if len(self) >= (self.orders_bar_executed[data._name] + 5):
# ПРОДАВАЙ, ПРОДАВАЙ, ПРОДАВАЙ!!! (с параметрами по умолчанию)
self.log('SELL CREATE, %.2f' % self.data.close[0])
# Следим за созданным ордером, чтобы избежать второго дублирующегося ордера
self.order = self.sell(data=data)
except:
print("error...")
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
def notify_order(self, order):
ticker = order.data._name
size = order.size
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Проверка, мы в рынке?
# Внимание: брокер может отклонить заявку, если недостаточно денег
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
self.bar_executed = len(self)
self.orders_bar_executed[order.data._name] = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Запись: отложенного ордера - нет
self.order = None
def notify_data(self, data, status, *args, **kwargs):
"""Изменение статуса приходящих баров"""
data_status = data._getstatusname(status) # Получаем статус (только при LiveBars=True)
print(f'{data._name} - {self.p.name} - {data_status}') # Статус приходит для каждого тикера отдельно
self.isLive = data_status == 'LIVE' # В Live режим переходим после перехода первого тикера
Теперь давайте запустим эту стратегию и посмотрим результат!
![результат запуска стратегии результат запуска стратегии](https://habrastorage.org/r/w1560/getpro/habr/upload_files/4f7/7a4/2f8/4f77a42f82e190744a806eb82988a78b.png)
результат запуска стратегии
Стоимость портфеля: 1 294 456.74
Свободные средства: 45 568 288.40
ЧТО???
![Круто? Да? ))) Круто? Да? )))](https://habrastorage.org/r/w1560/getpro/habr/upload_files/e42/3fe/453/e423fe453c660142abe758e4bbc9d3ca.png)
Круто? Да? )))
Сразу скажу, что это не грааль — и в коде есть небольшая ошибка, которая приводит к таким ошеломляющим результатам)))
Кому интересно, заснял видео с разоблачением такой сверх доходности, доступно по
Для просмотра ссылки необходимо нажать
Вход или Регистрация
.Иногда лучше один раз увидеть, чем сто раз прочитать
Поэтому создание этой стратегии есть по шагам в видео, доступно по
Для просмотра ссылки необходимо нажать
Вход или Регистрация
Итог
Напоминаю, что цель данной статьи была показать — насколько легко теперь вы можете создавать своих собственных торговых роботов. Ни каких финансовых консультаций или рекомендаций не даю — просто пишем код для торгового бота. И без многих проверок — не запускайте торгового робота в live режиме...Итак, просто пишите код торгового робота, тестируете его на истории, включаете Live режим, и запускаете в работу)
Для просмотра ссылки необходимо нажать
Вход или Регистрация