Торговый робот Binance

Этот приведенный ниже код торгового робота для Bitcoin и Ethereum был протестирован на истории, и 2000 USDT были превращены в 5716 USDT = > 185% прибыли))

Несколько слов обо мне

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

В настоящее время я увлекаюсь написанием торговых роботов. Постепенно я изучаю нейронные сети для их применения к анализу цен/объемов акций/фьючерсов.

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

— Что, если готовый код можно использовать и на других активах??? Например, о криптоактивах для Биткоина или Эфира или других?

Уже изучив множество библиотек и примеров за долгое время написания своих торговых роботов, я решил сделать небольшую backtrader_binance библиотеку для интеграции Binance API и библиотеки тестирования торговых стратегий Backtrader.

Вот, с помощью backtrader_binance, теперь мы создадим алгоритмического робота для торговли BTC и ETH.

Подготовка среды

  1. Установка последней версии Python 3.11
  2. Установка среды разработки PyCharm Community 2023.1
  3. Запуск сообщества PyCharm
  4. Создаем в нем новый проект, назовем его algo_trade_robot и укажем, что создаем виртуальную среду Virtualenv, с Python 3.11 => нажимаем «Создать».
Создание нового проекта для алготрейдинга

5. После того, как проект был создан и в нем создана виртуальная среда, мы готовы установить необходимые библиотеки))) Нажимаем на «Терминал» слева внизу, чтобы открыть терминал, в котором мы будем вводить команды для установки библиотек.

Проект открытого терминала

6. Установка необходимых библиотек

Чтобы установить библиотеку, которая интегрирует Binance API с Backtrader, введите команду

pip install backtrader_binance

Ввод backtrader_binance в терминале

Теперь вам нужно установить библиотеку тестирования торговых стратегий Backtrader

pip install git+https://github.com/WISEPLAT/backtrader.git

P.S. Пожалуйста, используйте Backtrader из моего репозитория (так как вы можете размещать там свои коммиты).

И, наконец, у нас есть некоторые зависимости, которые вам также необходимо установить

pip install python-binance pandas matplotlib

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

git clone https://github.com/WISEPLAT/backtrader_binance

А сейчас наш проект выглядит так

Проект торгового робота для Binance

Создание конфигурации для торговой стратегии

Чтобы было проще понять, как все работает, я сделал для вас множество примеров в папках DataExamplesBinance и StrategyExamplesBinance.

Перед запуском примера необходимо получить ключ API и секретный ключ и записать их в файл ConfigBinance\Config.py:# content of ConfigBinance\Config.py
class Config:
BINANCE_API_KEY = «YOUR_API_KEY»
BINANCE_API_SECRET = «YOUR_SECRET_KEY»

Как получить токен Binance API

  1. Зарегистрируйте свой аккаунт на Binance
  2. Перейдите в раздел «Управление API»
  3. Затем нажмите кнопку «Создать API» и выберите «Сгенерировано системой».
  4. В разделе «Лимиты API» включите «Включить спотовую и маржинальную торговлю».
  5. Скопируйте и вставьте полученные «Ключ API» и «Секретный ключ» в файл ConfigBinance\Config.py

Теперь вы можете запускать примеры из папок DataExamplesBinance и StrategyExamplesBinance.

Создание торгового робота для Binance

Для создания торгового робота обычно следуют определенной структуре кода, можно сказать шаблон, по которому код работает с торговой стратегией и с данными с рынка по тикеру/тикерам, и после отработки этого кода выводится какой-то результат.import of the required library

indicator class

Strategy class/Trading System

# — main section —
API connection to the exchange
setting strategy launch parameters strategy launch
getting ticker/ticker data via API
processing of this data by strategy
placing orders for purchase/sale
return of results from the strategy
output of results

В примерах вы найдете несколько вариантов запуска стратегий, но вот примерно стандартная структура кода для торгового робота, файл «07 — Offline Backtest Indicators.py» :import datetime as dt
import backtrader as bt
from backtrader_binance import BinanceStore
from ConfigBinance.Config import Config # Configuration file

# video on creating this strategy — on Russian )
# RuTube: https://rutube.ru/video/417e306e6b5d6351d74bd9cd4d6af051/
# YouTube: https://youtube.com/live/k82vabGva7s

class UnderOver(bt.Indicator):
lines = (‘underover’,)
params = dict(data2=20)
plotinfo = dict(plot=True)

