15 мая 2023 smart-lab.ru Шпагин Олег

Пару слов обо мне
Программирование для меня это хобби и любимое дело. А так я сертифицированный системный архитектор. Поэтому прошу не особо ругать за код:‑)
Выбор брокера и библиотек
Как вы знаете, брокеров много))) но нам нужны те, у которых есть API — программный интерфейс через который наш торговый робот сможет отправлять заявки на покупку и продажу акций.
В этой статье будем рассматривать Российских брокеров для торговли Российскими акциями, если вы захотите торговать иностранными акциями — то это тоже можно сделать через них же — через СПБ биржу. (код торгового робота не поменяется — поменяется только название тикера — торговой бумаги, которой вы будете торговать).
Чтобы вас долго не мучать с выбором хорошего брокера для торгового бота, я приведу мои решения, которые сформировались после длительной практики по написанию торговых ботов, работающих в live режиме — прямо сейчас торгующих российскими акциями.

список сделок за сегодня, таймфрейм H1
Нужный и важный компонент в разработке торгового бота — это возможность тестирования вашей стратегии на истории, например используя простую библиотеку BackTrader.
Итак приступим! )))
1й вариант — если очень сильно хочется иметь робота, который может торговать практически через любого брокера — то есть очень хорошее решение использовать библиотеку QuikPy в связке с библиотекой BackTraderQuik — использование этих двух библиотек позволит вашему торговому роботу работать с любым брокером, у которого есть возможность предоставить вам торговый терминал Quik. А этот торговый терминал есть у большинства брокеров.
Если вам интересно, как это настроить и сделать, я могу рассказать в отдельной подробной статье, просто голосуйте за это! )))
Множество примеров по этой связке специально для вас выложил вот здесь.
2й вариант — он немного ограничивает в выборе брокеров, но даёт прекрасную возможность общаться с разработчиками API брокеров, и они!!! заметьте быстро и эффективно исправляют косяки) и добавляют функционал — это большой плюс!
Для брокера Финам — API еще в разработке ))) библиотеки FinamPy + BackTraderFinam
Для брокера Тинькофф — библиотека BackTraderTinkoff
* Несколько примеров кода опубликовал в их репозитории — пример стратегии которая использует только API Тинькофф
Для брокера Алор — библиотеки AlorPy и BackTraderAlor
ОФФТОПИК: Если кому интересно подключение к криптобирже — то я написал свою библиотеку backtrader_binance, она работает так же, т. е. один и тот же код, можно использовать для разных активов, вот про нее статья.
Итак, выбираем последнего брокера — Алор. )) Если вам интересно увидеть как написать торгового робота для Финам или Тинькофф — как это настроить и сделать, я могу рассказать в отдельной подробной статье, просто голосуйте за это! )))
Приступаем к написанию торгового бота
Устанавливаем последнюю версию Python 3.11;
Устанавливаем среду разработки PyCharm Community 2023.1;
Запускаем PyCharm Community;
В нём создаем новый проект, давайте его назовём alor_trade_robot и укажем что создаем виртуальное окружение Virtualenv, с Python 3.11 => нажимаем «Create»;

Создание нового проекта для алго-трейдинга
После того, как проект создался и в нём создалось виртуальное окружение, мы стали готовы к установке необходимых библиотек))) Кликаем внизу слева на «Terminal» для открытия терминала, в котором как раз и будем вводить команды установки библиотек;

Открытый терминал проекта
Устанавливаем необходимые библиотеки:
В терминале вводим команды для подключения к брокеру Алор по API:
git clone https://github.com/WISEPLAT/AlorPy
для интеграции API Алора с Backtrader:
git clone
после этих манипуляций у нас появилось две папки AlorPy и BackTraderAlor

