├── ConfigBinance └── Config.py ├── DataExamplesBinance ├── 01 - Symbol.py ├── 02 - Symbol data to DF.py ├── 03 - Symbols.py ├── 04 - Resample.py ├── 05 - Replay.py ├── 06 - Rollover.py ├── 07 - Get Asset Balance.py ├── 08 - Timeframes.py ├── 09 - Get Asset Info - no Decimal.py ├── 09 - Get Asset Info - through client.py ├── 09 - Get Asset Info.py ├── 10 - Get Historical Data.py ├── BTCUSDT_1d.csv ├── BTCUSDT_1d_minus_5_days.csv └── Strategy.py ├── DataExamplesBinance_ru ├── 01 - Symbol.py ├── 02 - Symbol data to DF.py ├── 03 - Symbols.py ├── 04 - Resample.py ├── 05 - Replay.py ├── 06 - Rollover.py ├── 07 - Get Asset Balance.py ├── 08 - Timeframes.py ├── 09 - Get Asset Info - no Decimal.py ├── 09 - Get Asset Info - through client.py ├── 09 - Get Asset Info.py ├── 10 - Get Historical Data.py ├── BTCUSDT_1d.csv ├── BTCUSDT_1d_minus_5_days.csv └── Strategy.py ├── LICENSE ├── README.md ├── StrategyExamplesBinance ├── 01 - Live Trade - Just Buy and Sell.py ├── 01 - Live Trade.py ├── 02 - Live Trade MultiPortfolio.py ├── 03 - Live Trade ETH.py ├── 04 - Offline Backtest.py ├── 05 - Offline Backtest MultiPortfolio.py ├── 06 - Live Trade Just Buy and Close by Market.py ├── 07 - Offline Backtest Indicators.py └── 08 - Offline Backtest Margin Trade with Leverage 50x - Linear Trade.py ├── StrategyExamplesBinance_ru ├── 01 - Live Trade - Just Buy and Sell.py ├── 01 - Live Trade.py ├── 02 - Live Trade MultiPortfolio.py ├── 03 - Live Trade ETH.py ├── 04 - Offline Backtest.py ├── 05 - Offline Backtest MultiPortfolio.py ├── 06 - Live Trade Just Buy and Close by Market.py ├── 07 - Offline Backtest Indicators.py └── 08 - Offline Backtest Margin Trade with Leverage 50x - Linear Trade.py ├── backtrader_binance ├── __init__.py ├── binance_broker.py ├── binance_feed.py └── binance_store.py ├── requirements.txt ├── setup.cfg └── setup.py /ConfigBinance/Config.py: -------------------------------------------------------------------------------- 1 | # How to get Binance API Token: 2 | # 1. Register your account at Binance https://www.binance.com/?ref=CPA_004RZBKQWK 3 | # 2. Go to "API Management" https://www.binance.com/en/my/settings/api-management?ref=CPA_004RZBKQWK 4 | # 3. Then push the button "Create API" and select "System generated" 5 | # 4. In "API restrictions" enable "Enable Spot & Margin Trading" 6 | # 5. Copy & Paste here "API Key" and "Secret Key" 7 | # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | # P.S. If you use my referral link - Thanks a lot)) 9 | # If you liked this software => Put a star on github - https://github.com/WISEPLAT/backtrader_binance 10 | # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | 12 | 13 | class Config: 14 | BINANCE_API_KEY = "YOUR_API_KEY" 15 | BINANCE_API_SECRET = "YOUR_SECRET_KEY" 16 | -------------------------------------------------------------------------------- /DataExamplesBinance/01 - Symbol.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | # Historical/new bars of ticker 8 | if __name__ == '__main__': # Entry point when running this script 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False, 19 | # tld="us", # for US customers => to use the 'Binance.us' url 20 | ) # Binance Storage 21 | broker = store.getbroker() 22 | cerebro.setbroker(broker) 23 | 24 | # # 1. Historical 5-minute bars for the last 10 hours + Chart because offline/ timeframe M5 25 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # we take data for the last 10 hours 26 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) 27 | 28 | # 2. Historical 1-minute bars for the last hour + new live bars / timeframe M1 29 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # we take data for the last 1 hour 30 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 31 | 32 | # # 3. Historical 1-hour bars for the week + Chart because offline / timeframe H1 33 | # from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # we take data for the last week from the current time 34 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) 35 | 36 | cerebro.adddata(data) # Adding data 37 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 38 | 39 | cerebro.run() # Launching a trading system 40 | cerebro.plot() # Draw a chart 41 | -------------------------------------------------------------------------------- /DataExamplesBinance/02 - Symbol data to DF.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | import pandas as pd 4 | from backtrader_binance import BinanceStore 5 | from ConfigBinance.Config import Config # Configuration file 6 | 7 | 8 | # Trading System 9 | class StrategySaveOHLCVToDF(bt.Strategy): 10 | """Сохраняет OHLCV в DF""" 11 | params = ( # Parameters of the trading system 12 | ('coin_target', ''), # 13 | ) 14 | 15 | def __init__(self): 16 | self.df = {} 17 | self.df_tf = {} 18 | 19 | def start(self): 20 | for data in self.datas: # Running through all the requested tickers 21 | ticker = data._name 22 | self.df[ticker] = [] 23 | self.df_tf[ticker] = self.broker._store.get_interval(data._timeframe, data._compression) 24 | 25 | def next(self): 26 | """Arrival of a new ticker candle""" 27 | for data in self.datas: # Running through all the requested tickers 28 | ticker = data._name 29 | try: 30 | status = data._state # 0 - Live data, 1 - History data, 2 - None 31 | _interval = data.interval 32 | except Exception as e: 33 | if data.resampling == 1: 34 | status = 22 35 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 36 | _interval = f"_{_interval}" 37 | else: 38 | print("Error:", e) 39 | 40 | if status == 1: 41 | _state = "Resampled Data" 42 | if status == 1: _state = "False - History data" 43 | if status == 0: _state = "True - Live data" 44 | 45 | self.df[ticker].append([bt.num2date(data.datetime[0]), data.open[0], data.high[0], data.low[0], data.close[0], data.volume[0]]) 46 | 47 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 48 | bt.num2date(data.datetime[0]), 49 | data._name, 50 | _interval, # ticker timeframe 51 | data.open[0], 52 | data.high[0], 53 | data.low[0], 54 | data.close[0], 55 | data.volume[0], 56 | _state, 57 | )) 58 | 59 | 60 | # Historical/new bars of ticker 61 | if __name__ == '__main__': # Entry point when running this script 62 | cerebro = bt.Cerebro(quicknotify=True) 63 | 64 | coin_target = 'USDT' # the base ticker in which calculations will be performed 65 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 66 | 67 | store = BinanceStore( 68 | api_key=Config.BINANCE_API_KEY, 69 | api_secret=Config.BINANCE_API_SECRET, 70 | coin_target=coin_target, 71 | testnet=False, 72 | # tld="us", # for US customers => to use the 'Binance.us' url 73 | ) # Binance Storage 74 | broker = store.getbroker() 75 | cerebro.setbroker(broker) 76 | 77 | # 1. Historical D1 bars for 365 days + Chart because offline/ timeframe D1 78 | from_date = dt.datetime.utcnow() - dt.timedelta(days=365) # we take data for 365 days from the current time 79 | data = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) 80 | 81 | cerebro.adddata(data) # Adding data 82 | cerebro.addstrategy(StrategySaveOHLCVToDF, coin_target=coin_target) # Adding a trading system 83 | 84 | results = cerebro.run() # Launching a trading system 85 | 86 | print(results[0].df) 87 | 88 | df = pd.DataFrame(results[0].df[symbol], columns=["datetime", "open", "high", "low", "close", "volume"]) 89 | print(df) 90 | 91 | tf = results[0].df_tf[symbol] 92 | 93 | # save to file 94 | df.to_csv(f"{symbol}_{tf}.csv", index=False) 95 | 96 | # save to file 97 | df[:-5].to_csv(f"{symbol}_{tf}_minus_5_days.csv", index=False) 98 | 99 | cerebro.plot() # Draw a chart 100 | -------------------------------------------------------------------------------- /DataExamplesBinance/03 - Symbols.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | # Multiple tickers for multiple trading systems on the same time interval history + live 8 | if __name__ == '__main__': # Entry point when running this script 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbols = ('BTC', 'ETH') # tickers for which we will receive data 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False, 19 | # tld="us", # for US customers => to use the 'Binance.us' url 20 | ) # Binance Storage 21 | broker = store.getbroker() 22 | cerebro.setbroker(broker) 23 | 24 | for _symbol in symbols: # Running through all the tickers 25 | 26 | symbol = _symbol + coin_target # the ticker by which we will receive data in the format 27 | 28 | # # 1. Historical 5-minute bars for the last 10 hours + Chart because offline/ timeframe M5 29 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # we take data for the last 10 hours 30 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) 31 | 32 | # # 2. Historical 1-minute bars for the last hour + new live bars / timeframe M1 33 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # we take data for the last 1 hour 34 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 35 | 36 | # 3. Historical 1-hour bars for the week + Chart because offline / timeframe H1 37 | from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # we take data for the last week from the current time 38 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) 39 | 40 | cerebro.adddata(data) # Adding data 41 | 42 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 43 | 44 | cerebro.run() # Launching a trading system 45 | cerebro.plot() # Draw a chart 46 | -------------------------------------------------------------------------------- /DataExamplesBinance/04 - Resample.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | # Multiple time intervals for one ticker: Getting a larger time interval from a smaller one (Resample) 8 | if __name__ == '__main__': # Entry point when running this script 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False, 19 | # tld="us", # for US customers => to use the 'Binance.us' url 20 | ) # Binance Storage 21 | broker = store.getbroker() 22 | cerebro.setbroker(broker) 23 | 24 | # # Historical 1-minute bars + 5-minute bars are obtained by Resample + M5 to control the last 1 hour + Chart because offline / timeframe M5 + resample M1 + M5 + M5 25 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # we take data for the last 10 hours 26 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for the smallest time interval 27 | # cerebro.adddata(data) # Adding data 28 | # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5, name="resampled") # You can add a larger time interval multiple of a smaller one (added automatically) 29 | # # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5, name="resampled").plotinfo.plot = False # without output on the chart 30 | # # Adding data из Binance для проверки правильности работы Resample 31 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a large time interval 32 | 33 | # ------------------------------------------------------------------------------------------- 34 | # Attention! - Data does not arrive synchronously on different TF, so there is a time shift # 35 | # - You can fix it, test it and push the changes! # 36 | # ------------------------------------------------------------------------------------------- 37 | 38 | # Historical M15, H1 are obtained by Resample + H1 for control + Chart because offline/ timeframe M15 + resample H1 + H1 39 | from_date = dt.datetime.utcnow().date().today() 40 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=15, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for the smallest time interval 41 | cerebro.adddata(data) # Adding data 42 | cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="resampled",) # You can add a larger time interval multiple of a smaller one (added automatically) 43 | # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="resampled", boundoff=1).plotinfo.plot = False # without output on the chart 44 | # Adding data from Binance to verify the correct operation of Resample 45 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a large time interval 46 | 47 | cerebro.adddata(data) # Adding data 48 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 49 | 50 | cerebro.run() # Launching a trading system 51 | cerebro.plot() # Draw a chart 52 | -------------------------------------------------------------------------------- /DataExamplesBinance/05 - Replay.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | # Using a smaller time interval (Replay) 8 | if __name__ == '__main__': # Entry point when running this script 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False, 19 | # tld="us", # for US customers => to use the 'Binance.us' url 20 | ) # Binance Storage 21 | broker = store.getbroker() 22 | cerebro.setbroker(broker) 23 | 24 | # For the strategy, we use historical M1 bars, display the result on H1 + Chart because offline/ timeframe M5 + resample M1 + M5 + M5 25 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # we take data for the last 10 hours 26 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for the smallest time interval 27 | cerebro.adddata(data) # Adding data 28 | cerebro.replaydata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="replayed") # We see a large interval on the graph, we run the vehicle on a smaller one 29 | 30 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 31 | 32 | cerebro.run() # Launching a trading system 33 | cerebro.plot() # Draw a chart 34 | -------------------------------------------------------------------------------- /DataExamplesBinance/06 - Rollover.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | 8 | def get_timeframe(tf, TimeFrame): 9 | """Converting TF to parameters for adding strategy data""" 10 | interval = 1 # by default, the timeframe is 1m 11 | _timeframe = TimeFrame.Minutes # by default, the timeframe is 1m 12 | 13 | if tf == '1m': interval = 1 14 | if tf == '5m': interval = 5 15 | if tf == '15m': interval = 15 16 | if tf == '30m': interval = 30 17 | if tf == '1h': interval = 60 18 | if tf == '1d': _timeframe = TimeFrame.Days 19 | if tf == '1w': _timeframe = TimeFrame.Weeks 20 | if tf == '1M': _timeframe = TimeFrame.Months 21 | return _timeframe, interval 22 | 23 | 24 | # Gluing the ticker history from a file and Binance (Rollover) 25 | if __name__ == '__main__': # Entry point when running this script 26 | cerebro = bt.Cerebro(quicknotify=True) 27 | 28 | coin_target = 'USDT' # the base ticker in which calculations will be performed 29 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 30 | 31 | store = BinanceStore( 32 | api_key=Config.BINANCE_API_KEY, 33 | api_secret=Config.BINANCE_API_SECRET, 34 | coin_target=coin_target, 35 | testnet=False, 36 | # tld="us", # for US customers => to use the 'Binance.us' url 37 | ) # Binance Storage 38 | broker = store.getbroker() 39 | cerebro.setbroker(broker) 40 | 41 | tf = "1d" # '1m' '5m' '15m' '30m' '1h' '1d' '1w' '1M' 42 | _t, _c = get_timeframe(tf, bt.TimeFrame) 43 | 44 | d1 = bt.feeds.GenericCSVData( # We get the history from the file - which does not contain the last 5 days 45 | timeframe=_t, compression=_c, # to be in the same TF as d2 46 | dataname=f'{symbol}_{tf}_minus_5_days.csv', # File to import from Binance. Created from example 02 - Symbol data to DF.py 47 | separator=',', # Columns are separated by commas 48 | dtformat='%Y-%m-%d', # dtformat='%Y-%m-%d %H:%M:%S', # Date/time format YYYY-MM-DD HH:MM:SS 49 | openinterest=-1, # There is no open interest in the file 50 | sessionend=dt.time(0, 0), # For daily data and above, the end time of the session is substituted. To coincide with the story, you need to set the closing time to 00:00 51 | ) 52 | 53 | from_date = dt.datetime.utcnow() - dt.timedelta(days=15) # we take data for the last 15 days 54 | d2 = store.getdata(timeframe=_t, compression=_c, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for the smallest time interval 55 | 56 | cerebro.rolloverdata(d1, d2, name=symbol) # Glued ticker 57 | 58 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 59 | 60 | cerebro.run() # Launching a trading system 61 | cerebro.plot() # Draw a chart 62 | -------------------------------------------------------------------------------- /DataExamplesBinance/07 - Get Asset Balance.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | 4 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 5 | 6 | asset = 'BTC' 7 | 8 | balance = client.get_asset_balance(asset=asset) 9 | 10 | print(f" - Balance for {asset} is {balance['free']}") -------------------------------------------------------------------------------- /DataExamplesBinance/08 - Timeframes.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Trading System 6 | 7 | # Multiple time intervals for one ticker: Getting from history + live 8 | if __name__ == '__main__': # Entry point when running this script 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False, 19 | # tld="us", # for US customers => to use the 'Binance.us' url 20 | ) # Binance Storage 21 | broker = store.getbroker() 22 | cerebro.setbroker(broker) 23 | 24 | # 1. Historical 5-minute bars + 15-minute bars for the last 10 hours + Chart because offline/ timeframe M5 + M15 25 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # we take data for the last 10 hours 26 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a small time interval (should go first) 27 | cerebro.adddata(data) # Adding data 28 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=15, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a large time interval 29 | 30 | # # 2. Historical 1-minute + 5-minute bars for the last hour + new live bars / timeframe M1 + M5 31 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # we take data for the last 1 hour 32 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) # Historical data for a small time interval (should go first) 33 | # cerebro.adddata(data) # Adding data 34 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=True) # Historical data for a large time interval 35 | 36 | # # 3. Historical 1-hour bars + 4-hour bars for the week + Chart because offline/ timeframe H1 + H4 37 | # from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # we take data for the last week from the current time 38 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a small time interval (should go first) 39 | # cerebro.adddata(data) # Adding data 40 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=240, dataname=symbol, start_date=from_date, LiveBars=False) # Historical data for a large time interval 41 | 42 | cerebro.adddata(data) # Adding data 43 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Adding a trading system 44 | 45 | cerebro.run() # Launching a trading system 46 | cerebro.plot() # Draw a chart 47 | -------------------------------------------------------------------------------- /DataExamplesBinance/09 - Get Asset Info - no Decimal.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | 4 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 5 | 6 | asset = 'BTC' 7 | 8 | balance = client.get_asset_balance(asset=asset) 9 | 10 | print(f" - Balance for {asset} is {balance['free']}") 11 | 12 | info = client.get_symbol_info('ETHUSDT') 13 | print(info) 14 | 15 | info = client.get_symbol_info('BTCUSDT') 16 | print(info) 17 | 18 | info = client.get_symbol_info('BNBUSDT') 19 | print(info) 20 | print(info['filters']) 21 | 22 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 23 | info = {} 24 | for ticker in tickers: 25 | info[ticker] = {} 26 | for _filter in client.get_symbol_info(ticker)['filters']: 27 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = float(_filter['minPrice']) 28 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = float(_filter['minQty']) 29 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = float(_filter['stepSize']) 30 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 31 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 32 | print(info) 33 | 34 | -------------------------------------------------------------------------------- /DataExamplesBinance/09 - Get Asset Info - through client.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | from backtrader_binance import BinanceStore 3 | from ConfigBinance.Config import Config 4 | from decimal import Decimal 5 | 6 | cerebro = bt.Cerebro(quicknotify=True) 7 | 8 | cerebro.broker.setcash(100000) # Setting how much money 9 | cerebro.broker.setcommission(commission=0.0015) # Set the commission - 0.15% ... divide by 100 to remove % 10 | 11 | coin_target = 'USDT' # the base ticker in which calculations will be performed 12 | symbols = ('BTC', 'ETH', 'BNB') # tickers for which we will receive data 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Binance Storage 19 | 20 | client = store.binance # !!! 21 | 22 | asset = 'BTC' 23 | 24 | balance = client.get_asset_balance(asset=asset) 25 | 26 | print(f" - Balance for {asset} is {balance['free']}") 27 | 28 | info = client.get_symbol_info('ETHUSDT') 29 | print(info) 30 | 31 | info = client.get_symbol_info('BTCUSDT') 32 | print(info) 33 | 34 | info = client.get_symbol_info('BNBUSDT') 35 | print(info) 36 | print(info['filters']) 37 | 38 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 39 | info = {} 40 | for ticker in tickers: 41 | info[ticker] = {} 42 | for _filter in client.get_symbol_info(ticker)['filters']: 43 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = Decimal(_filter['minPrice']) 44 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = Decimal(_filter['minQty']) 45 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = Decimal(_filter['stepSize']) 46 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 47 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 48 | print(info) 49 | 50 | -------------------------------------------------------------------------------- /DataExamplesBinance/09 - Get Asset Info.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | from decimal import Decimal 4 | 5 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 6 | 7 | asset = 'BTC' 8 | 9 | balance = client.get_asset_balance(asset=asset) 10 | 11 | print(f" - Balance for {asset} is {balance['free']}") 12 | 13 | info = client.get_symbol_info('ETHUSDT') 14 | print(info) 15 | 16 | info = client.get_symbol_info('BTCUSDT') 17 | print(info) 18 | 19 | info = client.get_symbol_info('BNBUSDT') 20 | print(info) 21 | print(info['filters']) 22 | 23 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 24 | info = {} 25 | for ticker in tickers: 26 | info[ticker] = {} 27 | for _filter in client.get_symbol_info(ticker)['filters']: 28 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = Decimal(_filter['minPrice']) 29 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = Decimal(_filter['minQty']) 30 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = Decimal(_filter['stepSize']) 31 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 32 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 33 | print(info) 34 | 35 | -------------------------------------------------------------------------------- /DataExamplesBinance/10 - Get Historical Data.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | import pandas as pd 4 | 5 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 6 | 7 | klines = client.get_historical_klines("BTCUSDT", Client.KLINE_INTERVAL_1DAY, "2023-05-10") 8 | 9 | df = pd.DataFrame(klines) 10 | df.drop(df.columns[[6, 7, 8, 9, 10, 11]], axis=1, inplace=True) # Remove unnecessary columns 11 | df.columns = ['datetime', 'open', 'high', 'low', 'close', 'volume'] 12 | df['datetime'] = df['datetime'].values.astype(dtype='datetime64[ms]') 13 | df['open'] = df['open'].values.astype(float) 14 | df['high'] = df['high'].values.astype(float) 15 | df['low'] = df['low'].values.astype(float) 16 | df['close'] = df['close'].values.astype(float) 17 | df['volume'] = df['volume'].values.astype(float) 18 | 19 | # df = df[:-1] # to skip last candle 20 | 21 | print(df) 22 | -------------------------------------------------------------------------------- /DataExamplesBinance/Strategy.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | 4 | class StrategyJustPrintsOHLCVAndState(bt.Strategy): 5 | """ 6 | - Displays the connection status 7 | - When a new bar arrives, it displays its prices/volume 8 | - Displays the status - historical bars or live 9 | """ 10 | params = ( # Parameters of the trading system 11 | ('coin_target', ''), # 12 | ) 13 | 14 | def next(self): 15 | """Arrival of a new ticker candle""" 16 | for data in self.datas: # Running through all the requested tickers 17 | ticker = data._name 18 | status = 1 19 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 20 | try: 21 | status = data._state # 0 - Live data, 1 - History data, 2 - None 22 | _interval = data.interval 23 | except Exception as e: 24 | if data.resampling == 1: 25 | status = 22 26 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 27 | _interval = f"_{_interval}" 28 | else: 29 | # print("Error:", e) 30 | pass 31 | 32 | if status in [0, 1, 22]: 33 | _state = "Resampled Data" 34 | if status == 1: _state = "False - History data" 35 | if status == 0: _state = "True - Live data" 36 | 37 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 38 | bt.num2date(data.datetime[0]), 39 | data._name, 40 | _interval, # ticker timeframe 41 | data.open[0], 42 | data.high[0], 43 | data.low[0], 44 | data.close[0], 45 | data.volume[0], 46 | _state, 47 | )) 48 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/01 - Symbol.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | # Исторические/новые бары тикера 8 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 12 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Хранилище Binance 19 | broker = store.getbroker() 20 | cerebro.setbroker(broker) 21 | 22 | # # 1. Исторические 5-минутные бары за последние 10 часов + График т.к. оффлайн/ таймфрейм M5 23 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # берем данные за последние 5 часов 24 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) 25 | 26 | # 2. Исторические 1-минутные бары за прошлый час + новые live бары / таймфрейм M1 27 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # берем данные за последний 1 час 28 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 29 | 30 | # # 3. Исторические 1-часовые бары за неделю + График т.к. оффлайн/ таймфрейм H1 31 | # from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # берем данные за последнюю неделю от текущего времени 32 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) 33 | 34 | cerebro.adddata(data) # Добавляем данные 35 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 36 | 37 | cerebro.run() # Запуск торговой системы 38 | cerebro.plot() # Рисуем график 39 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/02 - Symbol data to DF.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | import pandas as pd 4 | from backtrader_binance import BinanceStore 5 | from ConfigBinance.Config import Config # Файл конфигурации 6 | 7 | 8 | # Торговая система 9 | class StrategySaveOHLCVToDF(bt.Strategy): 10 | """Сохраняет OHLCV в DF""" 11 | params = ( # Параметры торговой системы 12 | ('coin_target', ''), # 13 | ) 14 | 15 | def __init__(self): 16 | self.df = {} 17 | self.df_tf = {} 18 | 19 | def start(self): 20 | for data in self.datas: # Пробегаемся по всем запрошенным тикерам 21 | ticker = data._name 22 | self.df[ticker] = [] 23 | self.df_tf[ticker] = self.broker._store.get_interval(data._timeframe, data._compression) 24 | 25 | def next(self): 26 | """Приход нового бара тикера""" 27 | for data in self.datas: # Пробегаемся по всем запрошенным тикерам 28 | ticker = data._name 29 | try: 30 | status = data._state # 0 - Live data, 1 - History data, 2 - None 31 | _interval = data.interval 32 | except Exception as e: 33 | if data.resampling == 1: 34 | status = 22 35 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 36 | _interval = f"_{_interval}" 37 | else: 38 | print("Error:", e) 39 | 40 | if status == 1: 41 | _state = "Resampled Data" 42 | if status == 1: _state = "False - History data" 43 | if status == 0: _state = "True - Live data" 44 | 45 | self.df[ticker].append([bt.num2date(data.datetime[0]), data.open[0], data.high[0], data.low[0], data.close[0], data.volume[0]]) 46 | 47 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 48 | bt.num2date(data.datetime[0]), 49 | data._name, 50 | _interval, # таймфрейм тикера 51 | data.open[0], 52 | data.high[0], 53 | data.low[0], 54 | data.close[0], 55 | data.volume[0], 56 | _state, 57 | )) 58 | 59 | 60 | # Исторические/новые бары тикера 61 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 62 | cerebro = bt.Cerebro(quicknotify=True) 63 | 64 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 65 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 66 | 67 | store = BinanceStore( 68 | api_key=Config.BINANCE_API_KEY, 69 | api_secret=Config.BINANCE_API_SECRET, 70 | coin_target=coin_target, 71 | testnet=False) # Хранилище Binance 72 | broker = store.getbroker() 73 | cerebro.setbroker(broker) 74 | 75 | # 1. Исторические D1 бары за 365 дней + График т.к. оффлайн/ таймфрейм D1 76 | from_date = dt.datetime.utcnow() - dt.timedelta(days=365) # берем данные за 365 дней от текущего времени 77 | data = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) 78 | 79 | cerebro.adddata(data) # Добавляем данные 80 | cerebro.addstrategy(StrategySaveOHLCVToDF, coin_target=coin_target) # Добавляем торговую систему 81 | 82 | results = cerebro.run() # Запуск торговой системы 83 | 84 | print(results[0].df) 85 | 86 | df = pd.DataFrame(results[0].df[symbol], columns=["datetime", "open", "high", "low", "close", "volume"]) 87 | print(df) 88 | 89 | tf = results[0].df_tf[symbol] 90 | 91 | # save to file 92 | df.to_csv(f"{symbol}_{tf}.csv", index=False) 93 | 94 | # save to file 95 | df[:-5].to_csv(f"{symbol}_{tf}_minus_5_days.csv", index=False) 96 | 97 | cerebro.plot() # Рисуем график 98 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/03 - Symbols.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | # Несколько тикеров для нескольких торговых систем по одному временнОму интервалу history + live 8 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 12 | symbols = ('BTC', 'ETH') # тикеры, по которым будем получать данные 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Хранилище Binance 19 | broker = store.getbroker() 20 | cerebro.setbroker(broker) 21 | 22 | for _symbol in symbols: # Пробегаемся по всем тикерам 23 | 24 | symbol = _symbol + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 25 | 26 | # # 1. Исторические 5-минутные бары за последние 10 часов + График т.к. оффлайн/ таймфрейм M5 27 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # берем данные за последние 5 часов 28 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) 29 | 30 | # # 2. Исторические 1-минутные бары за прошлый час + новые live бары / таймфрейм M1 31 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # берем данные за последний 1 час 32 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 33 | 34 | # 3. Исторические 1-часовые бары за неделю + График т.к. оффлайн/ таймфрейм H1 35 | from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # берем данные за последнюю неделю от текущего времени 36 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) 37 | 38 | cerebro.adddata(data) # Добавляем данные 39 | 40 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 41 | 42 | cerebro.run() # Запуск торговой системы 43 | cerebro.plot() # Рисуем график 44 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/04 - Resample.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | # Несколько временнЫх интервалов по одному тикеру: Получение большего временнОго интервала из меньшего (Resample) 8 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 12 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Хранилище Binance 19 | broker = store.getbroker() 20 | cerebro.setbroker(broker) 21 | 22 | # # Исторические 1-минутные бары + 5-минутные получаем путем Resample + M5 для контроля за последний 1 час + График т.к. оффлайн/ таймфрейм M5 + resample M1 + M5 + M5 23 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # берем данные за последние 5 часов 24 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по самому меньшему временному интервалу 25 | # cerebro.adddata(data) # Добавляем данные 26 | # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5, name="resampled") # Можно добавить больший временной интервал кратный меньшему (добавляется автоматом) 27 | # # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5, name="resampled").plotinfo.plot = False # без вывода на графике 28 | # # Добавляем данные из Binance для проверки правильности работы Resample 29 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по большому временнОму интервалу 30 | 31 | # -------------------------------------------------------------------------------------- 32 | # Внимание! - Данные приходят не синхронно по разным ТФ, поэтому есть сдвиг по времени # 33 | # - Вы можете это поправить, протестить и запушить изменения! # 34 | # -------------------------------------------------------------------------------------- 35 | 36 | # Исторические M15, H1 получаем путем Resample + H1 для контроля + График т.к. оффлайн/ таймфрейм M15 + resample H1 + H1 37 | from_date = dt.datetime.utcnow().date().today() 38 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=15, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по самому меньшему временному интервалу 39 | cerebro.adddata(data) # Добавляем данные 40 | cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="resampled",) # Можно добавить больший временной интервал кратный меньшему (добавляется автоматом) 41 | # cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="resampled", boundoff=1).plotinfo.plot = False # без вывода на графике 42 | # Добавляем данные из Binance для проверки правильности работы Resample 43 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по большому временнОму интервалу 44 | 45 | cerebro.adddata(data) # Добавляем данные 46 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 47 | 48 | cerebro.run() # Запуск торговой системы 49 | cerebro.plot() # Рисуем график 50 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/05 - Replay.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | # Использование меньшего временнОго интервала (Replay) 8 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 12 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Хранилище Binance 19 | broker = store.getbroker() 20 | cerebro.setbroker(broker) 21 | 22 | # Для стратегии используем исторические M1 бары, отображаем результат на H1 + График т.к. оффлайн/ таймфрейм M5 + resample M1 + M5 + M5 23 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # берем данные за последние 5 часов 24 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по самому меньшему временному интервалу 25 | cerebro.adddata(data) # Добавляем данные 26 | cerebro.replaydata(data, timeframe=bt.TimeFrame.Minutes, compression=60, name="replayed") # На графике видим большой интервал, прогоняем ТС на меньшем 27 | 28 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 29 | 30 | cerebro.run() # Запуск торговой системы 31 | cerebro.plot() # Рисуем график 32 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/06 - Rollover.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | 8 | def get_timeframe(tf, TimeFrame): 9 | """Преобразуем ТФ в параметры для добавления данных по стратегии""" 10 | interval = 1 # по умолчанию таймфрейм минутный 11 | _timeframe = TimeFrame.Minutes # по умолчанию таймфрейм минутный 12 | 13 | if tf == '1m': interval = 1 14 | if tf == '5m': interval = 5 15 | if tf == '15m': interval = 15 16 | if tf == '30m': interval = 30 17 | if tf == '1h': interval = 60 18 | if tf == '1d': _timeframe = TimeFrame.Days 19 | if tf == '1w': _timeframe = TimeFrame.Weeks 20 | if tf == '1M': _timeframe = TimeFrame.Months 21 | return _timeframe, interval 22 | 23 | 24 | # Склейка истории тикера из файла и Binance (Rollover) 25 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 26 | cerebro = bt.Cerebro(quicknotify=True) 27 | 28 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 29 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 30 | 31 | store = BinanceStore( 32 | api_key=Config.BINANCE_API_KEY, 33 | api_secret=Config.BINANCE_API_SECRET, 34 | coin_target=coin_target, 35 | testnet=False) # Хранилище Binance 36 | broker = store.getbroker() 37 | cerebro.setbroker(broker) 38 | 39 | tf = "1d" # '1m' '5m' '15m' '30m' '1h' '1d' '1w' '1M' 40 | _t, _c = get_timeframe(tf, bt.TimeFrame) 41 | 42 | d1 = bt.feeds.GenericCSVData( # Получаем историю из файла - в котором нет последних 5 дней 43 | timeframe=_t, compression=_c, # что-бы был тот же ТФ как и у d2 44 | dataname=f'{symbol}_{tf}_minus_5_days.csv', # Файл для импорта из Binance. Создается из примера 02 - Symbol data to DF.py 45 | separator=',', # Колонки разделены запятой 46 | dtformat='%Y-%m-%d', # dtformat='%Y-%m-%d %H:%M:%S', # Формат даты/времени YYYY-MM-DD HH:MM:SS 47 | openinterest=-1, # Открытого интереса в файле нет 48 | sessionend=dt.time(0, 0), # Для дневных данных и выше подставляется время окончания сессии. Чтобы совпадало с историей, нужно поставить закрытие на 00:00 49 | ) 50 | 51 | from_date = dt.datetime.utcnow() - dt.timedelta(days=15) # берем данные за последние 15 дней 52 | d2 = store.getdata(timeframe=_t, compression=_c, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по самому меньшему временному интервалу 53 | 54 | cerebro.rolloverdata(d1, d2, name=symbol) # Склеенный тикер 55 | 56 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 57 | 58 | cerebro.run() # Запуск торговой системы 59 | cerebro.plot() # Рисуем график 60 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/07 - Get Asset Balance.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | 4 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 5 | 6 | asset = 'BTC' 7 | 8 | balance = client.get_asset_balance(asset=asset) 9 | 10 | print(f" - Balance for {asset} is {balance['free']}") -------------------------------------------------------------------------------- /DataExamplesBinance_ru/08 - Timeframes.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | from Strategy import StrategyJustPrintsOHLCVAndState # Торговая система 6 | 7 | # Несколько временнЫх интервалов по одному тикеру: Получение из истории + live 8 | if __name__ == '__main__': # Точка входа при запуске этого скрипта 9 | cerebro = bt.Cerebro(quicknotify=True) 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 12 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Хранилище Binance 19 | broker = store.getbroker() 20 | cerebro.setbroker(broker) 21 | 22 | # 1. Исторические 5-минутные бары + 15-минутные за последние 10 часов + График т.к. оффлайн/ таймфрейм M5 + M15 23 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=10*60) # берем данные за последние 5 часов 24 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по малому временнОму интервалу (должен идти первым) 25 | cerebro.adddata(data) # Добавляем данные 26 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=15, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по большому временнОму интервалу 27 | 28 | # # 2. Исторические 1-минутные + 5-минутные бары за прошлый час + новые live бары / таймфрейм M1 + M5 29 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) # берем данные за последний 1 час 30 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) # Исторические данные по малому временнОму интервалу (должен идти первым) 31 | # cerebro.adddata(data) # Добавляем данные 32 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=5, dataname=symbol, start_date=from_date, LiveBars=True) # Исторические данные по большому временнОму интервалу 33 | 34 | # # 3. Исторические 1-часовые бары + 4-часовые за неделю + График т.к. оффлайн/ таймфрейм H1 + H4 35 | # from_date = dt.datetime.utcnow() - dt.timedelta(hours=24*7) # берем данные за последнюю неделю от текущего времени 36 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=60, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по малому временнОму интервалу (должен идти первым) 37 | # cerebro.adddata(data) # Добавляем данные 38 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=240, dataname=symbol, start_date=from_date, LiveBars=False) # Исторические данные по большому временнОму интервалу 39 | 40 | cerebro.adddata(data) # Добавляем данные 41 | cerebro.addstrategy(StrategyJustPrintsOHLCVAndState, coin_target=coin_target) # Добавляем торговую систему 42 | 43 | cerebro.run() # Запуск торговой системы 44 | cerebro.plot() # Рисуем график 45 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/09 - Get Asset Info - no Decimal.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | 4 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 5 | 6 | asset = 'BTC' 7 | 8 | balance = client.get_asset_balance(asset=asset) 9 | 10 | print(f" - Баланс для {asset} равен {balance['free']}") 11 | 12 | info = client.get_symbol_info('ETHUSDT') 13 | print(info) 14 | 15 | info = client.get_symbol_info('BTCUSDT') 16 | print(info) 17 | 18 | info = client.get_symbol_info('BNBUSDT') 19 | print(info) 20 | print(info['filters']) 21 | 22 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 23 | info = {} 24 | for ticker in tickers: 25 | info[ticker] = {} 26 | for _filter in client.get_symbol_info(ticker)['filters']: 27 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = float(_filter['minPrice']) 28 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = float(_filter['minQty']) 29 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = float(_filter['stepSize']) 30 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 31 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 32 | print(info) 33 | 34 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/09 - Get Asset Info - through client.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | from backtrader_binance import BinanceStore 3 | from ConfigBinance.Config import Config 4 | from decimal import Decimal 5 | 6 | cerebro = bt.Cerebro(quicknotify=True) 7 | 8 | cerebro.broker.setcash(100000) # Устанавливаем, сколько денег 9 | cerebro.broker.setcommission(commission=0.0015) # Установленная комиссия - 0,15%... разделите на 100, чтобы удалить % 10 | 11 | coin_target = 'USDT' # базовый тикер, в котором будут выполняться вычисления 12 | symbols = ('BTC', 'ETH', 'BNB') # тикеры, по которым мы будем получать данные 13 | 14 | store = BinanceStore( 15 | api_key=Config.BINANCE_API_KEY, 16 | api_secret=Config.BINANCE_API_SECRET, 17 | coin_target=coin_target, 18 | testnet=False) # Binance Storage 19 | 20 | client = store.binance # !!! 21 | 22 | asset = 'BTC' 23 | 24 | balance = client.get_asset_balance(asset=asset) 25 | 26 | print(f" - Balance for {asset} is {balance['free']}") 27 | 28 | info = client.get_symbol_info('ETHUSDT') 29 | print(info) 30 | 31 | info = client.get_symbol_info('BTCUSDT') 32 | print(info) 33 | 34 | info = client.get_symbol_info('BNBUSDT') 35 | print(info) 36 | print(info['filters']) 37 | 38 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 39 | info = {} 40 | for ticker in tickers: 41 | info[ticker] = {} 42 | for _filter in client.get_symbol_info(ticker)['filters']: 43 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = Decimal(_filter['minPrice']) 44 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = Decimal(_filter['minQty']) 45 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = Decimal(_filter['stepSize']) 46 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 47 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 48 | print(info) 49 | 50 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/09 - Get Asset Info.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | from decimal import Decimal 4 | 5 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 6 | 7 | asset = 'BTC' 8 | 9 | balance = client.get_asset_balance(asset=asset) 10 | 11 | print(f" - Balance for {asset} is {balance['free']}") 12 | 13 | info = client.get_symbol_info('ETHUSDT') 14 | print(info) 15 | 16 | info = client.get_symbol_info('BTCUSDT') 17 | print(info) 18 | 19 | info = client.get_symbol_info('BNBUSDT') 20 | print(info) 21 | print(info['filters']) 22 | 23 | tickers = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', ] 24 | info = {} 25 | for ticker in tickers: 26 | info[ticker] = {} 27 | for _filter in client.get_symbol_info(ticker)['filters']: 28 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["minPrice"] = Decimal(_filter['minPrice']) 29 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["minQty"] = Decimal(_filter['minQty']) 30 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["stepSize"] = Decimal(_filter['stepSize']) 31 | if _filter['filterType'] == 'LOT_SIZE': info[ticker]["step_num"] = _filter['stepSize'].find('1') - 2 32 | if _filter['filterType'] == 'PRICE_FILTER': info[ticker]["f_nums"] = _filter['minPrice'].find('1') - 1 33 | print(info) 34 | 35 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/10 - Get Historical Data.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from ConfigBinance.Config import Config 3 | import pandas as pd 4 | 5 | client = Client(Config.BINANCE_API_KEY, Config.BINANCE_API_SECRET) 6 | 7 | klines = client.get_historical_klines("BTCUSDT", Client.KLINE_INTERVAL_1DAY, "2023-05-10") 8 | 9 | df = pd.DataFrame(klines) 10 | df.drop(df.columns[[6, 7, 8, 9, 10, 11]], axis=1, inplace=True) # Удаление ненужных столбцов 11 | df.columns = ['datetime', 'open', 'high', 'low', 'close', 'volume'] 12 | df['datetime'] = df['datetime'].values.astype(dtype='datetime64[ms]') 13 | df['open'] = df['open'].values.astype(float) 14 | df['high'] = df['high'].values.astype(float) 15 | df['low'] = df['low'].values.astype(float) 16 | df['close'] = df['close'].values.astype(float) 17 | df['volume'] = df['volume'].values.astype(float) 18 | 19 | # df = df[:-1] # to skip last candle 20 | 21 | print(df) 22 | -------------------------------------------------------------------------------- /DataExamplesBinance_ru/Strategy.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | 4 | class StrategyJustPrintsOHLCVAndState(bt.Strategy): 5 | """ 6 | - Отображает статус подключения 7 | - При приходе нового бара отображает его цены/объем 8 | - Отображает статус - исторические бары или live 9 | """ 10 | params = ( # Параметры торговой системы 11 | ('coin_target', ''), # 12 | ) 13 | 14 | def next(self): 15 | """Приход нового бара тикера""" 16 | for data in self.datas: # Пробегаемся по всем запрошенным тикерам 17 | ticker = data._name 18 | status = 1 19 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 20 | try: 21 | status = data._state # 0 - Live data, 1 - History data, 2 - None 22 | _interval = data.interval 23 | except Exception as e: 24 | if data.resampling == 1: 25 | status = 22 26 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 27 | _interval = f"_{_interval}" 28 | else: 29 | # print("Error:", e) 30 | pass 31 | 32 | if status in [0, 1, 22]: 33 | _state = "Resampled Data" 34 | if status == 1: _state = "False - History data" 35 | if status == 0: _state = "True - Live data" 36 | 37 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 38 | bt.num2date(data.datetime[0]), 39 | data._name, 40 | _interval, # таймфрейм тикера 41 | data.open[0], 42 | data.high[0], 43 | data.low[0], 44 | data.close[0], 45 | data.volume[0], 46 | _state, 47 | )) 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Oleg Shpagin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/01 - Live Trade - Just Buy and Sell.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class JustBuySellStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration - just buy and sell 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | def next(self): 23 | """Arrival of a new ticker candle""" 24 | for data in self.datas: # Running through all the requested bars of all tickers 25 | ticker = data._name 26 | status = data._state # 0 - Live data, 1 - History data, 2 - None 27 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 28 | 29 | if status in [0, 1]: 30 | if status: _state = "False - History data" 31 | else: _state = "True - Live data" 32 | 33 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 34 | bt.num2date(data.datetime[0]), 35 | data._name, 36 | _interval, # ticker timeframe 37 | data.open[0], 38 | data.high[0], 39 | data.low[0], 40 | data.close[0], 41 | data.volume[0], 42 | _state, 43 | )) 44 | 45 | if status == 0: # Live trade 46 | coin_target = self.p.coin_target 47 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 48 | # Very slow function! Because we are going through API to get those values... 49 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 50 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 51 | 52 | order = self.orders[data._name] # The order of ticker 53 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 54 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 55 | 56 | if not self.getposition(data): # If there is no position 57 | print("there is no position") 58 | 59 | # if we have order but don't get position -> then cancel it 60 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 61 | print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 62 | self.cancel(order) # then cancel it 63 | 64 | size = float(self.broker._store._min_order[ticker]) # min value to buy for BTC and ETH 65 | min_in_USDT = float(self.broker._store._min_order_in_target[ticker]) # min value to buy for BTC and ETH 66 | _close = data.close[0] 67 | # correct size to be minimum applicable volume 68 | if size * _close < min_in_USDT: size = min_in_USDT / _close 69 | size = float(self.broker._store.format_quantity(ticker, size)) 70 | 71 | # # Set Limit order 72 | # # Let's buy min value of ticker - min_order by price lower on 5% from current 73 | # price = float(self.broker._store.format_price(ticker, data.low[0] * 0.95)) # 5% lower than the min price 74 | # # correct size to be minimum applicable volume 75 | # if size * price < min_in_USDT: size = min_in_USDT / price 76 | # size = float(self.broker._store.format_quantity(ticker, size)) 77 | # 78 | # print(f" - buy {ticker} size = {size} (min_order) at price = {price}") 79 | # self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 80 | # print(f"\t - The Limit order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 81 | 82 | # Set Market order 83 | # Let's buy just a little amount by market price 84 | print(f" - buy {ticker} size = {size} (min_order) at Market price") 85 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Market, size=size) 86 | print(f"\t - The Market order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 87 | 88 | def notify_order(self, order): 89 | """Changing the status of the order""" 90 | order_data_name = order.data._name # Name of ticker from order 91 | 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}') 92 | if order.status == bt.Order.Completed: # If the order is fully executed 93 | if order.isbuy(): # The order to buy 94 | self.log(f'Buy {order_data_name} Price: {order.executed.price:.2f}, Value {order.executed.value:.2f} {self.p.coin_target}, Commission {order.executed.comm:.10f} {self.p.coin_target}') 95 | else: # The order to sell 96 | self.log(f'Sell {order_data_name} Price: {order.executed.price:.2f}, Value {order.executed.value:.2f} {self.p.coin_target}, Commission {order.executed.comm:.10f} {self.p.coin_target}') 97 | self.orders[order_data_name] = None # Reset the order to enter the position 98 | 99 | def notify_trade(self, trade): 100 | """Changing the position status""" 101 | if trade.isclosed: # If the position is closed 102 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 103 | 104 | def log(self, txt, dt=None): 105 | """Print string with date to the console""" 106 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 107 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 108 | 109 | 110 | if __name__ == '__main__': 111 | cerebro = bt.Cerebro(quicknotify=True) 112 | 113 | coin_target = 'USDT' # the base ticker in which calculations will be performed 114 | symbol = 'ETH' + coin_target # the ticker by which we will receive data in the format 115 | 116 | store = BinanceStore( 117 | api_key=Config.BINANCE_API_KEY, 118 | api_secret=Config.BINANCE_API_SECRET, 119 | coin_target=coin_target, 120 | testnet=False) # Binance Storage 121 | 122 | # live connection to Binance - for Offline comment these two lines 123 | broker = store.getbroker() 124 | cerebro.setbroker(broker) 125 | 126 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 127 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=5) 128 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 129 | 130 | cerebro.adddata(data) # Adding data 131 | 132 | cerebro.addstrategy(JustBuySellStrategy, coin_target=coin_target) # Adding a trading system 133 | 134 | cerebro.run() # Launching a trading system 135 | cerebro.plot() # Draw a chart 136 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/01 - Live Trade.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | # creating indicators for each ticker 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Arrival of a new ticker candle""" 34 | for data in self.datas: # Running through all the requested bars of all tickers 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # ticker timeframe 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # The order of ticker 64 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 65 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 66 | if not self.getposition(data): # If there is no position 67 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 68 | print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 69 | self.cancel(order) # then cancel it 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "ETHUSDT": size = 0.007 74 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # 5% lower than the min price 75 | print(f" - buy {ticker} size = {size} at price = {price}") 76 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 77 | print(f"\t - The order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 78 | 79 | else: # If there is a position 80 | if self.rsi[ticker] > 75: 81 | print("sell") 82 | print(f"\t - Sell it by the market {data._name}...") 83 | self.orders[data._name] = self.close() # Request to close a position at the market price 84 | 85 | def notify_order(self, order): 86 | """Changing the status of the order""" 87 | order_data_name = order.data._name # Name of ticker from order 88 | 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}') 89 | if order.status == bt.Order.Completed: # If the order is fully executed 90 | if order.isbuy(): # The order to buy 91 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 92 | else: # The order to sell 93 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 94 | self.orders[order_data_name] = None # Reset the order to enter the position 95 | 96 | def notify_trade(self, trade): 97 | """Changing the position status""" 98 | if trade.isclosed: # If the position is closed 99 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 100 | 101 | def log(self, txt, dt=None): 102 | """Print string with date to the console""" 103 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 104 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 105 | 106 | 107 | if __name__ == '__main__': 108 | cerebro = bt.Cerebro(quicknotify=True) 109 | 110 | coin_target = 'USDT' # the base ticker in which calculations will be performed 111 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 112 | symbol2 = 'ETH' + coin_target # the ticker by which we will receive data in the format 113 | 114 | store = BinanceStore( 115 | api_key=Config.BINANCE_API_KEY, 116 | api_secret=Config.BINANCE_API_SECRET, 117 | coin_target=coin_target, 118 | testnet=False) # Binance Storage 119 | 120 | # live connection to Binance - for Offline comment these two lines 121 | broker = store.getbroker() 122 | cerebro.setbroker(broker) 123 | 124 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 125 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 126 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 127 | data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=True) 128 | 129 | cerebro.adddata(data) # Adding data 130 | cerebro.adddata(data2) # Adding data 131 | 132 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Adding a trading system 133 | 134 | cerebro.run() # Launching a trading system 135 | cerebro.plot() # Draw a chart 136 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/02 - Live Trade MultiPortfolio.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | # creating indicators for each ticker 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Arrival of a new ticker candle""" 34 | for data in self.datas: # Running through all the requested bars of all tickers 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # ticker timeframe 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # The order of ticker 64 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 65 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 66 | if not self.getposition(data): # If there is no position 67 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 68 | print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 69 | self.cancel(order) # then cancel it 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "ETHUSDT": size = 0.007 74 | if data._name == "BNBUSDT": size = 0.05 75 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # 5% lower than the min price 76 | print(f" - buy {ticker} size = {size} at price = {price}") 77 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 78 | print(f"\t - The order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 79 | 80 | else: # If there is a position 81 | if self.rsi[ticker] > 75: 82 | print("sell") 83 | print(f"\t - Sell it by the market {data._name}...") 84 | self.orders[data._name] = self.close() # Request to close a position at the market price 85 | 86 | def notify_order(self, order): 87 | """Changing the status of the order""" 88 | order_data_name = order.data._name # Name of ticker from order 89 | 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}') 90 | if order.status == bt.Order.Completed: # If the order is fully executed 91 | if order.isbuy(): # The order to buy 92 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 93 | else: # The order to sell 94 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 95 | self.orders[order_data_name] = None # Reset the order to enter the position 96 | 97 | def notify_trade(self, trade): 98 | """Changing the position status""" 99 | if trade.isclosed: # If the position is closed 100 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 101 | 102 | def log(self, txt, dt=None): 103 | """Print string with date to the console""" 104 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 105 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 106 | 107 | 108 | if __name__ == '__main__': 109 | cerebro = bt.Cerebro(quicknotify=True) 110 | 111 | coin_target = 'USDT' # the base ticker in which calculations will be performed 112 | symbols = ('BTC', 'ETH', 'BNB') # tickers for which we will receive data 113 | 114 | store = BinanceStore( 115 | api_key=Config.BINANCE_API_KEY, 116 | api_secret=Config.BINANCE_API_SECRET, 117 | coin_target=coin_target, 118 | testnet=False) # Binance Storage 119 | 120 | # live connection to Binance - for Offline comment these two lines 121 | broker = store.getbroker() 122 | cerebro.setbroker(broker) 123 | 124 | for _symbol in symbols: # Running through all the tickers 125 | 126 | symbol = _symbol + coin_target # the ticker by which we will receive data in the format 127 | 128 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 129 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 130 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 131 | 132 | cerebro.adddata(data) # Adding data 133 | 134 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Adding a trading system 135 | 136 | cerebro.run() # Launching a trading system 137 | cerebro.plot() # Draw a chart 138 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/03 - Live Trade ETH.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | # creating indicators for each ticker 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Arrival of a new ticker candle""" 34 | for data in self.datas: # Running through all the requested bars of all tickers 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # ticker timeframe 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # The order of ticker 64 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 65 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 66 | if not self.getposition(data): # If there is no position 67 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 68 | print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 69 | self.cancel(order) # then cancel it 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "XMRETH": size = 0.1 74 | if data._name == "BNBETH": size = 0.1 75 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # 5% lower than the min price 76 | print(f" - buy {ticker} size = {size} at price = {price}") 77 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 78 | print(f"\t - The order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 79 | 80 | else: # If there is a position 81 | if self.rsi[ticker] > 75: 82 | print("sell") 83 | print(f"\t - Sell it by the market {data._name}...") 84 | self.orders[data._name] = self.close() # Request to close a position at the market price 85 | 86 | def notify_order(self, order): 87 | """Changing the status of the order""" 88 | order_data_name = order.data._name # Name of ticker from order 89 | 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}') 90 | if order.status == bt.Order.Completed: # If the order is fully executed 91 | if order.isbuy(): # The order to buy 92 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 93 | else: # The order to sell 94 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 95 | self.orders[order_data_name] = None # Reset the order to enter the position 96 | 97 | def notify_trade(self, trade): 98 | """Changing the position status""" 99 | if trade.isclosed: # If the position is closed 100 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 101 | 102 | def log(self, txt, dt=None): 103 | """Print string with date to the console""" 104 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 105 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 106 | 107 | 108 | if __name__ == '__main__': 109 | cerebro = bt.Cerebro(quicknotify=True) 110 | 111 | coin_target = 'ETH' # the base ticker in which calculations will be performed 112 | symbol = 'BNB' + coin_target # the ticker by which we will receive data in the format 113 | symbol2 = 'XMR' + coin_target # the ticker by which we will receive data in the format 114 | 115 | store = BinanceStore( 116 | api_key=Config.BINANCE_API_KEY, 117 | api_secret=Config.BINANCE_API_SECRET, 118 | coin_target=coin_target, 119 | testnet=False) # Binance Storage 120 | 121 | # live connection to Binance - for Offline comment these two lines 122 | broker = store.getbroker() 123 | cerebro.setbroker(broker) 124 | 125 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 126 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 127 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 128 | data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=True) 129 | 130 | cerebro.adddata(data) # Adding data 131 | cerebro.adddata(data2) # Adding data 132 | 133 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Adding a trading system 134 | 135 | cerebro.run() # Launching a trading system 136 | cerebro.plot() # Draw a chart 137 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/04 - Offline Backtest.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ('timeframe', ''), 15 | ) 16 | 17 | def __init__(self): 18 | """Initialization, adding indicators for each ticker""" 19 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 20 | for d in self.datas: # Running through all the tickers 21 | self.orders[d._name] = None # There is no order for ticker yet 22 | 23 | # creating indicators for each ticker 24 | self.sma1 = {} 25 | self.sma2 = {} 26 | self.rsi = {} 27 | for i in range(len(self.datas)): 28 | ticker = list(self.dnames.keys())[i] # key name is ticker name 29 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 30 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 31 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 32 | 33 | def next(self): 34 | """Arrival of a new ticker candle""" 35 | for data in self.datas: # Running through all the requested bars of all tickers 36 | ticker = data._name 37 | status = data._state # 0 - Live data, 1 - History data, 2 - None 38 | _interval = self.p.timeframe 39 | 40 | if status in [0, 1]: 41 | if status: _state = "False - History data" 42 | else: _state = "True - Live data" 43 | 44 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 45 | bt.num2date(data.datetime[0]), 46 | data._name, 47 | _interval, # ticker timeframe 48 | data.open[0], 49 | data.high[0], 50 | data.low[0], 51 | data.close[0], 52 | data.volume[0], 53 | _state, 54 | )) 55 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 56 | 57 | coin_target = self.p.coin_target 58 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 59 | 60 | # Very slow function! Because we are going through API to get those values... 61 | # symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 62 | # print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 63 | 64 | order = self.orders[data._name] # The order of ticker 65 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 66 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 67 | if not self.getposition(data): # If there is no position 68 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 69 | print(f"\t - Cancel the order {order.p.tradeid} to buy {data._name}") 70 | # self.cancel(order) # then cancel it 71 | 72 | if self.rsi[ticker] < 30: # Enter long 73 | size = 0.0005 # min value to buy for BTC and ETH 74 | if data._name == "ETHUSDT": size = 0.05 75 | 76 | price = data.close[0] # by closing price 77 | 78 | print(f" - buy {ticker} size = {size} at price = {price}") 79 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 80 | print(f"\t - The order has been submitted {self.orders[data._name].p.tradeid} to buy {data._name}") 81 | 82 | else: # If there is a position 83 | if self.rsi[ticker] > 70: 84 | print("sell") 85 | print(f"\t - Sell it by the market {data._name}...") 86 | self.orders[data._name] = self.close() # Request to close a position at the market price 87 | 88 | def notify_order(self, order): 89 | """Changing the status of the order""" 90 | order_data_name = order.data._name # Name of ticker from order 91 | 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}') 92 | if order.status == bt.Order.Completed: # If the order is fully executed 93 | if order.isbuy(): # The order to buy 94 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 95 | else: # The order to sell 96 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 97 | self.orders[order_data_name] = None # Reset the order to enter the position 98 | 99 | def notify_trade(self, trade): 100 | """Changing the position status""" 101 | if trade.isclosed: # If the position is closed 102 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 103 | 104 | def log(self, txt, dt=None): 105 | """Print string with date to the console""" 106 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 107 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 108 | 109 | 110 | if __name__ == '__main__': 111 | cerebro = bt.Cerebro(quicknotify=True) 112 | 113 | cerebro.broker.setcash(200000) # Setting how much money 114 | cerebro.broker.setcommission(commission=0.0015) # Set the commission - 0.15% ... divide by 100 to remove % 115 | 116 | coin_target = 'USDT' # the base ticker in which calculations will be performed 117 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 118 | symbol2 = 'ETH' + coin_target # the ticker by which we will receive data in the format 119 | 120 | store = BinanceStore( 121 | api_key=Config.BINANCE_API_KEY, 122 | api_secret=Config.BINANCE_API_SECRET, 123 | coin_target=coin_target, 124 | testnet=False) # Binance Storage 125 | 126 | # # live connection to Binance - for Offline comment these two lines 127 | # broker = store.getbroker() 128 | # cerebro.setbroker(broker) 129 | 130 | # ----------------------------------------------------------- 131 | # Attention! - Now it's Offline for testing strategies # 132 | # ----------------------------------------------------------- 133 | 134 | # Historical 1-minute bars for 10 hours + new live bars / timeframe M1 135 | timeframe = "M1" 136 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 137 | 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 138 | 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 139 | 140 | cerebro.adddata(data) # Adding data 141 | cerebro.adddata(data2) # Adding data 142 | 143 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Adding a trading system 144 | 145 | cerebro.run() # Launching a trading system 146 | cerebro.plot() # Draw a chart 147 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/05 - Offline Backtest MultiPortfolio.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ('timeframe', ''), 15 | ) 16 | 17 | def __init__(self): 18 | """Initialization, adding indicators for each ticker""" 19 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 20 | for d in self.datas: # Running through all the tickers 21 | self.orders[d._name] = None # There is no order for ticker yet 22 | 23 | # creating indicators for each ticker 24 | self.sma1 = {} 25 | self.sma2 = {} 26 | self.rsi = {} 27 | for i in range(len(self.datas)): 28 | ticker = list(self.dnames.keys())[i] # key name is ticker name 29 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 30 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 31 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 32 | 33 | def next(self): 34 | """Arrival of a new ticker candle""" 35 | for data in self.datas: # Running through all the requested bars of all tickers 36 | ticker = data._name 37 | status = data._state # 0 - Live data, 1 - History data, 2 - None 38 | _interval = self.p.timeframe 39 | 40 | if status in [0, 1]: 41 | if status: _state = "False - History data" 42 | else: _state = "True - Live data" 43 | 44 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 45 | bt.num2date(data.datetime[0]), 46 | data._name, 47 | _interval, # ticker timeframe 48 | data.open[0], 49 | data.high[0], 50 | data.low[0], 51 | data.close[0], 52 | data.volume[0], 53 | _state, 54 | )) 55 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 56 | 57 | coin_target = self.p.coin_target 58 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 59 | 60 | # Very slow function! Because we are going through API to get those values... 61 | # symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 62 | # print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 63 | 64 | order = self.orders[data._name] # The order of ticker 65 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 66 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 67 | if not self.getposition(data): # If there is no position 68 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 69 | print(f"\t - Cancel the order {order.p.tradeid} to buy {data._name}") 70 | # self.cancel(order) # then cancel it 71 | 72 | if self.rsi[ticker] < 30: # Enter long 73 | size = 0.0005 # min value to buy for BTC and ETH 74 | if data._name == "ETHUSDT": size = 0.05 75 | if data._name == "BNBUSDT": size = 0.01 76 | 77 | price = data.close[0] # by closing price 78 | 79 | print(f" - buy {ticker} size = {size} at price = {price}") 80 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 81 | print(f"\t - The order has been submitted {self.orders[data._name].p.tradeid} to buy {data._name}") 82 | 83 | else: # If there is a position 84 | if self.rsi[ticker] > 70: 85 | print("sell") 86 | print(f"\t - Sell it by the market {data._name}...") 87 | self.orders[data._name] = self.close() # Request to close a position at the market price 88 | 89 | def notify_order(self, order): 90 | """Changing the status of the order""" 91 | order_data_name = order.data._name # Name of ticker from order 92 | 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}') 93 | if order.status == bt.Order.Completed: # If the order is fully executed 94 | if order.isbuy(): # The order to buy 95 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 96 | else: # The order to sell 97 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 98 | self.orders[order_data_name] = None # Reset the order to enter the position 99 | 100 | def notify_trade(self, trade): 101 | """Changing the position status""" 102 | if trade.isclosed: # If the position is closed 103 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 104 | 105 | def log(self, txt, dt=None): 106 | """Print string with date to the console""" 107 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 108 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 109 | 110 | 111 | if __name__ == '__main__': 112 | cerebro = bt.Cerebro(quicknotify=True) 113 | 114 | cerebro.broker.setcash(100000) # Setting how much money 115 | cerebro.broker.setcommission(commission=0.0015) # Set the commission - 0.15% ... divide by 100 to remove % 116 | 117 | coin_target = 'USDT' # the base ticker in which calculations will be performed 118 | symbols = ('BTC', 'ETH', 'BNB') # tickers for which we will receive data 119 | 120 | store = BinanceStore( 121 | api_key=Config.BINANCE_API_KEY, 122 | api_secret=Config.BINANCE_API_SECRET, 123 | coin_target=coin_target, 124 | testnet=False) # Binance Storage 125 | 126 | # # live connection to Binance - for Offline comment these two lines 127 | # broker = store.getbroker() 128 | # cerebro.setbroker(broker) 129 | 130 | # ----------------------------------------------------------- 131 | # Attention! - Now it's Offline for testing strategies # 132 | # ----------------------------------------------------------- 133 | 134 | for _symbol in symbols: # Running through all the tickers 135 | 136 | symbol = _symbol + coin_target # the ticker by which we will receive data in the format 137 | 138 | # Historical 1-minute bars for 10 hours + new live bars / timeframe M1 139 | timeframe = "M1" 140 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 141 | 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 142 | 143 | cerebro.adddata(data) # Adding data 144 | 145 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Adding a trading system 146 | 147 | cerebro.run() # Launching a trading system 148 | cerebro.plot() # Draw a chart 149 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/06 - Live Trade Just Buy and Close by Market.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | # creating indicators for each ticker 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | self.buy_once = {} 33 | self.sell_once = {} 34 | 35 | def start(self): 36 | for d in self.datas: # Running through all the tickers 37 | self.buy_once[d._name] = False 38 | self.sell_once[d._name] = False 39 | 40 | def next(self): 41 | """Arrival of a new ticker candle""" 42 | for data in self.datas: # Running through all the requested bars of all tickers 43 | ticker = data._name 44 | status = data._state # 0 - Live data, 1 - History data, 2 - None 45 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 46 | 47 | if status in [0, 1]: 48 | if status: _state = "False - History data" 49 | else: _state = "True - Live data" 50 | 51 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 52 | bt.num2date(data.datetime[0]), 53 | data._name, 54 | _interval, # ticker timeframe 55 | data.open[0], 56 | data.high[0], 57 | data.low[0], 58 | data.close[0], 59 | data.volume[0], 60 | _state, 61 | )) 62 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 63 | 64 | if status != 0: continue # if not live - do not enter to position! 65 | 66 | coin_target = self.p.coin_target 67 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 68 | 69 | # Very slow function! Because we are going through API to get those values... 70 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 71 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 72 | 73 | order = self.orders[data._name] # The order of ticker 74 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 75 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 76 | if not self.getposition(data): # If there is no position 77 | # if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 78 | # print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 79 | # self.cancel(order) # then cancel it 80 | 81 | if not self.buy_once[ticker]: # Enter long 82 | size = 0.0007 # min value to buy for BTC and ETH 83 | if data._name == "ETHUSDT": size = 0.007 84 | price = self.broker._store.format_price(ticker, data.close[0] * 1) # buy at close price 85 | print(f" - buy {ticker} size = {size} at price = {price}") 86 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 87 | print(f"\t - The order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 88 | 89 | self.buy_once[ticker] = len(self) # prevent from second buy... writing the number of bar 90 | 91 | else: # If there is a position 92 | print(self.sell_once[ticker], self.buy_once[ticker], len(self), len(self) > self.buy_once[ticker] + 3) 93 | if not self.sell_once[ticker]: # if we are selling first time 94 | if self.buy_once[ticker] and len(self) > self.buy_once[ticker] + 3: # if we have position sell after 3 bars by market 95 | print("sell") 96 | print(f"\t - Sell it by the market {data._name}...") 97 | self.orders[data._name] = self.close() # Request to close a position at the market price 98 | 99 | self.sell_once[ticker] = True # to prevent sell second time 100 | 101 | def notify_order(self, order): 102 | """Changing the status of the order""" 103 | order_data_name = order.data._name # Name of ticker from order 104 | 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}') 105 | if order.status == bt.Order.Completed: # If the order is fully executed 106 | if order.isbuy(): # The order to buy 107 | print(self.orders[data._name].binance_order) # order.executed.price, order.executed.value, order.executed.comm - you can get from here 108 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 109 | else: # The order to sell 110 | print(self.orders[data._name].binance_order) # order.executed.price, order.executed.value, order.executed.comm - you can get from here 111 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 112 | self.orders[order_data_name] = None # Reset the order to enter the position - in case of linked buy 113 | # self.orders[order_data_name] = None # Reset the order to enter the position 114 | 115 | def notify_trade(self, trade): 116 | """Changing the position status""" 117 | if trade.isclosed: # If the position is closed 118 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 119 | 120 | def log(self, txt, dt=None): 121 | """Print string with date to the console""" 122 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 123 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 124 | 125 | 126 | if __name__ == '__main__': 127 | cerebro = bt.Cerebro(quicknotify=True) 128 | 129 | coin_target = 'USDT' # the base ticker in which calculations will be performed 130 | symbol = 'ETH' + coin_target # the ticker by which we will receive data in the format 131 | 132 | store = BinanceStore( 133 | api_key=Config.BINANCE_API_KEY, 134 | api_secret=Config.BINANCE_API_SECRET, 135 | coin_target=coin_target, 136 | testnet=False) # Binance Storage 137 | 138 | # live connection to Binance - for Offline comment these two lines 139 | broker = store.getbroker() 140 | cerebro.setbroker(broker) 141 | 142 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 143 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 144 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 145 | 146 | cerebro.adddata(data) # Adding data 147 | 148 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Adding a trading system 149 | 150 | cerebro.run() # Launching a trading system 151 | cerebro.plot() # Draw a chart 152 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/07 - Offline Backtest Indicators.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | # video on creating this strategy - on Russian ) 7 | # RuTube: https://rutube.ru/video/417e306e6b5d6351d74bd9cd4d6af051/ 8 | # YouTube: https://youtube.com/live/k82vabGva7s 9 | 10 | class UnderOver(bt.Indicator): 11 | lines = ('underover',) 12 | params = dict(data2=20) 13 | plotinfo = dict(plot=True) 14 | 15 | def __init__(self): 16 | self.l.underover = self.data < self.p.data2 # data under data2 == 1 17 | 18 | # Trading System 19 | class RSIStrategy(bt.Strategy): 20 | """ 21 | Live strategy demonstration with SMA, RSI indicators 22 | """ 23 | params = ( # Parameters of the trading system 24 | ('coin_target', ''), 25 | ('timeframe', ''), 26 | ) 27 | 28 | def __init__(self): 29 | """Initialization, adding indicators for each ticker""" 30 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 31 | for d in self.datas: # Running through all the tickers 32 | self.orders[d._name] = None # There is no order for ticker yet 33 | 34 | # creating indicators for each ticker 35 | self.sma1 = {} 36 | self.sma2 = {} 37 | self.sma3 = {} 38 | self.crossover = {} 39 | self.underover_sma = {} 40 | self.rsi = {} 41 | self.underover_rsi = {} 42 | for i in range(len(self.datas)): 43 | ticker = list(self.dnames.keys())[i] # key name is ticker name 44 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=9) # SMA1 indicator 45 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=30) # SMA2 indicator 46 | self.sma3[ticker] = bt.indicators.SMA(self.datas[i], period=60) # SMA3 indicator 47 | 48 | # signal 1 - intersection of a fast SMA from bottom to top of a slow SMA 49 | self.crossover[ticker] = bt.ind.CrossOver(self.sma1[ticker], self.sma2[ticker]) # crossover SMA1 and SMA2 50 | 51 | # signal 2 - when SMA3 is below SMA2 52 | self.underover_sma[ticker] = UnderOver(self.sma3[ticker].lines.sma, data2=self.sma2[ticker].lines.sma) 53 | 54 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=20) # RSI indicator 55 | 56 | # signal 3 - when the RSI is below 30 57 | self.underover_rsi[ticker] = UnderOver(self.rsi[ticker].lines.rsi, data2=30) 58 | 59 | def next(self): 60 | """Arrival of a new ticker candle""" 61 | for data in self.datas: # Running through all the requested bars of all tickers 62 | ticker = data._name 63 | status = data._state # 0 - Live data, 1 - History data, 2 - None 64 | _interval = self.p.timeframe 65 | 66 | if status in [0, 1]: 67 | if status: _state = "False - History data" 68 | else: _state = "True - Live data" 69 | 70 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 71 | bt.num2date(data.datetime[0]), 72 | data._name, 73 | _interval, # ticker timeframe 74 | data.open[0], 75 | data.high[0], 76 | data.low[0], 77 | data.close[0], 78 | data.volume[0], 79 | _state, 80 | )) 81 | print(f'\t - RSI =', self.rsi[ticker][0]) 82 | print(f"\t - crossover =", self.crossover[ticker].lines.crossover[0]) 83 | 84 | coin_target = self.p.coin_target 85 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 86 | 87 | # signals to open position 88 | signal1 = self.crossover[ticker].lines.crossover[0] # signal 1 - intersection of a fast SMA from bottom to top of a slow SMA 89 | signal2 = self.underover_sma[ticker] # signal 2 - when SMA3 is below SMA2 90 | 91 | # signals to close position 92 | signal3 = self.underover_rsi[ticker] # signal 3 - when the RSI is below 30 93 | 94 | if not self.getposition(data): # If there is no position 95 | if signal1 == 1: 96 | if signal2 == 1: 97 | # buy 98 | free_money = self.broker.getcash() 99 | price = data.close[0] # by closing price 100 | size = (free_money / price) * 0.25 # 25% of available funds 101 | print("-"*50) 102 | print(f"\t - buy {ticker} size = {size} at price = {price}") 103 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 104 | print(f"\t - Order has been submitted {self.orders[data._name].p.tradeid} to buy {data._name}") 105 | print("-" * 50) 106 | 107 | else: # If there is a position 108 | if signal3 == 1: 109 | # sell 110 | print("-" * 50) 111 | print(f"\t - Продаем по рынку {data._name}...") 112 | self.orders[data._name] = self.close() # Request to close a position at the market price 113 | print("-" * 50) 114 | 115 | def notify_order(self, order): 116 | """Changing the status of the order""" 117 | print("*"*50) 118 | order_data_name = order.data._name # Name of ticker from order 119 | 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}') 120 | if order.status == bt.Order.Completed: # If the order is fully executed 121 | if order.isbuy(): # The order to buy 122 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 123 | else: # The order to sell 124 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 125 | self.orders[order_data_name] = None # Reset the order to enter the position 126 | print("*" * 50) 127 | 128 | def notify_trade(self, trade): 129 | """Changing the position status""" 130 | if trade.isclosed: # If the position is closed 131 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 132 | 133 | def log(self, txt, dt=None): 134 | """Print string with date to the console""" 135 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 136 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 137 | 138 | 139 | if __name__ == '__main__': 140 | cerebro = bt.Cerebro(quicknotify=True) 141 | 142 | cerebro.broker.setcash(2000) # Setting how much money 143 | cerebro.broker.setcommission(commission=0.0015) # Set the commission - 0.15% ... divide by 100 to remove % 144 | 145 | coin_target = 'USDT' # the base ticker in which calculations will be performed 146 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 147 | symbol2 = 'ETH' + coin_target # the ticker by which we will receive data in the format 148 | 149 | store = BinanceStore( 150 | api_key=Config.BINANCE_API_KEY, 151 | api_secret=Config.BINANCE_API_SECRET, 152 | coin_target=coin_target, 153 | testnet=False) # Binance Storage 154 | 155 | # # live connection to Binance - for Offline comment these two lines 156 | # broker = store.getbroker() 157 | # cerebro.setbroker(broker) 158 | 159 | # ----------------------------------------------------------- 160 | # Attention! - Now it's Offline for testing strategies # 161 | # ----------------------------------------------------------- 162 | 163 | # # Historical 1-minute bars for 10 hours + new live bars / timeframe M1 164 | # timeframe = "M1" 165 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 166 | # 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 167 | # # 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 168 | 169 | # Historical D1 bars for 365 days + new live bars / timeframe D1 170 | timeframe = "D1" 171 | from_date = dt.datetime.utcnow() - dt.timedelta(days=365*3) 172 | 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 173 | 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 174 | 175 | cerebro.adddata(data) # Adding data 176 | cerebro.adddata(data2) # Adding data 177 | 178 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Adding a trading system 179 | 180 | cerebro.run() # Launching a trading system 181 | cerebro.plot() # Draw a chart 182 | 183 | print() 184 | print("$"*77) 185 | print(f"Liquidation value of the portfolio: {cerebro.broker.getvalue()}") # Liquidation value of the portfolio 186 | print(f"Remaining available funds: {cerebro.broker.getcash()}") # Remaining available funds 187 | print("$" * 77) 188 | -------------------------------------------------------------------------------- /StrategyExamplesBinance/08 - Offline Backtest Margin Trade with Leverage 50x - Linear Trade.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | import backtrader.analyzers as btanalyzers 4 | from backtrader_binance import BinanceStore 5 | from ConfigBinance.Config import Config # Configuration file 6 | 7 | # https://www.backtrader.com/blog/posts/2019-08-29-fractional-sizes/fractional-sizes/ 8 | class CommInfoFractional(bt.CommissionInfo): 9 | def getsize(self, price, cash): 10 | """ Returns fractional size for cash operation for cryptocurrencies """ 11 | return self.p.leverage * (cash / price) 12 | 13 | 14 | class UnderOver(bt.Indicator): 15 | lines = ('underover',) 16 | params = dict(data2=20) 17 | plotinfo = dict(plot=True) 18 | 19 | def __init__(self): 20 | self.l.underover = self.data < self.p.data2 # data under data2 == 1 21 | 22 | 23 | # Trading System 24 | class SimpleSMAStrategy(bt.Strategy): 25 | """ Backtest strategy demonstration with SMA indicators """ 26 | params = ( # Parameters of the trading system 27 | ('coin_target', ''), 28 | ('timeframe', ''), 29 | ('leverage', ''), 30 | ) 31 | 32 | def __init__(self): 33 | """Initialization, adding indicators for each ticker""" 34 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 35 | for d in self.datas: # Running through all the tickers 36 | self.orders[d._name] = None # There is no order for ticker yet 37 | 38 | # creating indicators for each ticker 39 | self.sma1 = {} 40 | self.sma2 = {} 41 | self.sma3 = {} 42 | self.crossover = {} 43 | self.underover_sma = {} 44 | 45 | for i in range(len(self.datas)): 46 | ticker = list(self.dnames.keys())[i] # key name is ticker name 47 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=9) # SMA1 indicator 48 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=30) # SMA2 indicator 49 | self.sma3[ticker] = bt.indicators.SMA(self.datas[i], period=60) # SMA3 indicator 50 | 51 | # signal 1 - intersection of a fast SMA from bottom to top of a slow SMA 52 | self.crossover[ticker] = bt.ind.CrossOver(self.sma1[ticker], self.sma2[ticker]) # crossover SMA1 and SMA2 53 | 54 | # signal 2 - when SMA3 is below SMA2 55 | self.underover_sma[ticker] = UnderOver(self.sma3[ticker].lines.sma, data2=self.sma2[ticker].lines.sma) 56 | 57 | 58 | def next(self): 59 | """Arrival of a new ticker candle""" 60 | for data in self.datas: # Running through all the requested bars of all tickers 61 | ticker = data._name 62 | status = data._state # 0 - Live data, 1 - History data, 2 - None 63 | _interval = self.p.timeframe 64 | 65 | if status in [0, 1]: 66 | if status: _state = "False - History data" 67 | else: _state = "True - Live data" 68 | 69 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format(bt.num2date(data.datetime[0]), ticker, _interval, data.open[0], data.high[0], data.low[0], data.close[0], data.volume[0], _state, )) 70 | 71 | # signals to open position 72 | signal1 = self.crossover[ticker].lines.crossover[0] # signal 1 - intersection of a fast SMA from bottom to top of a slow SMA 73 | signal2 = self.underover_sma[ticker] # signal 2 - when SMA3 is below SMA2 74 | 75 | if not self.getposition(data): # If there is no position 76 | if signal1 == 1: 77 | if signal2 == 1: 78 | # buy 79 | free_money = self.broker.getcash() 80 | price = data.close[0] # by closing price 81 | size = (free_money * self.p.leverage / price) * 0.10 # 10% of available funds * leverage 82 | 83 | print("-"*50) 84 | print(f"\t - Let's buy {ticker}, size = {size} at price = {price}, depo: {self.broker.getcash()} {self.p.coin_target}") 85 | self.orders[ticker] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 86 | print(f"\t - Order has been submitted to buy {ticker}, size={size}, price={price}") 87 | print("-" * 50) 88 | 89 | else: # If there is a position 90 | if signal1 == 0 and signal2 == 0: 91 | # sell 92 | if self.orders[ticker]: 93 | print("-" * 50) 94 | print(f"\t - Let's sell by market {ticker}... size:{self.orders[ticker].size}") 95 | self.orders[ticker] = self.close(data=data) # Request to close a position at the market price 96 | print(f"\t - Order has been submitted to sell by market {ticker}") 97 | print("-" * 50) 98 | 99 | def notify_order(self, order): 100 | """Changing the status of the order""" 101 | ticker = order.data._name # Name of ticker from order 102 | self.log(f'Order number {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Buy" if order.isbuy() else "Sell"} {ticker} {order.size} @ {order.price}') 103 | if order.status == bt.Order.Completed: # If the order is fully executed 104 | if order.isbuy(): # The order to buy 105 | self.log(f'\t*** BUY ORDER COMPLETED for {ticker} price: {order.executed.price:.2f}, size = {order.size}, cost: {order.executed.value:.2f}, comm: {order.executed.comm:.2f}, depo={self.cerebro.broker.getcash():.2f} {self.p.coin_target}') 106 | else: # The order to sell 107 | self.log(f'\t*** SELL ORDER COMPLETED for {ticker} price: {order.executed.price:.2f}, size = {order.size}, cost: {order.executed.value:.2f}, comm: {order.executed.comm:.2f}, depo={self.cerebro.broker.getcash():.2f} {self.p.coin_target}') 108 | self.orders[ticker] = None # Reset the order to enter the position 109 | 110 | def notify_trade(self, trade): 111 | """Changing the position status""" 112 | if trade.isclosed: # If the position is closed 113 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, commission={abs(trade.pnl-trade.pnlcomm):.2f}') 114 | 115 | def log(self, txt, dt=None): 116 | """Print string with date to the console""" 117 | dt = bt.num2date(self.datas[0].datetime[0]) if dt is None else dt # date or date of the current bar 118 | print(f'{dt.strftime("%Y-%m-%d %H:%M:%S")} {txt}') # Print the date and time with the specified text to the console 119 | 120 | 121 | if __name__ == '__main__': 122 | cerebro = bt.Cerebro(quicknotify=True) 123 | 124 | money = 2000 # money 125 | leverage = 50.0 # margin leverage x50 126 | 127 | cerebro.broker.setcash(money) # Setting how much money 128 | cerebro.broker.addcommissioninfo(CommInfoFractional()) # set float size 129 | cerebro.broker.setcommission(commission=0.0015, # Set the commission - 0.15% ... divide by 100 to remove % 130 | leverage=leverage, ) # Set leverage 131 | 132 | coin_target = 'USDT' # the base ticker in which calculations will be performed 133 | symbol = 'BTC' + coin_target # the ticker by which we will receive data in the format 134 | symbol2 = 'ETH' + coin_target # the ticker by which we will receive data in the format 135 | 136 | store = BinanceStore( 137 | api_key=Config.BINANCE_API_KEY, 138 | api_secret=Config.BINANCE_API_SECRET, 139 | coin_target=coin_target, 140 | testnet=False) # Binance Storage 141 | 142 | # # live connection to Binance - for Offline comment these two lines 143 | # broker = store.getbroker() 144 | # cerebro.setbroker(broker) 145 | 146 | # ----------------------------------------------------------- 147 | # Attention! - Now it's Offline for testing strategies # 148 | # ----------------------------------------------------------- 149 | 150 | # # Historical 1-minute bars for 10 hours + new live bars / timeframe M1 151 | # timeframe = "M1" 152 | # from_date = dt.datetime.now() - dt.timedelta(minutes=60*10) 153 | # 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 154 | # # 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 155 | 156 | # Historical D1 bars for 365 days + new live bars / timeframe D1 157 | timeframe = "D1" 158 | from_date = dt.datetime.now() - dt.timedelta(days=365*3) 159 | 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 160 | 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 161 | 162 | cerebro.adddata(data) # Adding data 163 | cerebro.adddata(data2) # Adding data 164 | 165 | cerebro.addstrategy(SimpleSMAStrategy, coin_target=coin_target, timeframe=timeframe, leverage=leverage) # Adding a trading system 166 | 167 | # Add Finance metrics of quality our strategy on historical data (backtest and results) 168 | cerebro.addanalyzer(btanalyzers.SQN, _name='SQN') 169 | cerebro.addanalyzer(btanalyzers.VWR, _name='VWR', fund=True) 170 | cerebro.addanalyzer(btanalyzers.TimeDrawDown, _name='TDD', fund=True, timeframe=bt.TimeFrame.Days) 171 | cerebro.addanalyzer(btanalyzers.DrawDown, _name='DD', fund=True) 172 | cerebro.addanalyzer(btanalyzers.Returns, _name='R', fund=True, timeframe=bt.TimeFrame.Days) 173 | cerebro.addanalyzer(btanalyzers.AnnualReturn, _name='AR', ) 174 | cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='SR') 175 | cerebro.addanalyzer(btanalyzers.TradeAnalyzer, _name='TradeAnalyzer') 176 | 177 | result = cerebro.run() # Launching a trading system 178 | cerebro.plot() # Draw a chart 179 | 180 | print() 181 | print("$"*77) 182 | # Print the final cash amount 183 | print('Was money: %.2f' % money) 184 | print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Liquidation value of the portfolio 185 | print('Remaining available funds: %.2f' % cerebro.broker.getcash()) 186 | print('Assets in the amount of: %.2f' % (cerebro.broker.getvalue() - cerebro.broker.getcash())) 187 | print() 188 | p = (cerebro.broker.getvalue() / money - 1) * 100 189 | print(f"{money:.2f} ==> {cerebro.broker.getvalue():.2f} ==> +{p:.2f}%") 190 | print() 191 | print('SQN: ', result[0].analyzers.SQN.get_analysis()) 192 | print('VWR: ', result[0].analyzers.VWR.get_analysis()) 193 | print('TDD: ', result[0].analyzers.TDD.get_analysis()) 194 | print('DD: ', result[0].analyzers.DD.get_analysis()) 195 | print('AR: ', result[0].analyzers.AR.get_analysis()) 196 | print('Profitability: ', result[0].analyzers.R.get_analysis()) 197 | print("$" * 77) 198 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/01 - Live Trade - Just Buy and Sell.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class JustBuySellStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration - just buy and sell 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | def next(self): 23 | """Arrival of a new ticker candle""" 24 | for data in self.datas: # Running through all the requested bars of all tickers 25 | ticker = data._name 26 | status = data._state # 0 - Live data, 1 - History data, 2 - None 27 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 28 | 29 | if status in [0, 1]: 30 | if status: _state = "False - History data" 31 | else: _state = "True - Live data" 32 | 33 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 34 | bt.num2date(data.datetime[0]), 35 | data._name, 36 | _interval, # ticker timeframe 37 | data.open[0], 38 | data.high[0], 39 | data.low[0], 40 | data.close[0], 41 | data.volume[0], 42 | _state, 43 | )) 44 | 45 | if status == 0: # Live trade 46 | coin_target = self.p.coin_target 47 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 48 | # Very slow function! Because we are going through API to get those values... 49 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 50 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 51 | 52 | order = self.orders[data._name] # The order of ticker 53 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 54 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 55 | 56 | if not self.getposition(data): # If there is no position 57 | print("there is no position") 58 | 59 | # if we have order but don't get position -> then cancel it 60 | if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 61 | print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 62 | self.cancel(order) # then cancel it 63 | 64 | size = float(self.broker._store._min_order[ticker]) # min value to buy for BTC and ETH 65 | min_in_USDT = float(self.broker._store._min_order_in_target[ticker]) # min value to buy for BTC and ETH 66 | _close = data.close[0] 67 | # correct size to be minimum applicable volume 68 | if size * _close < min_in_USDT: size = min_in_USDT / _close 69 | size = float(self.broker._store.format_quantity(ticker, size)) 70 | 71 | # # Set Limit order 72 | # # Let's buy min value of ticker - min_order by price lower on 5% from current 73 | # price = float(self.broker._store.format_price(ticker, data.low[0] * 0.95)) # 5% lower than the min price 74 | # # correct size to be minimum applicable volume 75 | # if size * price < min_in_USDT: size = min_in_USDT / price 76 | # size = float(self.broker._store.format_quantity(ticker, size)) 77 | # 78 | # print(f" - buy {ticker} size = {size} (min_order) at price = {price}") 79 | # self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 80 | # print(f"\t - The Limit order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 81 | 82 | # Set Market order 83 | # Let's buy just a little amount by market price 84 | print(f" - buy {ticker} size = {size} (min_order) at Market price") 85 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Market, size=size) 86 | print(f"\t - The Market order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 87 | 88 | def notify_order(self, order): 89 | """Changing the status of the order""" 90 | order_data_name = order.data._name # Name of ticker from order 91 | 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}') 92 | if order.status == bt.Order.Completed: # If the order is fully executed 93 | if order.isbuy(): # The order to buy 94 | self.log(f'Buy {order_data_name} Price: {order.executed.price:.2f}, Value {order.executed.value:.2f} {self.p.coin_target}, Commission {order.executed.comm:.10f} {self.p.coin_target}') 95 | else: # The order to sell 96 | self.log(f'Sell {order_data_name} Price: {order.executed.price:.2f}, Value {order.executed.value:.2f} {self.p.coin_target}, Commission {order.executed.comm:.10f} {self.p.coin_target}') 97 | self.orders[order_data_name] = None # Reset the order to enter the position 98 | 99 | def notify_trade(self, trade): 100 | """Changing the position status""" 101 | if trade.isclosed: # If the position is closed 102 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 103 | 104 | def log(self, txt, dt=None): 105 | """Print string with date to the console""" 106 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 107 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 108 | 109 | 110 | if __name__ == '__main__': 111 | cerebro = bt.Cerebro(quicknotify=True) 112 | 113 | coin_target = 'USDT' # the base ticker in which calculations will be performed 114 | symbol = 'ETH' + coin_target # the ticker by which we will receive data in the format 115 | 116 | store = BinanceStore( 117 | api_key=Config.BINANCE_API_KEY, 118 | api_secret=Config.BINANCE_API_SECRET, 119 | coin_target=coin_target, 120 | testnet=False) # Binance Storage 121 | 122 | # live connection to Binance - for Offline comment these two lines 123 | broker = store.getbroker() 124 | cerebro.setbroker(broker) 125 | 126 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 127 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=5) 128 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 129 | 130 | cerebro.adddata(data) # Adding data 131 | 132 | cerebro.addstrategy(JustBuySellStrategy, coin_target=coin_target) # Adding a trading system 133 | 134 | cerebro.run() # Launching a trading system 135 | cerebro.plot() # Draw a chart 136 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/01 - Live Trade.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # Торговая система 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Демонстрация live стратегии с индикаторами SMA, RSI 11 | """ 12 | params = ( # Параметры торговой системы 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Инициализация, добавление индикаторов для каждого тикера""" 18 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 19 | for d in self.datas: # Пробегаемся по всем тикерам 20 | self.orders[d._name] = None # Заявки по тикеру пока нет 21 | 22 | # создаем индикаторы для каждого тикера 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Приход нового бара тикера""" 34 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # таймфрейм тикера 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # Заявка тикера 64 | if order and order.status == bt.Order.Submitted: # Если заявка не на бирже (отправлена брокеру) 65 | return # то ждем постановки заявки на бирже, выходим, дальше не продолжаем 66 | if not self.getposition(data): # Если позиции нет 67 | if order and order.status == bt.Order.Accepted: # Если заявка на бирже (принята брокером) 68 | print(f"\t - Снимаем заявку {order.binance_order['orderId']} на покупку {data._name}") 69 | self.cancel(order) # то снимаем ее 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "ETHUSDT": size = 0.007 74 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # На 5% ниже min цены 75 | print(f" - buy {ticker} size = {size} at price = {price}") 76 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 77 | print(f"\t - Выставлена заявка {self.orders[data._name].binance_order['orderId']} на покупку {data._name}") 78 | 79 | else: # Если позиция есть 80 | if self.rsi[ticker] > 75: 81 | print("sell") 82 | print(f"\t - Продаем по рынку {data._name}...") 83 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 84 | 85 | def notify_order(self, order): 86 | """Изменение статуса заявки""" 87 | order_data_name = order.data._name # Имя тикера из заявки 88 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 89 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 90 | if order.isbuy(): # Заявка на покупку 91 | self.log(f'Покупка {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 92 | else: # Заявка на продажу 93 | self.log(f'Продажа {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 94 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 95 | 96 | def notify_trade(self, trade): 97 | """Изменение статуса позиции""" 98 | if trade.isclosed: # Если позиция закрыта 99 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 100 | 101 | def log(self, txt, dt=None): 102 | """Вывод строки с датой на консоль""" 103 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 104 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 105 | 106 | 107 | if __name__ == '__main__': 108 | cerebro = bt.Cerebro(quicknotify=True) 109 | 110 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 111 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 112 | symbol2 = 'ETH' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 113 | 114 | store = BinanceStore( 115 | api_key=Config.BINANCE_API_KEY, 116 | api_secret=Config.BINANCE_API_SECRET, 117 | coin_target=coin_target, 118 | testnet=False) # Хранилище Binance 119 | 120 | # live подключение к Binance - для Offline закомментировать эти две строки 121 | broker = store.getbroker() 122 | cerebro.setbroker(broker) 123 | 124 | # Исторические 1-минутные бары за прошлый час + новые live бары / таймфрейм M1 125 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 126 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 127 | data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=True) 128 | 129 | cerebro.adddata(data) # Добавляем данные 130 | cerebro.adddata(data2) # Добавляем данные 131 | 132 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Добавляем торговую систему 133 | 134 | cerebro.run() # Запуск торговой системы 135 | cerebro.plot() # Рисуем график 136 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/02 - Live Trade MultiPortfolio.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # Торговая система 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Демонстрация live стратегии с индикаторами SMA, RSI 11 | """ 12 | params = ( # Параметры торговой системы 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Инициализация, добавление индикаторов для каждого тикера""" 18 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 19 | for d in self.datas: # Пробегаемся по всем тикерам 20 | self.orders[d._name] = None # Заявки по тикеру пока нет 21 | 22 | # создаем индикаторы для каждого тикера 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Приход нового бара тикера""" 34 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # таймфрейм тикера 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # Заявка тикера 64 | if order and order.status == bt.Order.Submitted: # Если заявка не на бирже (отправлена брокеру) 65 | return # то ждем постановки заявки на бирже, выходим, дальше не продолжаем 66 | if not self.getposition(data): # Если позиции нет 67 | if order and order.status == bt.Order.Accepted: # Если заявка на бирже (принята брокером) 68 | print(f"\t - Снимаем заявку {order.binance_order['orderId']} на покупку {data._name}") 69 | self.cancel(order) # то снимаем ее 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "ETHUSDT": size = 0.007 74 | if data._name == "BNBUSDT": size = 0.05 75 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # На 5% ниже min цены 76 | print(f" - buy {ticker} size = {size} at price = {price}") 77 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 78 | print(f"\t - Выставлена заявка {self.orders[data._name].binance_order['orderId']} на покупку {data._name}") 79 | 80 | else: # Если позиция есть 81 | if self.rsi[ticker] > 75: 82 | print("sell") 83 | print(f"\t - Продаем по рынку {data._name}...") 84 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 85 | 86 | def notify_order(self, order): 87 | """Изменение статуса заявки""" 88 | order_data_name = order.data._name # Имя тикера из заявки 89 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 90 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 91 | if order.isbuy(): # Заявка на покупку 92 | self.log(f'Покупка {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 93 | else: # Заявка на продажу 94 | self.log(f'Продажа {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 95 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 96 | 97 | def notify_trade(self, trade): 98 | """Изменение статуса позиции""" 99 | if trade.isclosed: # Если позиция закрыта 100 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 101 | 102 | def log(self, txt, dt=None): 103 | """Вывод строки с датой на консоль""" 104 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 105 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 106 | 107 | 108 | if __name__ == '__main__': 109 | cerebro = bt.Cerebro(quicknotify=True) 110 | 111 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 112 | symbols = ('BTC', 'ETH', 'BNB') # тикеры, по которым будем получать данные 113 | 114 | store = BinanceStore( 115 | api_key=Config.BINANCE_API_KEY, 116 | api_secret=Config.BINANCE_API_SECRET, 117 | coin_target=coin_target, 118 | testnet=False) # Хранилище Binance 119 | 120 | # live подключение к Binance - для Offline закомментировать эти две строки 121 | broker = store.getbroker() 122 | cerebro.setbroker(broker) 123 | 124 | for _symbol in symbols: # Пробегаемся по всем тикерам 125 | 126 | symbol = _symbol + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 127 | 128 | # Исторические 1-минутные бары за прошлый час + новые live бары / таймфрейм M1 129 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 130 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 131 | 132 | cerebro.adddata(data) # Добавляем данные 133 | 134 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Добавляем торговую систему 135 | 136 | cerebro.run() # Запуск торговой системы 137 | cerebro.plot() # Рисуем график 138 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/03 - Live Trade ETH.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # Торговая система 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Демонстрация live стратегии с индикаторами SMA, RSI 11 | """ 12 | params = ( # Параметры торговой системы 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Инициализация, добавление индикаторов для каждого тикера""" 18 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 19 | for d in self.datas: # Пробегаемся по всем тикерам 20 | self.orders[d._name] = None # Заявки по тикеру пока нет 21 | 22 | # создаем индикаторы для каждого тикера 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | def next(self): 33 | """Приход нового бара тикера""" 34 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 35 | ticker = data._name 36 | status = data._state # 0 - Live data, 1 - History data, 2 - None 37 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 38 | 39 | if status in [0, 1]: 40 | if status: _state = "False - History data" 41 | else: _state = "True - Live data" 42 | 43 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 44 | bt.num2date(data.datetime[0]), 45 | data._name, 46 | _interval, # таймфрейм тикера 47 | data.open[0], 48 | data.high[0], 49 | data.low[0], 50 | data.close[0], 51 | data.volume[0], 52 | _state, 53 | )) 54 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 55 | 56 | coin_target = self.p.coin_target 57 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 58 | 59 | # Very slow function! Because we are going through API to get those values... 60 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 61 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 62 | 63 | order = self.orders[data._name] # Заявка тикера 64 | if order and order.status == bt.Order.Submitted: # Если заявка не на бирже (отправлена брокеру) 65 | return # то ждем постановки заявки на бирже, выходим, дальше не продолжаем 66 | if not self.getposition(data): # Если позиции нет 67 | if order and order.status == bt.Order.Accepted: # Если заявка на бирже (принята брокером) 68 | print(f"\t - Снимаем заявку {order.binance_order['orderId']} на покупку {data._name}") 69 | self.cancel(order) # то снимаем ее 70 | 71 | if self.rsi[ticker] < 60: # Enter long 72 | size = 0.0007 # min value to buy for BTC and ETH 73 | if data._name == "XMRETH": size = 0.1 74 | if data._name == "BNBETH": size = 0.1 75 | price = self.broker._store.format_price(ticker, data.low[0] * 0.95) # На 5% ниже min цены 76 | print(f" - buy {ticker} size = {size} at price = {price}") 77 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 78 | print(f"\t - Выставлена заявка {self.orders[data._name].binance_order['orderId']} на покупку {data._name}") 79 | 80 | else: # Если позиция есть 81 | if self.rsi[ticker] > 75: 82 | print("sell") 83 | print(f"\t - Продаем по рынку {data._name}...") 84 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 85 | 86 | def notify_order(self, order): 87 | """Изменение статуса заявки""" 88 | order_data_name = order.data._name # Имя тикера из заявки 89 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 90 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 91 | if order.isbuy(): # Заявка на покупку 92 | self.log(f'Покупка {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 93 | else: # Заявка на продажу 94 | self.log(f'Продажа {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 95 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 96 | 97 | def notify_trade(self, trade): 98 | """Изменение статуса позиции""" 99 | if trade.isclosed: # Если позиция закрыта 100 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 101 | 102 | def log(self, txt, dt=None): 103 | """Вывод строки с датой на консоль""" 104 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 105 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 106 | 107 | 108 | if __name__ == '__main__': 109 | cerebro = bt.Cerebro(quicknotify=True) 110 | 111 | coin_target = 'ETH' # базовый тикер, в котором будут осуществляться расчеты 112 | symbol = 'BNB' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 113 | symbol2 = 'XMR' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 114 | 115 | store = BinanceStore( 116 | api_key=Config.BINANCE_API_KEY, 117 | api_secret=Config.BINANCE_API_SECRET, 118 | coin_target=coin_target, 119 | testnet=False) # Хранилище Binance 120 | 121 | # live подключение к Binance - для Offline закомментировать эти две строки 122 | broker = store.getbroker() 123 | cerebro.setbroker(broker) 124 | 125 | # Исторические 1-минутные бары за прошлый час + новые live бары / таймфрейм M1 126 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 127 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 128 | data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=True) 129 | 130 | cerebro.adddata(data) # Добавляем данные 131 | cerebro.adddata(data2) # Добавляем данные 132 | 133 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Добавляем торговую систему 134 | 135 | cerebro.run() # Запуск торговой системы 136 | cerebro.plot() # Рисуем график 137 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/04 - Offline Backtest.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # Торговая система 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Демонстрация live стратегии с индикаторами SMA, RSI 11 | """ 12 | params = ( # Параметры торговой системы 13 | ('coin_target', ''), 14 | ('timeframe', ''), 15 | ) 16 | 17 | def __init__(self): 18 | """Инициализация, добавление индикаторов для каждого тикера""" 19 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 20 | for d in self.datas: # Пробегаемся по всем тикерам 21 | self.orders[d._name] = None # Заявки по тикеру пока нет 22 | 23 | # создаем индикаторы для каждого тикера 24 | self.sma1 = {} 25 | self.sma2 = {} 26 | self.rsi = {} 27 | for i in range(len(self.datas)): 28 | ticker = list(self.dnames.keys())[i] # key name is ticker name 29 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 30 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 31 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 32 | 33 | def next(self): 34 | """Приход нового бара тикера""" 35 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 36 | ticker = data._name 37 | status = data._state # 0 - Live data, 1 - History data, 2 - None 38 | _interval = self.p.timeframe 39 | 40 | if status in [0, 1]: 41 | if status: _state = "False - History data" 42 | else: _state = "True - Live data" 43 | 44 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 45 | bt.num2date(data.datetime[0]), 46 | data._name, 47 | _interval, # таймфрейм тикера 48 | data.open[0], 49 | data.high[0], 50 | data.low[0], 51 | data.close[0], 52 | data.volume[0], 53 | _state, 54 | )) 55 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 56 | 57 | coin_target = self.p.coin_target 58 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 59 | 60 | # Very slow function! Because we are going through API to get those values... 61 | # symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 62 | # print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 63 | 64 | order = self.orders[data._name] # Заявка тикера 65 | if order and order.status == bt.Order.Submitted: # Если заявка не на бирже (отправлена брокеру) 66 | return # то ждем постановки заявки на бирже, выходим, дальше не продолжаем 67 | if not self.getposition(data): # Если позиции нет 68 | if order and order.status == bt.Order.Accepted: # Если заявка на бирже (принята брокером) 69 | print(f"\t - Снимаем заявку {order.p.tradeid} на покупку {data._name}") 70 | # self.cancel(order) # то снимаем ее 71 | 72 | if self.rsi[ticker] < 30: # Enter long 73 | size = 0.0005 # min value to buy for BTC and ETH 74 | if data._name == "ETHUSDT": size = 0.05 75 | 76 | price = data.close[0] # по цене закрытия 77 | 78 | print(f" - buy {ticker} size = {size} at price = {price}") 79 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 80 | print(f"\t - Выставлена заявка {self.orders[data._name].p.tradeid} на покупку {data._name}") 81 | 82 | else: # Если позиция есть 83 | if self.rsi[ticker] > 70: 84 | print("sell") 85 | print(f"\t - Продаем по рынку {data._name}...") 86 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 87 | 88 | def notify_order(self, order): 89 | """Изменение статуса заявки""" 90 | order_data_name = order.data._name # Имя тикера из заявки 91 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 92 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 93 | if order.isbuy(): # Заявка на покупку 94 | self.log(f'Покупка {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 95 | else: # Заявка на продажу 96 | self.log(f'Продажа {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 97 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 98 | 99 | def notify_trade(self, trade): 100 | """Изменение статуса позиции""" 101 | if trade.isclosed: # Если позиция закрыта 102 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 103 | 104 | def log(self, txt, dt=None): 105 | """Вывод строки с датой на консоль""" 106 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 107 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 108 | 109 | 110 | if __name__ == '__main__': 111 | cerebro = bt.Cerebro(quicknotify=True) 112 | 113 | cerebro.broker.setcash(200000) # Устанавливаем сколько денег 114 | cerebro.broker.setcommission(commission=0.0015) # Установить комиссию- 0.15% ... разделите на 100, чтобы удалить % 115 | 116 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 117 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 118 | symbol2 = 'ETH' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 119 | 120 | store = BinanceStore( 121 | api_key=Config.BINANCE_API_KEY, 122 | api_secret=Config.BINANCE_API_SECRET, 123 | coin_target=coin_target, 124 | testnet=False) # Хранилище Binance 125 | 126 | # # live подключение к Binance - для Offline закомментировать эти две строки 127 | # broker = store.getbroker() 128 | # cerebro.setbroker(broker) 129 | 130 | # ----------------------------------------------------------- 131 | # Внимание! - Теперь это Offline для тестирования стратегий # 132 | # ----------------------------------------------------------- 133 | 134 | # Исторические 1-минутные бары за 10 часов + новые live бары / таймфрейм M1 135 | timeframe = "M1" 136 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 137 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 138 | data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 139 | 140 | cerebro.adddata(data) # Добавляем данные 141 | cerebro.adddata(data2) # Добавляем данные 142 | 143 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Добавляем торговую систему 144 | 145 | cerebro.run() # Запуск торговой системы 146 | cerebro.plot() # Рисуем график 147 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/05 - Offline Backtest MultiPortfolio.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # Торговая система 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Демонстрация live стратегии с индикаторами SMA, RSI 11 | """ 12 | params = ( # Параметры торговой системы 13 | ('coin_target', ''), 14 | ('timeframe', ''), 15 | ) 16 | 17 | def __init__(self): 18 | """Инициализация, добавление индикаторов для каждого тикера""" 19 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 20 | for d in self.datas: # Пробегаемся по всем тикерам 21 | self.orders[d._name] = None # Заявки по тикеру пока нет 22 | 23 | # создаем индикаторы для каждого тикера 24 | self.sma1 = {} 25 | self.sma2 = {} 26 | self.rsi = {} 27 | for i in range(len(self.datas)): 28 | ticker = list(self.dnames.keys())[i] # key name is ticker name 29 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 30 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 31 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 32 | 33 | def next(self): 34 | """Приход нового бара тикера""" 35 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 36 | ticker = data._name 37 | status = data._state # 0 - Live data, 1 - History data, 2 - None 38 | _interval = self.p.timeframe 39 | 40 | if status in [0, 1]: 41 | if status: _state = "False - History data" 42 | else: _state = "True - Live data" 43 | 44 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 45 | bt.num2date(data.datetime[0]), 46 | data._name, 47 | _interval, # таймфрейм тикера 48 | data.open[0], 49 | data.high[0], 50 | data.low[0], 51 | data.close[0], 52 | data.volume[0], 53 | _state, 54 | )) 55 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 56 | 57 | coin_target = self.p.coin_target 58 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 59 | 60 | # Very slow function! Because we are going through API to get those values... 61 | # symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 62 | # print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 63 | 64 | order = self.orders[data._name] # Заявка тикера 65 | if order and order.status == bt.Order.Submitted: # Если заявка не на бирже (отправлена брокеру) 66 | return # то ждем постановки заявки на бирже, выходим, дальше не продолжаем 67 | if not self.getposition(data): # Если позиции нет 68 | if order and order.status == bt.Order.Accepted: # Если заявка на бирже (принята брокером) 69 | print(f"\t - Снимаем заявку {order.p.tradeid} на покупку {data._name}") 70 | # self.cancel(order) # то снимаем ее 71 | 72 | if self.rsi[ticker] < 30: # Enter long 73 | size = 0.0005 # min value to buy for BTC and ETH 74 | if data._name == "ETHUSDT": size = 0.05 75 | if data._name == "BNBUSDT": size = 0.01 76 | 77 | price = data.close[0] # по цене закрытия 78 | 79 | print(f" - buy {ticker} size = {size} at price = {price}") 80 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 81 | print(f"\t - Выставлена заявка {self.orders[data._name].p.tradeid} на покупку {data._name}") 82 | 83 | else: # Если позиция есть 84 | if self.rsi[ticker] > 70: 85 | print("sell") 86 | print(f"\t - Продаем по рынку {data._name}...") 87 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 88 | 89 | def notify_order(self, order): 90 | """Изменение статуса заявки""" 91 | order_data_name = order.data._name # Имя тикера из заявки 92 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 93 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 94 | if order.isbuy(): # Заявка на покупку 95 | self.log(f'Покупка {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 96 | else: # Заявка на продажу 97 | self.log(f'Продажа {order_data_name} @{order.executed.price:.2f}, Цена {order.executed.value:.2f}, Комиссия {order.executed.comm:.2f}') 98 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 99 | 100 | def notify_trade(self, trade): 101 | """Изменение статуса позиции""" 102 | if trade.isclosed: # Если позиция закрыта 103 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 104 | 105 | def log(self, txt, dt=None): 106 | """Вывод строки с датой на консоль""" 107 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 108 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 109 | 110 | 111 | if __name__ == '__main__': 112 | cerebro = bt.Cerebro(quicknotify=True) 113 | 114 | cerebro.broker.setcash(100000) # Устанавливаем сколько денег 115 | cerebro.broker.setcommission(commission=0.0015) # Установить комиссию- 0.15% ... разделите на 100, чтобы удалить % 116 | 117 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 118 | symbols = ('BTC', 'ETH', 'BNB') # тикеры, по которым будем получать данные 119 | 120 | store = BinanceStore( 121 | api_key=Config.BINANCE_API_KEY, 122 | api_secret=Config.BINANCE_API_SECRET, 123 | coin_target=coin_target, 124 | testnet=False) # Хранилище Binance 125 | 126 | # # live подключение к Binance - для Offline закомментировать эти две строки 127 | # broker = store.getbroker() 128 | # cerebro.setbroker(broker) 129 | 130 | # ----------------------------------------------------------- 131 | # Внимание! - Теперь это Offline для тестирования стратегий # 132 | # ----------------------------------------------------------- 133 | 134 | for _symbol in symbols: # Пробегаемся по всем тикерам 135 | 136 | symbol = _symbol + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 137 | 138 | # Исторические 1-минутные бары за 10 часов + новые live бары / таймфрейм M1 139 | timeframe = "M1" 140 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 141 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 142 | 143 | cerebro.adddata(data) # Добавляем данные 144 | 145 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Добавляем торговую систему 146 | 147 | cerebro.run() # Запуск торговой системы 148 | cerebro.plot() # Рисуем график 149 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/06 - Live Trade Just Buy and Close by Market.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Configuration file 5 | 6 | 7 | # Trading System 8 | class RSIStrategy(bt.Strategy): 9 | """ 10 | Live strategy demonstration with SMA, RSI indicators 11 | """ 12 | params = ( # Parameters of the trading system 13 | ('coin_target', ''), 14 | ) 15 | 16 | def __init__(self): 17 | """Initialization, adding indicators for each ticker""" 18 | self.orders = {} # All orders as a dict, for this particularly trading strategy one ticker is one order 19 | for d in self.datas: # Running through all the tickers 20 | self.orders[d._name] = None # There is no order for ticker yet 21 | 22 | # creating indicators for each ticker 23 | self.sma1 = {} 24 | self.sma2 = {} 25 | self.rsi = {} 26 | for i in range(len(self.datas)): 27 | ticker = list(self.dnames.keys())[i] # key name is ticker name 28 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=8) # SMA indicator 29 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=16) # SMA indicator 30 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=14) # RSI indicator 31 | 32 | self.buy_once = {} 33 | self.sell_once = {} 34 | 35 | def start(self): 36 | for d in self.datas: # Running through all the tickers 37 | self.buy_once[d._name] = False 38 | self.sell_once[d._name] = False 39 | 40 | def next(self): 41 | """Arrival of a new ticker candle""" 42 | for data in self.datas: # Running through all the requested bars of all tickers 43 | ticker = data._name 44 | status = data._state # 0 - Live data, 1 - History data, 2 - None 45 | _interval = self.broker._store.get_interval(data._timeframe, data._compression) 46 | 47 | if status in [0, 1]: 48 | if status: _state = "False - History data" 49 | else: _state = "True - Live data" 50 | 51 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 52 | bt.num2date(data.datetime[0]), 53 | data._name, 54 | _interval, # ticker timeframe 55 | data.open[0], 56 | data.high[0], 57 | data.low[0], 58 | data.close[0], 59 | data.volume[0], 60 | _state, 61 | )) 62 | print(f'\t - {ticker} RSI : {self.rsi[ticker][0]}') 63 | 64 | if status != 0: continue # if not live - do not enter to position! 65 | 66 | coin_target = self.p.coin_target 67 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 68 | 69 | # Very slow function! Because we are going through API to get those values... 70 | symbol_balance, short_symbol_name = self.broker._store.get_symbol_balance(ticker) 71 | print(f"\t - {ticker} current balance = {symbol_balance} {short_symbol_name}") 72 | 73 | order = self.orders[data._name] # The order of ticker 74 | if order and order.status == bt.Order.Submitted: # If the order is not on the exchange (sent to the broker) 75 | return # then we are waiting for the order to be placed on the exchange, we leave, we do not continue further 76 | if not self.getposition(data): # If there is no position 77 | # if order and order.status == bt.Order.Accepted: # If the order is on the exchange (accepted by the broker) 78 | # print(f"\t - Cancel the order {order.binance_order['orderId']} to buy {data._name}") 79 | # self.cancel(order) # then cancel it 80 | 81 | if not self.buy_once[ticker]: # Enter long 82 | size = 0.0007 # min value to buy for BTC and ETH 83 | if data._name == "ETHUSDT": size = 0.007 84 | price = self.broker._store.format_price(ticker, data.close[0] * 1) # buy at close price 85 | print(f" - buy {ticker} size = {size} at price = {price}") 86 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 87 | print(f"\t - The order has been submitted {self.orders[data._name].binance_order['orderId']} to buy {data._name}") 88 | 89 | self.buy_once[ticker] = len(self) # prevent from second buy... writing the number of bar 90 | 91 | else: # If there is a position 92 | print(self.sell_once[ticker], self.buy_once[ticker], len(self), len(self) > self.buy_once[ticker] + 3) 93 | if not self.sell_once[ticker]: # if we are selling first time 94 | if self.buy_once[ticker] and len(self) > self.buy_once[ticker] + 3: # if we have position sell after 3 bars by market 95 | print("sell") 96 | print(f"\t - Sell it by the market {data._name}...") 97 | self.orders[data._name] = self.close() # Request to close a position at the market price 98 | 99 | self.sell_once[ticker] = True # to prevent sell second time 100 | 101 | def notify_order(self, order): 102 | """Changing the status of the order""" 103 | order_data_name = order.data._name # Name of ticker from order 104 | 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}') 105 | if order.status == bt.Order.Completed: # If the order is fully executed 106 | if order.isbuy(): # The order to buy 107 | print(self.orders[data._name].binance_order) # order.executed.price, order.executed.value, order.executed.comm - you can get from here 108 | self.log(f'Buy {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 109 | else: # The order to sell 110 | print(self.orders[data._name].binance_order) # order.executed.price, order.executed.value, order.executed.comm - you can get from here 111 | self.log(f'Sell {order_data_name} @{order.executed.price:.2f}, Price {order.executed.value:.2f}, Commission {order.executed.comm:.2f}') 112 | self.orders[order_data_name] = None # Reset the order to enter the position - in case of linked buy 113 | # self.orders[order_data_name] = None # Reset the order to enter the position 114 | 115 | def notify_trade(self, trade): 116 | """Changing the position status""" 117 | if trade.isclosed: # If the position is closed 118 | self.log(f'Profit on a closed position {trade.getdataname()} Total={trade.pnl:.2f}, No commission={trade.pnlcomm:.2f}') 119 | 120 | def log(self, txt, dt=None): 121 | """Print string with date to the console""" 122 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # date or date of the current bar 123 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Print the date and time with the specified text to the console 124 | 125 | 126 | if __name__ == '__main__': 127 | cerebro = bt.Cerebro(quicknotify=True) 128 | 129 | coin_target = 'USDT' # the base ticker in which calculations will be performed 130 | symbol = 'ETH' + coin_target # the ticker by which we will receive data in the format 131 | 132 | store = BinanceStore( 133 | api_key=Config.BINANCE_API_KEY, 134 | api_secret=Config.BINANCE_API_SECRET, 135 | coin_target=coin_target, 136 | testnet=False) # Binance Storage 137 | 138 | # live connection to Binance - for Offline comment these two lines 139 | broker = store.getbroker() 140 | cerebro.setbroker(broker) 141 | 142 | # Historical 1-minute bars for the last hour + new live bars / timeframe M1 143 | from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60) 144 | data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=True) 145 | 146 | cerebro.adddata(data) # Adding data 147 | 148 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target) # Adding a trading system 149 | 150 | cerebro.run() # Launching a trading system 151 | cerebro.plot() # Draw a chart 152 | -------------------------------------------------------------------------------- /StrategyExamplesBinance_ru/07 - Offline Backtest Indicators.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import backtrader as bt 3 | from backtrader_binance import BinanceStore 4 | from ConfigBinance.Config import Config # Файл конфигурации 5 | 6 | 7 | # видео по созданию этой стратегии 8 | # RuTube: https://rutube.ru/video/417e306e6b5d6351d74bd9cd4d6af051/ 9 | # YouTube: https://youtube.com/live/k82vabGva7s 10 | 11 | class UnderOver(bt.Indicator): 12 | lines = ('underover',) 13 | params = dict(data2=20) 14 | plotinfo = dict(plot=True) 15 | 16 | def __init__(self): 17 | self.l.underover = self.data < self.p.data2 # данные под data2 == 1 18 | 19 | 20 | # Торговая система 21 | class RSIStrategy(bt.Strategy): 22 | """ 23 | Демонстрация live стратегии с индикаторами SMA, RSI 24 | """ 25 | params = ( # Параметры торговой системы 26 | ('coin_target', ''), 27 | ('timeframe', ''), 28 | ) 29 | 30 | def __init__(self): 31 | """Инициализация, добавление индикаторов для каждого тикера""" 32 | self.orders = {} # Организовываем заявки в виде справочника, конкретно для этой стратегии один тикер - одна активная заявка 33 | for d in self.datas: # Пробегаемся по всем тикерам 34 | self.orders[d._name] = None # Заявки по тикеру пока нет 35 | 36 | # создаем индикаторы для каждого тикера 37 | self.sma1 = {} 38 | self.sma2 = {} 39 | self.sma3 = {} 40 | self.crossover = {} 41 | self.underover_sma = {} 42 | self.rsi = {} 43 | self.underover_rsi = {} 44 | for i in range(len(self.datas)): 45 | ticker = list(self.dnames.keys())[i] # key name is ticker name 46 | self.sma1[ticker] = bt.indicators.SMA(self.datas[i], period=9) # SMA1 indicator 47 | self.sma2[ticker] = bt.indicators.SMA(self.datas[i], period=30) # SMA2 indicator 48 | self.sma3[ticker] = bt.indicators.SMA(self.datas[i], period=60) # SMA3 indicator 49 | 50 | # signal 1 - пересечение быстрой SMA снизу вверх медленной SMA 51 | self.crossover[ticker] = bt.ind.CrossOver(self.sma1[ticker], self.sma2[ticker]) # crossover SMA1 and SMA2 52 | 53 | # signal 2 - когда SMA3 находится ниже SMA2 54 | self.underover_sma[ticker] = UnderOver(self.sma3[ticker].lines.sma, data2=self.sma2[ticker].lines.sma) 55 | 56 | self.rsi[ticker] = bt.indicators.RSI(self.datas[i], period=20) # RSI indicator 57 | 58 | # signal 3 - когда RSI находится ниже 30 59 | self.underover_rsi[ticker] = UnderOver(self.rsi[ticker].lines.rsi, data2=30) 60 | 61 | def next(self): 62 | """Приход нового бара тикера""" 63 | for data in self.datas: # Пробегаемся по всем запрошенным барам всех тикеров 64 | ticker = data._name 65 | status = data._state # 0 - Live data, 1 - History data, 2 - None 66 | _interval = self.p.timeframe 67 | 68 | if status in [0, 1]: 69 | if status: _state = "False - History data" 70 | else: _state = "True - Live data" 71 | 72 | print('{} / {} [{}] - Open: {}, High: {}, Low: {}, Close: {}, Volume: {} - Live: {}'.format( 73 | bt.num2date(data.datetime[0]), 74 | data._name, 75 | _interval, # таймфрейм тикера 76 | data.open[0], 77 | data.high[0], 78 | data.low[0], 79 | data.close[0], 80 | data.volume[0], 81 | _state, 82 | )) 83 | print(f'\t - RSI =', self.rsi[ticker][0]) 84 | print(f"\t - crossover =", self.crossover[ticker].lines.crossover[0]) 85 | 86 | coin_target = self.p.coin_target 87 | print(f"\t - Free balance: {self.broker.getcash()} {coin_target}") 88 | 89 | # сигналы на вход 90 | signal1 = self.crossover[ticker].lines.crossover[0] # signal 1 - пересечение быстрой SMA снизу вверх медленной SMA 91 | signal2 = self.underover_sma[ticker] # signal 2 - когда SMA3 находится ниже SMA2 92 | 93 | # сигналы на выход 94 | signal3 = self.underover_rsi[ticker] # signal 3 - когда RSI находится ниже 30 95 | 96 | if not self.getposition(data): # Если позиции нет 97 | if signal1 == 1: 98 | if signal2 == 1: 99 | # buy 100 | free_money = self.broker.getcash() 101 | price = data.close[0] # по цене закрытия 102 | size = (free_money / price) * 0.25 # 25% от доступных средств 103 | print("-"*50) 104 | print(f"\t - buy {ticker} size = {size} at price = {price}") 105 | self.orders[data._name] = self.buy(data=data, exectype=bt.Order.Limit, price=price, size=size) 106 | print(f"\t - Выставлена заявка {self.orders[data._name].p.tradeid} на покупку {data._name}") 107 | print("-" * 50) 108 | 109 | else: # Если позиция есть 110 | if signal3 == 1: 111 | # sell 112 | print("-" * 50) 113 | print(f"\t - Продаем по рынку {data._name}...") 114 | self.orders[data._name] = self.close() # Заявка на закрытие позиции по рыночной цене 115 | print("-" * 50) 116 | 117 | def notify_order(self, order): 118 | """Изменение статуса заявки""" 119 | order_data_name = order.data._name # Имя тикера из заявки 120 | print("*"*50) 121 | self.log(f'Заявка номер {order.ref} {order.info["order_number"]} {order.getstatusname()} {"Покупка" if order.isbuy() else "Продажа"} {order_data_name} {order.size} @ {order.price}') 122 | if order.status == bt.Order.Completed: # Если заявка полностью исполнена 123 | if order.isbuy(): # Заявка на покупку 124 | self.log(f'Покупка {order_data_name} Цена: {order.executed.price:.2f}, Объём: {order.executed.value:.2f}, Комиссия: {order.executed.comm:.2f}') 125 | else: # Заявка на продажу 126 | self.log(f'Продажа {order_data_name} Цена: {order.executed.price:.2f}, Объём: {order.executed.value:.2f}, Комиссия: {order.executed.comm:.2f}') 127 | self.orders[order_data_name] = None # Сбрасываем заявку на вход в позицию 128 | print("*" * 50) 129 | 130 | def notify_trade(self, trade): 131 | """Изменение статуса позиции""" 132 | if trade.isclosed: # Если позиция закрыта 133 | self.log(f'Прибыль по закрытой позиции {trade.getdataname()} Общая={trade.pnl:.2f}, Без комиссии={trade.pnlcomm:.2f}') 134 | 135 | def log(self, txt, dt=None): 136 | """Вывод строки с датой на консоль""" 137 | dt = bt.num2date(self.datas[0].datetime[0]) if not dt else dt # Заданная дата или дата текущего бара 138 | print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль 139 | 140 | 141 | if __name__ == '__main__': 142 | cerebro = bt.Cerebro(quicknotify=True) 143 | 144 | cerebro.broker.setcash(2000) # Устанавливаем сколько денег 145 | cerebro.broker.setcommission(commission=0.0015) # Установить комиссию- 0.15% ... разделите на 100, чтобы удалить % 146 | 147 | coin_target = 'USDT' # базовый тикер, в котором будут осуществляться расчеты 148 | symbol = 'BTC' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 149 | symbol2 = 'ETH' + coin_target # тикер, по которому будем получать данные в формате <КодТикераБазовыйТикер> 150 | 151 | store = BinanceStore( 152 | api_key=Config.BINANCE_API_KEY, 153 | api_secret=Config.BINANCE_API_SECRET, 154 | coin_target=coin_target, 155 | testnet=False) # Хранилище Binance 156 | 157 | # # live подключение к Binance - для Offline закомментировать эти две строки 158 | # broker = store.getbroker() 159 | # cerebro.setbroker(broker) 160 | 161 | # ----------------------------------------------------------- 162 | # Внимание! - Теперь это Offline для тестирования стратегий # 163 | # ----------------------------------------------------------- 164 | 165 | # # Исторические 1-минутные бары за 10 часов + новые live бары / таймфрейм M1 166 | # timeframe = "M1" 167 | # from_date = dt.datetime.utcnow() - dt.timedelta(minutes=60*10) 168 | # data = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 169 | # # data2 = store.getdata(timeframe=bt.TimeFrame.Minutes, compression=1, dataname=symbol2, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 170 | 171 | # Исторические D1 бары за 365 дней + новые live бары / таймфрейм D1 172 | timeframe = "D1" 173 | from_date = dt.datetime.utcnow() - dt.timedelta(days=365*3) 174 | data = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 175 | data2 = store.getdata(timeframe=bt.TimeFrame.Days, compression=1, dataname=symbol2, start_date=from_date, LiveBars=False) # поставьте здесь True - если нужно получать live бары 176 | 177 | cerebro.adddata(data) # Добавляем данные 178 | cerebro.adddata(data2) # Добавляем данные 179 | 180 | cerebro.addstrategy(RSIStrategy, coin_target=coin_target, timeframe=timeframe) # Добавляем торговую систему 181 | 182 | cerebro.run() # Запуск торговой системы 183 | cerebro.plot() # Рисуем график 184 | 185 | print() 186 | print("$"*77) 187 | print(f"Ликвидационная стоимость портфеля: {cerebro.broker.getvalue()}") # Ликвидационная стоимость портфеля 188 | print(f"Остаток свободных средств: {cerebro.broker.getcash()}") # Остаток свободных средств 189 | print("$" * 77) 190 | -------------------------------------------------------------------------------- /backtrader_binance/__init__.py: -------------------------------------------------------------------------------- 1 | from .binance_store import BinanceStore 2 | -------------------------------------------------------------------------------- /backtrader_binance/binance_broker.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | 3 | from collections import defaultdict, deque 4 | from math import copysign 5 | 6 | from backtrader.broker import BrokerBase 7 | from backtrader.order import Order, OrderBase 8 | from backtrader.position import Position 9 | from binance.enums import * 10 | 11 | 12 | class BinanceOrder(OrderBase): 13 | def __init__(self, owner, data, exectype, binance_order): 14 | self.owner = owner 15 | self.data = data 16 | self.exectype = exectype 17 | self.ordtype = self.Buy if binance_order['side'] == SIDE_BUY else self.Sell 18 | 19 | # Market order price is zero 20 | if self.exectype == Order.Market: 21 | self.size = float(binance_order['executedQty']) 22 | self.price = sum(float(fill['price']) for fill in binance_order['fills']) / len(binance_order['fills']) # Average price 23 | else: 24 | self.size = float(binance_order['origQty']) 25 | self.price = float(binance_order['price']) 26 | self.binance_order = binance_order 27 | 28 | super(BinanceOrder, self).__init__() 29 | self.accept() 30 | 31 | 32 | class BinanceBroker(BrokerBase): 33 | _ORDER_TYPES = { 34 | Order.Limit: ORDER_TYPE_LIMIT, 35 | Order.Market: ORDER_TYPE_MARKET, 36 | Order.Stop: ORDER_TYPE_STOP_LOSS, 37 | Order.StopLimit: ORDER_TYPE_STOP_LOSS_LIMIT, 38 | } 39 | 40 | def __init__(self, store): 41 | super(BinanceBroker, self).__init__() 42 | 43 | self.notifs = deque() 44 | self.positions = defaultdict(Position) 45 | 46 | self.startingcash = self.cash = 0 # Стартовые и текущие свободные средства по счету 47 | self.startingvalue = self.value = 0 # Стартовая и текущая стоимость позиций 48 | 49 | self.open_orders = list() 50 | 51 | self._store = store 52 | self._store.binance_socket.start_user_socket(self._handle_user_socket_message) 53 | 54 | def start(self): 55 | self.startingcash = self.cash = self.getcash() # Стартовые и текущие свободные средства по счету. Подписка на позиции для портфеля/биржи 56 | self.startingvalue = self.value = self.getvalue() # Стартовая и текущая стоимость позиций 57 | 58 | def _execute_order(self, order, date, executed_size, executed_price, executed_value, executed_comm): 59 | order.execute( 60 | date, 61 | executed_size, 62 | executed_price, 63 | 0, executed_value, executed_comm, 64 | 0, 0.0, 0.0, 65 | 0.0, 0.0, 66 | 0, 0.0) 67 | pos = self.getposition(order.data, clone=False) 68 | pos.update(copysign(executed_size, order.size), executed_price) 69 | 70 | def _handle_user_socket_message(self, msg): 71 | """https://binance-docs.github.io/apidocs/spot/en/#payload-order-update""" 72 | # print(msg) 73 | # {'e': 'executionReport', 'E': 1707120960762, 's': 'ETHUSDT', 'c': 'oVoRofmTTXJCqnGNuvcuEu', 'S': 'BUY', 'o': 'MARKET', 'f': 'GTC', 'q': '0.00220000', 'p': '0.00000000', 'P': '0.00000000', 'F': '0.00000000', 'g': -1, 'C': '', 'x': 'NEW', 'X': 'NEW', 'r': 'NONE', 'i': 15859894465, 'l': '0.00000000', 'z': '0.00000000', 'L': '0.00000000', 'n': '0', 'N': None, 'T': 1707120960761, 't': -1, 'I': 33028455024, 'w': True, 'm': False, 'M': False, 'O': 1707120960761, 'Z': '0.00000000', 'Y': '0.00000000', 'Q': '0.00000000', 'W': 1707120960761, 'V': 'EXPIRE_MAKER'} 74 | 75 | # {'e': 'executionReport', 'E': 1707120960762, 's': 'ETHUSDT', 'c': 'oVoRofmTTXJCqnGNuvcuEu', 'S': 'BUY', 'o': 'MARKET', 'f': 'GTC', 'q': '0.00220000', 'p': '0.00000000', 'P': '0.00000000', 'F': '0.00000000', 'g': -1, 'C': '', 76 | # 'x': 'TRADE', 'X': 'FILLED', 'r': 'NONE', 'i': 15859894465, 'l': '0.00220000', 'z': '0.00220000', 'L': '2319.53000000', 'n': '0.00000220', 'N': 'ETH', 'T': 1707120960761, 't': 1297224255, 'I': 33028455025, 'w': False, 77 | # 'm': False, 'M': True, 'O': 1707120960761, 'Z': '5.10296600', 'Y': '5.10296600', 'Q': '0.00000000', 'W': 1707120960761, 'V': 'EXPIRE_MAKER'} 78 | if msg['e'] == 'executionReport': 79 | if msg['s'] in self._store.symbols: 80 | for o in self.open_orders: 81 | if o.binance_order['orderId'] == msg['i']: 82 | if msg['X'] in [ORDER_STATUS_FILLED, ORDER_STATUS_PARTIALLY_FILLED]: 83 | _dt = dt.datetime.fromtimestamp(int(msg['T']) / 1000) 84 | executed_size = float(msg['l']) 85 | executed_price = float(msg['L']) 86 | executed_value = float(msg['Z']) 87 | executed_comm = float(msg['n']) 88 | # print(_dt, executed_size, executed_price) 89 | self._execute_order(o, _dt, executed_size, executed_price, executed_value, executed_comm) 90 | self._set_order_status(o, msg['X']) 91 | 92 | if o.status not in [Order.Accepted, Order.Partial]: 93 | self.open_orders.remove(o) 94 | self.notify(o) 95 | elif msg['e'] == 'error': 96 | raise msg 97 | 98 | def _set_order_status(self, order, binance_order_status): 99 | if binance_order_status == ORDER_STATUS_CANCELED: 100 | order.cancel() 101 | elif binance_order_status == ORDER_STATUS_EXPIRED: 102 | order.expire() 103 | elif binance_order_status == ORDER_STATUS_FILLED: 104 | order.completed() 105 | elif binance_order_status == ORDER_STATUS_PARTIALLY_FILLED: 106 | order.partial() 107 | elif binance_order_status == ORDER_STATUS_REJECTED: 108 | order.reject() 109 | 110 | def _submit(self, owner, data, side, exectype, size, price): 111 | type = self._ORDER_TYPES.get(exectype, ORDER_TYPE_MARKET) 112 | symbol = data._name 113 | binance_order = self._store.create_order(symbol, side, type, size, price) 114 | # print(1111, binance_order) 115 | # 1111 {'symbol': 'ETHUSDT', 'orderId': 15860400971, 'orderListId': -1, 'clientOrderId': 'EO7lLPcYNZR8cNEg8AOEPb', 'transactTime': 1707124560731, 'price': '0.00000000', 'origQty': '0.00220000', 'executedQty': '0.00220000', 'cummulativeQuoteQty': '5.10356000', 'status': 'FILLED', 'timeInForce': 'GTC', 'type': 'MARKET', 'side': 'BUY', 'workingTime': 1707124560731, 'fills': [{'price': '2319.80000000', 'qty': '0.00220000', 'commission': '0.00000220', 'commissionAsset': 'ETH', 'tradeId': 1297261843}], 'selfTradePreventionMode': 'EXPIRE_MAKER'} 116 | order = BinanceOrder(owner, data, exectype, binance_order) 117 | if binance_order['status'] in [ORDER_STATUS_FILLED, ORDER_STATUS_PARTIALLY_FILLED]: 118 | avg_price =0.0 119 | comm = 0.0 120 | for f in binance_order['fills']: 121 | comm += float(f['commission']) 122 | avg_price += float(f['price']) 123 | avg_price = self._store.format_price(symbol, avg_price/len(binance_order['fills'])) 124 | self._execute_order( 125 | order, 126 | dt.datetime.fromtimestamp(binance_order['transactTime'] / 1000), 127 | float(binance_order['executedQty']), 128 | float(avg_price), 129 | float(binance_order['cummulativeQuoteQty']), 130 | float(comm)) 131 | self._set_order_status(order, binance_order['status']) 132 | if order.status == Order.Accepted: 133 | self.open_orders.append(order) 134 | self.notify(order) 135 | return order 136 | 137 | def buy(self, owner, data, size, price=None, plimit=None, 138 | exectype=None, valid=None, tradeid=0, oco=None, 139 | trailamount=None, trailpercent=None, 140 | **kwargs): 141 | return self._submit(owner, data, SIDE_BUY, exectype, size, price) 142 | 143 | def cancel(self, order): 144 | order_id = order.binance_order['orderId'] 145 | symbol = order.binance_order['symbol'] 146 | self._store.cancel_order(symbol=symbol, order_id=order_id) 147 | 148 | def format_price(self, value): 149 | return self._store.format_price(value) 150 | 151 | def get_asset_balance(self, asset): 152 | return self._store.get_asset_balance(asset) 153 | 154 | def getcash(self): 155 | self.cash = self._store._cash 156 | return self.cash 157 | 158 | def get_notification(self): 159 | if not self.notifs: 160 | return None 161 | 162 | return self.notifs.popleft() 163 | 164 | def getposition(self, data, clone=True): 165 | pos = self.positions[data._dataname] 166 | if clone: 167 | pos = pos.clone() 168 | return pos 169 | 170 | def getvalue(self, datas=None): 171 | self.value = self._store._value 172 | return self.value 173 | 174 | def notify(self, order): 175 | self.notifs.append(order) 176 | 177 | def sell(self, owner, data, size, price=None, plimit=None, 178 | exectype=None, valid=None, tradeid=0, oco=None, 179 | trailamount=None, trailpercent=None, 180 | **kwargs): 181 | return self._submit(owner, data, SIDE_SELL, exectype, size, price) 182 | -------------------------------------------------------------------------------- /backtrader_binance/binance_feed.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | import pandas as pd 4 | 5 | from backtrader.feed import DataBase 6 | from backtrader.utils import date2num 7 | 8 | from backtrader import TimeFrame as tf 9 | 10 | 11 | class BinanceData(DataBase): 12 | params = ( 13 | ('drop_newest', True), 14 | ) 15 | 16 | # States for the Finite State Machine in _load 17 | _ST_LIVE, _ST_HISTORBACK, _ST_OVER = range(3) 18 | 19 | def __init__(self, store, **kwargs): # def __init__(self, store, timeframe, compression, start_date, LiveBars): 20 | # default values 21 | self.timeframe = tf.Minutes 22 | self.compression = 1 23 | self.start_date = None 24 | self.LiveBars = None 25 | 26 | self.symbol = self.p.dataname 27 | 28 | if hasattr(self.p, 'timeframe'): self.timeframe = self.p.timeframe 29 | if hasattr(self.p, 'compression'): self.compression = self.p.compression 30 | if 'start_date' in kwargs: self.start_date = kwargs['start_date'] 31 | if 'LiveBars' in kwargs: self.LiveBars = kwargs['LiveBars'] 32 | 33 | self._store = store 34 | self._data = deque() 35 | 36 | # print("Ok", self.timeframe, self.compression, self.start_date, self._store, self.LiveBars, self.symbol) 37 | 38 | def _handle_kline_socket_message(self, msg): 39 | """https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-streams""" 40 | if msg['e'] == 'kline': 41 | if msg['k']['x']: # Is closed 42 | kline = self._parser_to_kline(msg['k']['t'], msg['k']) 43 | self._data.extend(kline.values.tolist()) 44 | elif msg['e'] == 'error': 45 | raise msg 46 | 47 | def _load(self): 48 | if self._state == self._ST_OVER: 49 | return False 50 | elif self._state == self._ST_LIVE: 51 | return self._load_kline() 52 | elif self._state == self._ST_HISTORBACK: 53 | if self._load_kline(): 54 | return True 55 | else: 56 | self._start_live() 57 | 58 | def _load_kline(self): 59 | try: 60 | kline = self._data.popleft() 61 | except IndexError: 62 | return None 63 | 64 | timestamp, open_, high, low, close, volume = kline 65 | 66 | self.lines.datetime[0] = date2num(timestamp) 67 | self.lines.open[0] = open_ 68 | self.lines.high[0] = high 69 | self.lines.low[0] = low 70 | self.lines.close[0] = close 71 | self.lines.volume[0] = volume 72 | return True 73 | 74 | def _parser_dataframe(self, data): 75 | df = data.copy() 76 | df.columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume'] 77 | df['timestamp'] = df['timestamp'].values.astype(dtype='datetime64[ms]') 78 | df['open'] = df['open'].values.astype(float) 79 | df['high'] = df['high'].values.astype(float) 80 | df['low'] = df['low'].values.astype(float) 81 | df['close'] = df['close'].values.astype(float) 82 | df['volume'] = df['volume'].values.astype(float) 83 | # df.set_index('timestamp', inplace=True) 84 | return df 85 | 86 | def _parser_to_kline(self, timestamp, kline): 87 | df = pd.DataFrame([[timestamp, kline['o'], kline['h'], 88 | kline['l'], kline['c'], kline['v']]]) 89 | return self._parser_dataframe(df) 90 | 91 | def _start_live(self): 92 | # if live mode 93 | if self.LiveBars: 94 | self._state = self._ST_LIVE 95 | self.put_notification(self.LIVE) 96 | 97 | print(f"Live started for ticker: {self.symbol}") 98 | 99 | self._store.binance_socket.start_kline_socket( 100 | self._handle_kline_socket_message, 101 | self.symbol_info['symbol'], 102 | self.interval) 103 | else: 104 | self._state = self._ST_OVER 105 | 106 | def haslivedata(self): 107 | return self._state == self._ST_LIVE and self._data 108 | 109 | def islive(self): 110 | return True 111 | 112 | def start(self): 113 | DataBase.start(self) 114 | 115 | self.interval = self._store.get_interval(self.timeframe, self.compression) 116 | if self.interval is None: 117 | self._state = self._ST_OVER 118 | self.put_notification(self.NOTSUPPORTED_TF) 119 | return 120 | 121 | self.symbol_info = self._store.get_symbol_info(self.symbol) 122 | if self.symbol_info is None: 123 | self._state = self._ST_OVER 124 | self.put_notification(self.NOTSUBSCRIBED) 125 | return 126 | 127 | if self.start_date: 128 | self._state = self._ST_HISTORBACK 129 | self.put_notification(self.DELAYED) 130 | 131 | klines = self._store.binance.get_historical_klines( 132 | self.symbol_info['symbol'], 133 | self.interval, 134 | self.start_date.strftime('%d %b %Y %H:%M:%S')) 135 | 136 | try: 137 | if self.p.drop_newest: 138 | klines.pop() 139 | 140 | df = pd.DataFrame(klines) 141 | df.drop(df.columns[[6, 7, 8, 9, 10, 11]], axis=1, inplace=True) # Remove unnecessary columns 142 | df = self._parser_dataframe(df) 143 | self._data.extend(df.values.tolist()) 144 | except Exception as e: 145 | print("Exception (try set start_date in utc format):", e) 146 | 147 | else: 148 | self._start_live() 149 | -------------------------------------------------------------------------------- /backtrader_binance/binance_store.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from functools import wraps 4 | from math import floor 5 | 6 | from backtrader.dataseries import TimeFrame 7 | from binance import Client, ThreadedWebsocketManager 8 | from binance.enums import * 9 | from binance.exceptions import BinanceAPIException 10 | from requests.exceptions import ConnectTimeout, ConnectionError 11 | 12 | from .binance_broker import BinanceBroker 13 | from .binance_feed import BinanceData 14 | 15 | 16 | class BinanceStore(object): 17 | _GRANULARITIES = { 18 | (TimeFrame.Minutes, 1): KLINE_INTERVAL_1MINUTE, 19 | (TimeFrame.Minutes, 3): KLINE_INTERVAL_3MINUTE, 20 | (TimeFrame.Minutes, 5): KLINE_INTERVAL_5MINUTE, 21 | (TimeFrame.Minutes, 15): KLINE_INTERVAL_15MINUTE, 22 | (TimeFrame.Minutes, 30): KLINE_INTERVAL_30MINUTE, 23 | (TimeFrame.Minutes, 60): KLINE_INTERVAL_1HOUR, 24 | (TimeFrame.Minutes, 120): KLINE_INTERVAL_2HOUR, 25 | (TimeFrame.Minutes, 240): KLINE_INTERVAL_4HOUR, 26 | (TimeFrame.Minutes, 360): KLINE_INTERVAL_6HOUR, 27 | (TimeFrame.Minutes, 480): KLINE_INTERVAL_8HOUR, 28 | (TimeFrame.Minutes, 720): KLINE_INTERVAL_12HOUR, 29 | (TimeFrame.Days, 1): KLINE_INTERVAL_1DAY, 30 | (TimeFrame.Days, 3): KLINE_INTERVAL_3DAY, 31 | (TimeFrame.Weeks, 1): KLINE_INTERVAL_1WEEK, 32 | (TimeFrame.Months, 1): KLINE_INTERVAL_1MONTH, 33 | } 34 | 35 | def __init__(self, api_key, api_secret, coin_target, testnet=False, retries=5, tld='com'): # coin_refer, coin_target 36 | self.binance = Client(api_key, api_secret, testnet=testnet, tld=tld) 37 | self.binance_socket = ThreadedWebsocketManager(api_key, api_secret, testnet=testnet) 38 | self.binance_socket.daemon = True 39 | self.binance_socket.start() 40 | # self.coin_refer = coin_refer 41 | self.coin_target = coin_target # USDT 42 | # self.symbol = coin_refer + coin_target 43 | self.symbols = [] # symbols 44 | self.retries = retries 45 | 46 | self._cash = 0 47 | self._value = 0 48 | self.get_balance() 49 | 50 | self._step_size = {} 51 | self._min_order = {} 52 | self._min_order_in_target = {} 53 | self._tick_size = {} 54 | 55 | self._broker = BinanceBroker(store=self) 56 | self._data = None 57 | self._datas = {} 58 | 59 | def _format_value(self, value, step): 60 | precision = step.find('1') - 1 61 | if precision > 0: 62 | return '{:0.0{}f}'.format(float(value), precision) 63 | return floor(int(value)) 64 | 65 | def retry(func): 66 | @wraps(func) 67 | def wrapper(self, *args, **kwargs): 68 | for attempt in range(1, self.retries + 1): 69 | time.sleep(60 / 1200) # API Rate Limit 70 | try: 71 | return func(self, *args, **kwargs) 72 | except (BinanceAPIException, ConnectTimeout, ConnectionError) as err: 73 | if isinstance(err, BinanceAPIException) and err.code == -1021: 74 | # Recalculate timestamp offset between local and Binance's server 75 | res = self.binance.get_server_time() 76 | self.binance.timestamp_offset = res['serverTime'] - int(time.time() * 1000) 77 | 78 | if attempt == self.retries: 79 | raise 80 | return wrapper 81 | 82 | @retry 83 | def cancel_open_orders(self, symbol): 84 | orders = self.binance.get_open_orders(symbol=symbol) 85 | if len(orders) > 0: 86 | self.binance._request_api('delete', 'openOrders', signed=True, data={ 'symbol': symbol }) 87 | 88 | @retry 89 | def cancel_order(self, symbol, order_id): 90 | try: 91 | self.binance.cancel_order(symbol=symbol, orderId=order_id) 92 | except BinanceAPIException as api_err: 93 | if api_err.code == -2011: # Order filled 94 | return 95 | else: 96 | raise api_err 97 | except Exception as err: 98 | raise err 99 | 100 | @retry 101 | def create_order(self, symbol, side, type, size, price): 102 | params = dict() 103 | if type in [ORDER_TYPE_LIMIT, ORDER_TYPE_STOP_LOSS_LIMIT]: 104 | params.update({ 105 | 'timeInForce': TIME_IN_FORCE_GTC 106 | }) 107 | if type == ORDER_TYPE_STOP_LOSS: 108 | params.update({ 109 | 'stopPrice': self.format_price(symbol, price) 110 | }) 111 | elif type != ORDER_TYPE_MARKET: 112 | params.update({ 113 | 'price': self.format_price(symbol, price) 114 | }) 115 | 116 | return self.binance.create_order( 117 | symbol=symbol, 118 | side=side, 119 | type=type, 120 | quantity=self.format_quantity(symbol, size), 121 | newOrderRespType='RESULT', 122 | **params) 123 | 124 | def format_price(self, symbol, price): 125 | return self._format_value(price, self._tick_size[symbol]) 126 | 127 | def format_quantity(self, symbol, size): 128 | return self._format_value(size, self._step_size[symbol]) 129 | 130 | @retry 131 | def get_asset_balance(self, asset): 132 | balance = self.binance.get_asset_balance(asset) 133 | return float(balance['free']), float(balance['locked']) 134 | 135 | def get_symbol_balance(self, symbol): 136 | """Get symbol balance in symbol""" 137 | balance = 0 138 | try: 139 | symbol = symbol[0:len(symbol)-len(self.coin_target)] 140 | balance = self.binance.get_asset_balance(symbol) 141 | balance = float(balance['free']) 142 | except Exception as e: 143 | print("Error:", e) 144 | return balance, symbol # float(balance['locked']) 145 | 146 | def get_balance(self, ): 147 | """Balance in USDT for example - in coin target""" 148 | free, locked = self.get_asset_balance(self.coin_target) 149 | self._cash = free 150 | self._value = free + locked 151 | 152 | def getbroker(self): 153 | return self._broker 154 | 155 | def getdata(self, **kwargs): # timeframe, compression, start_date=None, LiveBars=True 156 | symbol = kwargs['dataname'] 157 | tf = self.get_interval(kwargs['timeframe'], kwargs['compression']) 158 | self.symbols.append(symbol) 159 | self.get_filters(symbol=symbol) 160 | if symbol not in self._datas: 161 | self._datas[f"{symbol}{tf}"] = BinanceData(store=self, **kwargs) # timeframe=timeframe, compression=compression, start_date=start_date, LiveBars=LiveBars 162 | return self._datas[f"{symbol}{tf}"] 163 | 164 | def get_filters(self, symbol): 165 | symbol_info = self.get_symbol_info(symbol) 166 | for f in symbol_info['filters']: 167 | if f['filterType'] == 'LOT_SIZE': 168 | self._step_size[symbol] = f['stepSize'] 169 | self._min_order[symbol] = f['minQty'] 170 | elif f['filterType'] == 'PRICE_FILTER': 171 | self._tick_size[symbol] = f['tickSize'] 172 | elif f['filterType'] == 'NOTIONAL': 173 | self._min_order_in_target[symbol] = f['minNotional'] 174 | 175 | def get_interval(self, timeframe, compression): 176 | return self._GRANULARITIES.get((timeframe, compression)) 177 | 178 | @retry 179 | def get_symbol_info(self, symbol): 180 | return self.binance.get_symbol_info(symbol) 181 | 182 | def stop_socket(self): 183 | self.binance_socket.stop() 184 | self.binance_socket.join(5) 185 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-binance 2 | backtrader 3 | pandas 4 | matplotlib 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # pip install setuptools twine 2 | # python setup.py sdist bdist_wheel 3 | # twine upload --repository testpypi dist/* 4 | # 'backtrader @ git+https://github.com/WISEPLAT/backtrader.git' - was removed from install_requires, as it can't publish 5 | # twine upload --repository pypi dist/* 6 | import os.path 7 | import codecs # To use a consistent encoding 8 | from setuptools import setup, find_packages 9 | 10 | here = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | # Get the long description from the relevant file 13 | with codecs.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 14 | long_description = f.read() 15 | 16 | setup(name='backtrader_binance', 17 | version='2.0.5', 18 | author='wiseplat', 19 | author_email='oshpagin@gmail.com', 20 | license='MIT License', 21 | description='Binance API integration with Backtrader', 22 | long_description=long_description, 23 | long_description_content_type='text/markdown', 24 | url='https://github.com/WISEPLAT/backtrader_binance', 25 | packages=find_packages(exclude=['docs', 'examples', 'ConfigBinance']), 26 | install_requires=['python-binance', 'backtrader', 'pandas', 'matplotlib'], 27 | classifiers=[ 28 | # How mature is this project? Common values are 29 | # 3 - Alpha 30 | # 4 - Beta 31 | # 5 - Production/Stable 32 | 'Development Status :: 5 - Production/Stable', 33 | 34 | # Indicate who your project is intended for 35 | 'Intended Audience :: Developers', 36 | 'Intended Audience :: Financial and Insurance Industry', 37 | 38 | # Indicate which Topics are covered by the package 39 | 'Topic :: Software Development', 40 | 'Topic :: Office/Business :: Financial', 41 | 42 | 'Programming Language :: Python :: 3.9', 43 | 'Programming Language :: Python :: 3.10', 44 | 'Programming Language :: Python :: 3.11', 45 | 'License :: OSI Approved :: MIT License', 46 | 'Operating System :: OS Independent' 47 | ], 48 | keywords=['trading', 'development'], 49 | project_urls={ 50 | 'Documentation': 'https://github.com/WISEPLAT/backtrader_binance/blob/master/README.md' 51 | }, 52 | python_requires='>=3.7' 53 | ) 54 | --------------------------------------------------------------------------------