def __init__(self):
self.l.underover = self.data < self.p.data2 # data under data2 == 1

# Trading System
class RSIStrategy(bt.Strategy):
«»»
Live strategy demonstration with SMA, RSI indicators
«»»
params = ( # Parameters of the trading system
(‘coin_target’, »),
(‘timeframe’, »),
)

def __init__(self):
«»»Initialization, adding indicators for each ticker»»»
self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order
for d in self.datas: # Running through all the tickers
self.orders[d._name] = None # There is no order for ticker yet

# creating indicators for each ticker
self.sma1 = {}
self.sma2 = {}
self.sma3 = {}
self.crossover = {}
self.underover_sma = {}
self.rsi = {}
self.underover_rsi = {}
for i in range(len(self.datas)):
ticker = list(self.dnames.keys())[i] # key name is ticker name
self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=9) # SMA1 indicator
self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=30) # SMA2 indicator
self.sma3[ticker] = bt.indicators.SMA(self.datas[i], period=60) # SMA3 indicator

# signal 1 — intersection of a fast SMA from bottom to top of a slow SMA
self.crossover[ticker] = bt.ind.CrossOver(self.sma1[ticker], self.sma2[ticker]) # crossover SMA1 and SMA2

# signal 2 — when SMA3 is below SMA2
self.underover_sma[ticker] = UnderOver(self.sma3[ticker].lines.sma, data2=self.sma2[ticker].lines.sma)

self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=20) # RSI indicator

# signal 3 — when the RSI is below 30
self.underover_rsi[ticker] = UnderOver(self.rsi[ticker].lines.rsi, data2=30)

def next(self):
«»»Arrival of a new ticker candle»»»
for data in self.datas: # Running through all the requested bars of all tickers
ticker = data._name
status = data._state # 0 — Live data, 1 — History data, 2 — None
_interval = self.p.timeframe

if status in [0, 1]:
if status: _state = «False — History data»
else: _state = «True — Live data»

print(‘{} / {} [{}] — Open: {}, High: {}, Low: {}, Close: {}, Volume: {} — Live: {}’.format(
bt.num2date(data.datetime[0]),
data._name,
_interval, # ticker timeframe
data.open[0],
data.high[0],
data.low[0],
data.close[0],
data.volume[0],
_state,
))
print(f’\t — RSI =’, self.rsi[ticker][0])
print(f»\t — crossover =», self.crossover[ticker].lines.crossover[0])

coin_target = self.p.coin_target
print(f»\t — Free balance: {self.broker.getcash()} {coin_target}»)

# signals to open position
signal1 = self.crossover[ticker].lines.crossover[0] # signal 1 — intersection of a fast SMA from bottom to top of a slow SMA
signal2 = self.underover_sma[ticker] # signal 2 — when SMA3 is below SMA2

# signals to close position
signal3 = self.underover_rsi[ticker] # signal 3 — when the RSI is below 30

if not self.getposition(data): # If there is no position
if signal1 == 1:
if signal2 == 1:
# buy
free_money = self.broker.getcash()
price = data.close[0] # by closing price
size = (free_money / price) * 0.25 # 25% of available funds
print(«-«*50)
print(f»\t — buy {ticker} size = {size} at price = {price}»)
self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size)
print(f»\t — Order has been submitted {self.orders[data._name].p.tradeid} to buy {data._name}»)
print(«-» * 50)

else: # If there is a position
if signal3 == 1:
# sell
print(«-» * 50)
print(f»\t — Продаем по рынку {data._name}…»)
self.orders[data._name] = self.close() # Request to close a position at the market price
print(«-» * 50)

def notify_order(self, order):
«»»Changing the status of the order»»»
print(«*»*50)
order_data_name = order.data._name # Name of ticker from order
self.log(f’Order number {order.ref} {order.info[«order_number»]} {order.getstatusname()} {«Buy» if order.isbuy() else «Sell»} {order_data_name} {order.size} @ {order.price}’)
if order.status == bt.Order.Completed: # If the order is fully executed
if order.isbuy(): # The order to buy
self.log(f’Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}’)
else: # The order to sell
self.log(f’Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}’)
self.orders[order_data_name] = None # Reset the order to enter the position
print(«*» * 50)

def notify_trade(self, trade):
«»»Changing the position status»»»
if trade.isclosed: # If the position is closed
self.log(f’Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}’)