установка AlorPy и BackTraderAlor
Теперь необходимо установить библиотеку тестирования торговых стратегий Backtrader
pip install git+
P.S. Пожалуйста, используйте Backtrader из моего репозитория (так как вы можете размещать в нем свои коммиты).
И наконец у нас есть некоторые зависимости, которые вам нужно так же установить
pip install requests pytz websockets matplotlib
Создание конфигурации для торговой стратегии
Чтобы было легче разобраться с этими библиотеками, есть множество примеров внутри этих папок AlorPy и BackTraderAlor.
Нам нужны два файла 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
И как все эти значения получить, так же прописано в этом же файле.
Остальные подсчета можно заполнить по необходимости, заполненного будет уже достаточно для торговли на основном счете на фондовом рынке.
Такой конфиг примерно получится:

Как получить токен Refresh Token
1) Открыть счет в Алор (можно сделать удаленно!);
2) Для получения тестового логина/пароля демо счета оставить заявку в Telegram;
3) Зарегистрироваться;
4) Выбрать «Токены для доступа к API».
Проверки подключения к Алору через API
В файле 02 — Symbols.py мы должны подключить свой конфиг файл my_config\Config.py для этого открываем его и правим одну строку:

меняем AlorPy.Config на my_config.Config станет так:

назначение своего конфиг файла
Отключите LiveBars режим, установив LiveBars=False
Теперь запускаем пример для проверки подключения… Должно получиться так:

LifeDars=False, и бары пришли
Как мы видим — тест удался — бары пришли.
Теперь можно приступать к созданию первого торгового робота!!
Создание торгового робота для торговли акциями
Для создания торгового робота обычно придерживаются некоторой структуры кода, можно сказать шаблона, по которому код работает с торговой стратегией и с данными с рынка по тикеру/тикерам и после отработки выводится некоторый результат.
<code class="python hljs">импорт необходимых_библиотек
класс Индикаторов
класс Стратегии/Торговой системы
# --- основной раздел ---
подключение по API к бирже
задание параметров запуска стратегии
запуск стратегии
получение данных по тикеру/тикерам по API
обработка этих данных стратегией
выставление заявок на покупку/продажу
возврат результатов из стратегии
вывод результатов</code>
Как мы видим, нам осталось реализовать пункты:
«обработка этих данных стратегией»;
«выставление заявок на покупку/продажу»;
возврат результатов из стратегии;
вывод результатов.
Остальные пункты сделаны в этом примере — код достаточно интуитивный, но я всё равно приведу его здесь, ещё раз, отключив лишнее — комментируя — специально не удаляя, чтобы вам легче было сравнить.
Итак основной файл для запуска торговой стратегии называется 02 — Symbols.py, вот его код:
<code class="python hljs">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() # Запуск торговой системы
</code>
Внесенные в него изменения:
поменял таймфрейм на M15;
оставил применение торговой системы/стратегии ко всем тикерам;
и дату старта получения баров установил на datetime(2021, 10, 4).
Теперь основной файл стратегии Strategy.py, вот его код:
<code class="python hljs">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 режим переходим после перехода первого тикера
</code>
Внесенные изменения в него:
в функции def next(self): — закомментил синхронность получения баров — т.к. для моей стратегии это не нужно.
Делаем контрольный запуск, чтобы удостовериться, что все работает и ничего не сломали:

контрольный запуск
Видим, что пришли 15-минутные бары и все ОК.
<code>....
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</code>
Теперь можно приступать к самому интересному — написанию торговой стратегии для робота!
Пишем стратегию для торгового робота
Все будем тестировать на истории — делать backtesting для нашей торговой стратегии.
1) устанавливаем, сколько денег у нас на счету и размер комиссии
<code class="python hljs">cerebro.broker.setcash(3000000) # Устанавливаем сколько денег
cerebro.broker.setcommission(commission=0.01) # Установить комиссию</code>
2) результат работы торговой стратегии возвращаем в главный файл и выводим на экран
<code class="python hljs">print('Стоимость портфеля: %.2f' % cerebro.broker.getvalue())
print('Свободные средства: %.2f' % cerebro.broker.get_cash())</code>
пункты 1) и 2) добавляем в файл 02 — Symbols.py
3) если вы захотите включить live режим работы вашей торговой стратегии, то это делается следующими четырьмя строчками ‑!!! но не рекомендую этого делать, т.к. все заявки на покупку и продажу сразу начнут попадать на биржу и будут пытаться выполняться так, как у вас написано в коде!! - если вы пробегаетесь по истории - то и скрипт будет пытаться выставить в рынок по "старой" цене... а текущая цена далеко уже не та... БУДЬТЕ ЗДЕСЬ ВНИМАТЕЛЬНЫ! Для live режима — не пробегайтесь по истории.
<code class="python hljs">exchange = 'MOEX' # Биржа
portfolio = Config.PortfolioStocks # Портфель фондового рынка
broker = store.getbroker(use_positions=False, portfolio=portfolio, exchange=exchange) # Брокер Alor
cerebro.setbroker(broker) # Устанавливаем брокера</code>
Этот код не добавляем! Просто для инфо, как включить live режим.
Класс торговой системы имеет несколько основных методов:
init - итак понятно — здесь инициализируем вспомогательные переменные и индикаторы для потоков данных;
start - здесь однократно вспомогательным переменным присваиваем значения;
next - вызывается каждый раз при приходе нового бара по тикеру;
notify_order - вызывается, когда происходит покупка или продажа;
notify_trade - вызывается когда меняется статус позиции;
notify_data - вызывается когда меняется статус прихода бара на live режим.
Вы можете по желанию расширять/добавлять новые методы/функционал.
4) В init добавляем:
<code class="python hljs">self.order = None
self.orders_bar_executed = {}</code>
5) В start добавляем:
<code class="python hljs">for data in self.datas: # Пробегаемся по всем запрошенным тикерам
ticker = data._dataname # имя тикера
self.orders_bar_executed[ticker] = 0</code>
6) В next добавляем:
<code class="python hljs">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...")</code>
7) добавляем функцию notify_trade:
<code class="python hljs">def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))</code>
8) добавляем функцию notify_order:
<code class="python hljs">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</code>
пункты 4), 5), 6), 7) и 8) добавляем в файл стратегии Strategy.py
В принципе описание кода достаточно интуитивно показывает смысл стратегии, что мы всегда покупаем и продаем через 5 баров.
Стратегия: мы всегда покупаем и продаем через 5 баров
Итак, код файла 02 — Symbols.py:
<code class="python hljs">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())
</code>
Итак, код файла Strategy.py
<code class="python hljs">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 режим переходим после перехода первого тикера
</code>
Теперь давайте запустим эту стратегию и посмотрим результат!

результат запуска стратегии
Стоимость портфеля: 1 294 456.74
Свободные средства: 45 568 288.40
ЧТО???

Круто? Да? )))
Сразу скажу, что это не грааль — и в коде есть небольшая ошибка, которая приводит к таким ошеломляющим результатам)))
Кому интересно, заснял видео с разоблачением такой сверх доходности, доступно по ссылке.
Иногда лучше один раз увидеть, чем сто раз прочитать
Поэтому создание этой стратегии есть по шагам в видео, доступно по ссылке
Итог
Напоминаю, что цель данной статьи была показать — насколько легко теперь вы можете создавать своих собственных торговых роботов. Ни каких финансовых консультаций или рекомендаций не даю — просто пишем код для торгового бота. И без многих проверок — не запускайте торгового робота в live режиме...
Итак, просто пишите код торгового робота, тестируете его на истории, включаете Live режим, и запускаете в работу)
Как мне видится, получилось довольно интересно:‑) И жду ваших коммитов / фиксов / идей!
P. S. Это код выложил на GitHub по этой ссылке. Не забудьте свой конфиг файл положить в my_config\Config.py
Всем хорошего дня! Спасибо за уделенное время! Если считаете полезным такие статьи то жду вашей позитивной оценки))
Не является индивидуальной инвестиционной рекомендацией | При копировании ссылка обязательна | Нашли ошибку - выделить и нажать Ctrl+Enter | Жалоба