def log(self, txt, dt=None):
«»»Print string with date to the console»»»
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar
print(f'{dt.strftime(«%d.%m.%Y %H:%M»)}, {txt}’) # Print the date and time with the specified text to the console

if __name__ == ‘__main__’:
cerebro = bt.Cerebro(quicknotify=True)

cerebro.broker.setcash(2000) # Setting how much money
cerebro.broker.setcommission(commission=0.0015) # Set the commission — 0.15% … divide by 100 to remove %

coin_target = ‘USDT’ # the base ticker in which calculations will be performed
symbol = ‘BTC’ + coin_target # the ticker by which we will receive data in the format <CodeTickerBaseTicker>
symbol2 = ‘ETH’ + coin_target # the ticker by which we will receive data in the format <CodeTickerBaseTicker>

store = BinanceStore(
api_key=Config.BINANCE_API_KEY,
api_secret=Config.BINANCE_API_SECRET,
coin_target=coin_target,
testnet=False) # Binance Storage

# # live connection to Binance — for Offline comment these two lines
# broker = store.getbroker()
# cerebro.setbroker(broker)

# ————————————————————
# Attention! — Now it’s Offline for testing strategies #
# ————————————————————

# # Historical 1-minute bars for 10 hours + new live bars / timeframe M1
# timeframe = «M1»
# from_date = dt.datetime.utcnow() — dt.timedelta(minutes=60*10)
# data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # set True here — if you need to get live bars
# # data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=False) # set True here — if you need to get live bars

# Historical D1 bars for 365 days + new live bars / timeframe D1
timeframe = «D1»
from_date = dt.datetime.utcnow() — dt.timedelta(days=365*3)
data = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # set True here — if you need to get live bars
data2 = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol2, start_date=from_date, LiveBars=False) # set True here — if you need to get live bars

cerebro.adddata(data) # Adding data
cerebro.adddata(data2) # Adding data

cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Adding a trading system

cerebro.run() # Launching a trading system
cerebro.plot() # Draw a chart

print()
print(«$»*77)
print(f»Liquidation value of the portfolio: {cerebro.broker.getvalue()}») # Liquidation value of the portfolio
print(f»Remaining available funds: {cerebro.broker.getcash()}») # Remaining available funds
print(«$» * 77)

Глядя на приведенный выше код, вы можете легко увидеть, что

  1. Импорт необходимых библиотек осуществляется по строкам 1..4

import datetime as dt
import backtrader as bt
from backtrader_binance import BinanceStore
from ConfigBinance.Config import Config # Configuration file

2. Индикатор класса 11..17 строки, обычно размещаемые в отдельном файлеclass UnderOver(bt.Indicator):
lines = (‘underover’,)
params = dict(data2=20)
plotinfo = dict(plot=True)

def __init__(self):
self.l.underover = self.data < self.p.data2 # data under data2 == 1

3. Стратегия/Торговая система класса 21..138, обычно размещается в отдельном файле# Trading System
class RSIStrategy(bt.Strategy):
«»»
Live strategy demonstration with SMA, RSI indicators
«»»
params = ( # Parameters of the trading system
(‘coin_target’, »),
(‘timeframe’, »),
)

def __init__(self):
«»»Initialization, adding indicators for each ticker»»»
self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order
for d in self.datas: # Running through all the tickers
self.orders[d._name] = None # There is no order for ticker yet

# creating indicators for each ticker
self.sma1 = {}
self.sma2 = {}
self.sma3 = {}
self.crossover = {}
self.underover_sma = {}
self.rsi = {}
self.underover_rsi = {}
for i in range(len(self.datas)):
ticker = list(self.dnames.keys())[i] # key name is ticker name
self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=9) # SMA1 indicator
self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=30) # SMA2 indicator
self.sma3[ticker] = bt.indicators.SMA(self.datas[i], period=60) # SMA3 indicator

# signal 1 — intersection of a fast SMA from bottom to top of a slow SMA
self.crossover[ticker] = bt.ind.CrossOver(self.sma1[ticker], self.sma2[ticker]) # crossover SMA1 and SMA2

# signal 2 — when SMA3 is below SMA2
self.underover_sma[ticker] = UnderOver(self.sma3[ticker].lines.sma, data2=self.sma2[ticker].lines.sma)

self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=20) # RSI indicator

# signal 3 — when the RSI is below 30
self.underover_rsi[ticker] = UnderOver(self.rsi[ticker].lines.rsi, data2=30)

def next(self):
«»»Arrival of a new ticker candle»»»
for data in self.datas: # Running through all the requested bars of all tickers
ticker = data._name
status = data._state # 0 — Live data, 1 — History data, 2 — None
_interval = self.p.timeframe

if status in [0, 1]:
if status: _state = «False — History data»
else: _state = «True — Live data»

print(‘{} / {} [{}] — Open: {}, High: {}, Low: {}, Close: {}, Volume: {} — Live: {}’.format(
bt.num2date(data.datetime[0]),
data._name,
_interval, # ticker timeframe
data.open[0],
data.high[0],
data.low[0],
data.close[0],
data.volume[0],
_state,
))
print(f’\t — RSI =’, self.rsi[ticker][0])
print(f»\t — crossover =», self.crossover[ticker].lines.crossover[0])

coin_target = self.p.coin_target
print(f»\t — Free balance: {self.broker.getcash()} {coin_target}»)

# signals to open position
signal1 = self.crossover[ticker].lines.crossover[0] # signal 1 — intersection of a fast SMA from bottom to top of a slow SMA
signal2 = self.underover_sma[ticker] # signal 2 — when SMA3 is below SMA2

# signals to close position
signal3 = self.underover_rsi[ticker] # signal 3 — when the RSI is below 30

if not self.getposition(data): # If there is no position
if signal1 == 1:
if signal2 == 1:
# buy
free_money = self.broker.getcash()
price = data.close[0] # by closing price
size = (free_money / price) * 0.25 # 25% of available funds
print(«-«*50)
print(f»\t — buy {ticker} size = {size} at price = {price}»)
self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size)
print(f»\t — Order has been submitted {self.orders[data._name].p.tradeid} to buy {data._name}»)
print(«-» * 50)

else: # If there is a position
if signal3 == 1:
# sell
print(«-» * 50)
print(f»\t — Продаем по рынку {data._name}…»)
self.orders[data._name] = self.close() # Request to close a position at the market price
print(«-» * 50)

def notify_order(self, order):
«»»Changing the status of the order»»»
print(«*»*50)
order_data_name = order.data._name # Name of ticker from order
self.log(f’Order number {order.ref} {order.info[«order_number»]} {order.getstatusname()} {«Buy» if order.isbuy() else «Sell»} {order_data_name} {order.size} @ {order.price}’)
if order.status == bt.Order.Completed: # If the order is fully executed
if order.isbuy(): # The order to buy
self.log(f’Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}’)
else: # The order to sell
self.log(f’Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}’)
self.orders[order_data_name] = None # Reset the order to enter the position
print(«*» * 50)

def notify_trade(self, trade):
«»»Changing the position status»»»
if trade.isclosed: # If the position is closed
self.log(f’Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}’)

def log(self, txt, dt=None):
«»»Print string with date to the console»»»
dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar
print(f'{dt.strftime(«%d.%m.%Y %H:%M»)}, {txt}’) # Print the date and time with the specified text to the console

4. — — основной участок — — строка 141

5. API подключение к бирже — строки 151..155

6. Настройка параметров запуска стратегии 172..180

7. Запуск стратегии — линия 182

8. получение данных по тикеру/тикерам через строку API 172..175

9. Обработка этих данных стратегией — строки 61..115

10. Выставление ордеров на покупку/продажу — строки 105 — покупка и 114 — продажа

11. Отдача результатов от стратегии — строки 183, 187, 188

12. Вывод результатов — строки 183, 187, 188

Класс торговых систем имеет несколько основных методов:

  1. init — так понятно — здесь мы инициализируем вспомогательные переменные и индикаторы для потоков данных
  2. далее — вызывается каждый раз, когда появляется новый тикер
  3. notify_order — вызывается, когда происходит покупка или продажа
  4. notify_trade — вызывается при изменении статуса позиции

Вы можете расширять/добавлять новые методы/функциональность по своему усмотрению.

Иногда лучше один раз увидеть, чем сто раз прочитать.

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

РуТьюб

Ютуб

Если у вас есть какие-то мысли по поводу создания, пишите давайте посмотрим.

Результат торговой стратегии для BTC и ETH

Параметры стратегии не были оптимизированы, поэтому она может дать лучший результат.

Покупка/продажа на D1
Результат торговой стратегии

Эти 2000 USDT превратились в 5716 USDT => 185% прироста

На мой взгляд, получилось довольно интересно 🙂 И жду ваших коммитов/исправлений/идей!

Источник