Repository: lindomar-oliveira/backtrader-binance
Branch: main
Commit: 695db65f8289
Files: 10
Total size: 19.0 KB
Directory structure:
gitextract_x0l9znb4/
├── .gitignore
├── LICENSE
├── README.md
├── backtrader_binance/
│ ├── __init__.py
│ ├── binance_broker.py
│ ├── binance_feed.py
│ └── binance_store.py
├── examples/
│ └── live_trade.py
├── requirements.txt
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
__pycache__/
.idea/
*.egg-info/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Lindomar Oliveira
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# backtrader-binance
Create your strategy for the [Backtrader](https://www.backtrader.com), do the backtesting and you will also be ready for live trading on the exchange [Binance](https://www.binance.com/pt-PT/register?ref=D9K8QI13) with this integration.
Installation
============
pip install git+https://github.com/lindomar-oliveira/backtrader-binance.git@main
Usage
=====
See examples folder
Thanks
======
- backtrader: An incredible library...
- [python-binance](https://github.com/sammchardy/python-binance): For creating Binance API wrapper, shortening a lot of work.
License
=======
[MIT](https://choosealicense.com/licenses/mit)
================================================
FILE: backtrader_binance/__init__.py
================================================
from .binance_store import BinanceStore
================================================
FILE: backtrader_binance/binance_broker.py
================================================
import datetime as dt
from collections import defaultdict, deque
from math import copysign
from backtrader.broker import BrokerBase
from backtrader.order import Order, OrderBase
from backtrader.position import Position
from binance.enums import *
class BinanceOrder(OrderBase):
def __init__(self, owner, data, exectype, binance_order):
self.owner = owner
self.data = data
self.exectype = exectype
self.ordtype = self.Buy if binance_order['side'] == SIDE_BUY else self.Sell
# Market order price is zero
if self.exectype == Order.Market:
self.size = float(binance_order['executedQty'])
self.price = sum(float(fill['price']) for fill in binance_order['fills']) / len(binance_order['fills']) # Average price
else:
self.size = float(binance_order['origQty'])
self.price = float(binance_order['price'])
self.binance_order = binance_order
super(BinanceOrder, self).__init__()
self.accept()
class BinanceBroker(BrokerBase):
_ORDER_TYPES = {
Order.Limit: ORDER_TYPE_LIMIT,
Order.Market: ORDER_TYPE_MARKET,
Order.Stop: ORDER_TYPE_STOP_LOSS,
Order.StopLimit: ORDER_TYPE_STOP_LOSS_LIMIT,
}
def __init__(self, store):
super(BinanceBroker, self).__init__()
self.notifs = deque()
self.positions = defaultdict(Position)
self.open_orders = list()
self._store = store
self._store.binance_socket.start_user_socket(self._handle_user_socket_message)
def _execute_order(self, order, date, executed_size, executed_price):
order.execute(
date,
executed_size,
executed_price,
0, 0.0, 0.0,
0, 0.0, 0.0,
0.0, 0.0,
0, 0.0)
pos = self.getposition(order.data, clone=False)
pos.update(copysign(executed_size, order.size), executed_price)
def _handle_user_socket_message(self, msg):
"""https://binance-docs.github.io/apidocs/spot/en/#payload-order-update"""
if msg['e'] == 'executionReport':
if msg['s'] == self._store.symbol:
for o in self.open_orders:
if o.binance_order['orderId'] == msg['i']:
if msg['X'] in [ORDER_STATUS_FILLED, ORDER_STATUS_PARTIALLY_FILLED]:
date = dt.datetime.fromtimestamp(msg['T'] / 1000)
executed_size = float(msg['l'])
executed_price = float(msg['L'])
self._execute_order(o, dt, executed_size, executed_price)
self._set_order_status(o, msg['X'])
if o.status not in [Order.Accepted, Order.Partial]:
self.open_orders.remove(o)
self.notify(o)
elif msg['e'] == 'error':
raise msg
def _set_order_status(self, order, binance_order_status):
if binance_order_status == ORDER_STATUS_CANCELED:
order.cancel()
elif binance_order_status == ORDER_STATUS_EXPIRED:
order.expire()
elif binance_order_status == ORDER_STATUS_FILLED:
order.completed()
elif binance_order_status == ORDER_STATUS_PARTIALLY_FILLED:
order.partial()
elif binance_order_status == ORDER_STATUS_REJECTED:
order.reject()
def _submit(self, owner, data, side, exectype, size, price):
type = self._ORDER_TYPES.get(exectype, ORDER_TYPE_MARKET)
binance_order = self._store.create_order(side, type, size, price)
order = BinanceOrder(owner, data, exectype, binance_order)
if binance_order['status'] in [ORDER_STATUS_FILLED, ORDER_STATUS_PARTIALLY_FILLED]:
self._execute_order(
order,
dt.datetime.fromtimestamp(binance_order['transactTime'] / 1000),
float(binance_order['executedQty']),
float(binance_order['price']))
self._set_order_status(order, binance_order['status'])
if order.status == Order.Accepted:
self.open_orders.append(order)
self.notify(order)
return order
def buy(self, owner, data, size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
**kwargs):
return self._submit(owner, data, SIDE_BUY, exectype, size, price)
def cancel(self, order):
order_id = order.binance_order['orderId']
self._store.cancel_order(order_id)
def format_price(self, value):
return self._store.format_price(value)
def get_asset_balance(self, asset):
return self._store.get_asset_balance(asset)
def getcash(self):
self.cash = self._store._cash
return self.cash
def get_notification(self):
if not self.notifs:
return None
return self.notifs.popleft()
def getposition(self, data, clone=True):
pos = self.positions[data._dataname]
if clone:
pos = pos.clone()
return pos
def getvalue(self, datas=None):
self.value = self._store._value
return self.value
def notify(self, order):
self.notifs.append(order)
def sell(self, owner, data, size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
**kwargs):
return self._submit(owner, data, SIDE_SELL, exectype, size, price)
================================================
FILE: backtrader_binance/binance_feed.py
================================================
from collections import deque
import pandas as pd
from backtrader.dataseries import TimeFrame
from backtrader.feed import DataBase
from backtrader.utils import date2num
class BinanceData(DataBase):
params = (
('drop_newest', True),
)
# States for the Finite State Machine in _load
_ST_LIVE, _ST_HISTORBACK, _ST_OVER = range(3)
def __init__(self, store, timeframe_in_minutes, start_date=None):
self.timeframe_in_minutes = timeframe_in_minutes
self.start_date = start_date
self._store = store
self._data = deque()
def _handle_kline_socket_message(self, msg):
"""https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-streams"""
if msg['e'] == 'kline':
if msg['k']['x']: # Is closed
kline = self._parser_to_kline(msg['k']['t'], msg['k'])
self._data.extend(kline.values.tolist())
elif msg['e'] == 'error':
raise msg
def _load(self):
if self._state == self._ST_OVER:
return False
elif self._state == self._ST_LIVE:
return self._load_kline()
elif self._state == self._ST_HISTORBACK:
if self._load_kline():
return True
else:
self._start_live()
def _load_kline(self):
try:
kline = self._data.popleft()
except IndexError:
return None
timestamp, open_, high, low, close, volume = kline
self.lines.datetime[0] = date2num(timestamp)
self.lines.open[0] = open_
self.lines.high[0] = high
self.lines.low[0] = low
self.lines.close[0] = close
self.lines.volume[0] = volume
return True
def _parser_dataframe(self, data):
df = data.copy()
df.columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
df['timestamp'] = df['timestamp'].values.astype(dtype='datetime64[ms]')
df['open'] = df['open'].values.astype(float)
df['high'] = df['high'].values.astype(float)
df['low'] = df['low'].values.astype(float)
df['close'] = df['close'].values.astype(float)
df['volume'] = df['volume'].values.astype(float)
# df.set_index('timestamp', inplace=True)
return df
def _parser_to_kline(self, timestamp, kline):
df = pd.DataFrame([[timestamp, kline['o'], kline['h'],
kline['l'], kline['c'], kline['v']]])
return self._parser_dataframe(df)
def _start_live(self):
self._state = self._ST_LIVE
self.put_notification(self.LIVE)
self._store.binance_socket.start_kline_socket(
self._handle_kline_socket_message,
self.symbol_info['symbol'],
self.interval)
def haslivedata(self):
return self._state == self._ST_LIVE and self._data
def islive(self):
return True
def start(self):
DataBase.start(self)
self.interval = self._store.get_interval(TimeFrame.Minutes, self.timeframe_in_minutes)
if self.interval is None:
self._state = self._ST_OVER
self.put_notification(self.NOTSUPPORTED_TF)
return
self.symbol_info = self._store.get_symbol_info(self._store.symbol)
if self.symbol_info is None:
self._state = self._ST_OVER
self.put_notification(self.NOTSUBSCRIBED)
return
if self.start_date:
self._state = self._ST_HISTORBACK
self.put_notification(self.DELAYED)
klines = self._store.binance.get_historical_klines(
self.symbol_info['symbol'],
self.interval,
self.start_date.strftime('%d %b %Y %H:%M:%S'))
if self.p.drop_newest:
klines.pop()
df = pd.DataFrame(klines)
df.drop(df.columns[[6, 7, 8, 9, 10, 11]], axis=1, inplace=True) # Remove unnecessary columns
df = self._parser_dataframe(df)
self._data.extend(df.values.tolist())
else:
self._start_live()
================================================
FILE: backtrader_binance/binance_store.py
================================================
import time
from functools import wraps
from math import floor
from backtrader.dataseries import TimeFrame
from binance import Client, ThreadedWebsocketManager
from binance.enums import *
from binance.exceptions import BinanceAPIException
from requests.exceptions import ConnectTimeout, ConnectionError
from .binance_broker import BinanceBroker
from .binance_feed import BinanceData
class BinanceStore(object):
_GRANULARITIES = {
(TimeFrame.Minutes, 1): KLINE_INTERVAL_1MINUTE,
(TimeFrame.Minutes, 3): KLINE_INTERVAL_3MINUTE,
(TimeFrame.Minutes, 5): KLINE_INTERVAL_5MINUTE,
(TimeFrame.Minutes, 15): KLINE_INTERVAL_15MINUTE,
(TimeFrame.Minutes, 30): KLINE_INTERVAL_30MINUTE,
(TimeFrame.Minutes, 60): KLINE_INTERVAL_1HOUR,
(TimeFrame.Minutes, 120): KLINE_INTERVAL_2HOUR,
(TimeFrame.Minutes, 240): KLINE_INTERVAL_4HOUR,
(TimeFrame.Minutes, 360): KLINE_INTERVAL_6HOUR,
(TimeFrame.Minutes, 480): KLINE_INTERVAL_8HOUR,
(TimeFrame.Minutes, 720): KLINE_INTERVAL_12HOUR,
(TimeFrame.Days, 1): KLINE_INTERVAL_1DAY,
(TimeFrame.Days, 3): KLINE_INTERVAL_3DAY,
(TimeFrame.Weeks, 1): KLINE_INTERVAL_1WEEK,
(TimeFrame.Months, 1): KLINE_INTERVAL_1MONTH,
}
def __init__(self, api_key, api_secret, coin_refer, coin_target, testnet=False, retries=5):
self.binance = Client(api_key, api_secret, testnet=testnet)
self.binance_socket = ThreadedWebsocketManager(api_key, api_secret, testnet=testnet)
self.binance_socket.daemon = True
self.binance_socket.start()
self.coin_refer = coin_refer
self.coin_target = coin_target
self.symbol = coin_refer + coin_target
self.retries = retries
self._cash = 0
self._value = 0
self.get_balance()
self._step_size = None
self._tick_size = None
self.get_filters()
self._broker = BinanceBroker(store=self)
self._data = None
def _format_value(self, value, step):
precision = step.find('1') - 1
if precision > 0:
return '{:0.0{}f}'.format(value, precision)
return floor(int(value))
def retry(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
for attempt in range(1, self.retries + 1):
time.sleep(60 / 1200) # API Rate Limit
try:
return func(self, *args, **kwargs)
except (BinanceAPIException, ConnectTimeout, ConnectionError) as err:
if isinstance(err, BinanceAPIException) and err.code == -1021:
# Recalculate timestamp offset between local and Binance's server
res = self.binance.get_server_time()
self.binance.timestamp_offset = res['serverTime'] - int(time.time() * 1000)
if attempt == self.retries:
raise
return wrapper
@retry
def cancel_open_orders(self):
orders = self.binance.get_open_orders(symbol=self.symbol)
if len(orders) > 0:
self.binance._request_api('delete', 'openOrders', signed=True, data={ 'symbol': self.symbol })
@retry
def cancel_order(self, order_id):
try:
self.binance.cancel_order(symbol=self.symbol, orderId=order_id)
except BinanceAPIException as api_err:
if api_err.code == -2011: # Order filled
return
else:
raise api_err
except Exception as err:
raise err
@retry
def create_order(self, side, type, size, price):
params = dict()
if type in [ORDER_TYPE_LIMIT, ORDER_TYPE_STOP_LOSS_LIMIT]:
params.update({
'timeInForce': TIME_IN_FORCE_GTC
})
if type != ORDER_TYPE_MARKET:
params.update({
'price': self.format_price(price)
})
return self.binance.create_order(
symbol=self.symbol,
side=side,
type=type,
quantity=self.format_quantity(size),
**params)
def format_price(self, price):
return self._format_value(price, self._tick_size)
def format_quantity(self, size):
return self._format_value(size, self._step_size)
@retry
def get_asset_balance(self, asset):
balance = self.binance.get_asset_balance(asset)
return float(balance['free']), float(balance['locked'])
def get_balance(self):
free, locked = self.get_asset_balance(self.coin_target)
self._cash = free
self._value = free + locked
def getbroker(self):
return self._broker
def getdata(self, timeframe_in_minutes, start_date=None):
if not self._data:
self._data = BinanceData(store=self, timeframe_in_minutes=timeframe_in_minutes, start_date=start_date)
return self._data
def get_filters(self):
symbol_info = self.get_symbol_info(self.symbol)
for f in symbol_info['filters']:
if f['filterType'] == 'LOT_SIZE':
self._step_size = f['stepSize']
elif f['filterType'] == 'PRICE_FILTER':
self._tick_size = f['tickSize']
def get_interval(self, timeframe, compression):
return self._GRANULARITIES.get((timeframe, compression))
@retry
def get_symbol_info(self, symbol):
return self.binance.get_symbol_info(symbol)
def stop_socket(self):
self.binance_socket.stop()
self.binance_socket.join(5)
================================================
FILE: examples/live_trade.py
================================================
import datetime as dt
import backtrader as bt
from backtrader_binance import BinanceStore
class RSIStrategy(bt.Strategy):
def __init__(self):
self.rsi = bt.indicators.RSI(period=14) # RSI indicator
def next(self):
print('Open: {}, High: {}, Low: {}, Close: {}'.format(
self.data.open[0],
self.data.high[0],
self.data.low[0],
self.data.close[0]))
print('RSI: {}'.format(self.rsi[0]))
if not self.position:
if self.rsi < 30: # Enter long
self.buy()
else:
if self.rsi > 70:
self.sell() # Close long position
def notify_order(self, order):
print(order)
if __name__ == '__main__':
cerebro = bt.Cerebro(quicknotify=True)
store = BinanceStore(
api_key='YOUR_BINANCE_KEY',
api_secret='YOUR_BINANCE_SECRET',
coin_refer='BTC',
coin_target='USDT',
testnet=True)
broker = store.getbroker()
cerebro.setbroker(broker)
from_date = dt.datetime.utcnow() - dt.timedelta(minutes=5*16)
data = store.getdata(
timeframe_in_minutes=5,
start_date=from_date)
cerebro.addstrategy(RSIStrategy)
cerebro.adddata(data)
cerebro.run()
================================================
FILE: requirements.txt
================================================
backtrader==1.9.76.123
matplotlib==3.2.2
pandas==1.2.4
python-binance==1.0.12
================================================
FILE: setup.py
================================================
import os
from setuptools import setup
with open(os.path.join('README.md')) as desc:
LONG_DESCRIPTION = desc.read()
with open(os.path.join('requirements.txt')) as reqs:
REQUIREMENTS = reqs.readlines()
setup(
name='backtrader-binance',
version='1.0.0',
description='Binance API integration with backtrader',
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
url='https://github.com/lindomar-oliveira/backtrader-binance',
author='Lindomar Oliveira',
author_email='lindomar.souza1999@gmail.com',
license='MIT',
packages=['backtrader_binance'],
python_requires='>=3.7',
keywords='backtrader,binance,bitcoin,bot,crypto,trading',
install_requires=REQUIREMENTS
)
gitextract_x0l9znb4/ ├── .gitignore ├── LICENSE ├── README.md ├── backtrader_binance/ │ ├── __init__.py │ ├── binance_broker.py │ ├── binance_feed.py │ └── binance_store.py ├── examples/ │ └── live_trade.py ├── requirements.txt └── setup.py
SYMBOL INDEX (50 symbols across 4 files)
FILE: backtrader_binance/binance_broker.py
class BinanceOrder (line 12) | class BinanceOrder(OrderBase):
method __init__ (line 13) | def __init__(self, owner, data, exectype, binance_order):
class BinanceBroker (line 32) | class BinanceBroker(BrokerBase):
method __init__ (line 40) | def __init__(self, store):
method _execute_order (line 51) | def _execute_order(self, order, date, executed_size, executed_price):
method _handle_user_socket_message (line 63) | def _handle_user_socket_message(self, msg):
method _set_order_status (line 82) | def _set_order_status(self, order, binance_order_status):
method _submit (line 94) | def _submit(self, owner, data, side, exectype, size, price):
method buy (line 111) | def buy(self, owner, data, size, price=None, plimit=None,
method cancel (line 117) | def cancel(self, order):
method format_price (line 121) | def format_price(self, value):
method get_asset_balance (line 124) | def get_asset_balance(self, asset):
method getcash (line 127) | def getcash(self):
method get_notification (line 131) | def get_notification(self):
method getposition (line 137) | def getposition(self, data, clone=True):
method getvalue (line 143) | def getvalue(self, datas=None):
method notify (line 147) | def notify(self, order):
method sell (line 150) | def sell(self, owner, data, size, price=None, plimit=None,
FILE: backtrader_binance/binance_feed.py
class BinanceData (line 10) | class BinanceData(DataBase):
method __init__ (line 18) | def __init__(self, store, timeframe_in_minutes, start_date=None):
method _handle_kline_socket_message (line 25) | def _handle_kline_socket_message(self, msg):
method _load (line 34) | def _load(self):
method _load_kline (line 45) | def _load_kline(self):
method _parser_dataframe (line 61) | def _parser_dataframe(self, data):
method _parser_to_kline (line 73) | def _parser_to_kline(self, timestamp, kline):
method _start_live (line 78) | def _start_live(self):
method haslivedata (line 87) | def haslivedata(self):
method islive (line 90) | def islive(self):
method start (line 93) | def start(self):
FILE: backtrader_binance/binance_store.py
class BinanceStore (line 16) | class BinanceStore(object):
method __init__ (line 35) | def __init__(self, api_key, api_secret, coin_refer, coin_target, testn...
method _format_value (line 56) | def _format_value(self, value, step):
method retry (line 62) | def retry(func):
method cancel_open_orders (line 80) | def cancel_open_orders(self):
method cancel_order (line 86) | def cancel_order(self, order_id):
method create_order (line 98) | def create_order(self, side, type, size, price):
method format_price (line 116) | def format_price(self, price):
method format_quantity (line 119) | def format_quantity(self, size):
method get_asset_balance (line 123) | def get_asset_balance(self, asset):
method get_balance (line 127) | def get_balance(self):
method getbroker (line 132) | def getbroker(self):
method getdata (line 135) | def getdata(self, timeframe_in_minutes, start_date=None):
method get_filters (line 140) | def get_filters(self):
method get_interval (line 148) | def get_interval(self, timeframe, compression):
method get_symbol_info (line 152) | def get_symbol_info(self, symbol):
method stop_socket (line 155) | def stop_socket(self):
FILE: examples/live_trade.py
class RSIStrategy (line 8) | class RSIStrategy(bt.Strategy):
method __init__ (line 9) | def __init__(self):
method next (line 12) | def next(self):
method notify_order (line 27) | def notify_order(self, order):
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (21K chars).
[
{
"path": ".gitignore",
"chars": 32,
"preview": "__pycache__/\n\n.idea/\n*.egg-info/"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2021 Lindomar Oliveira\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 647,
"preview": "# backtrader-binance\n\nCreate your strategy for the [Backtrader](https://www.backtrader.com), do the backtesting and you "
},
{
"path": "backtrader_binance/__init__.py",
"chars": 40,
"preview": "from .binance_store import BinanceStore\n"
},
{
"path": "backtrader_binance/binance_broker.py",
"chars": 5663,
"preview": "import datetime as dt\n\nfrom collections import defaultdict, deque\nfrom math import copysign\n\nfrom backtrader.broker impo"
},
{
"path": "backtrader_binance/binance_feed.py",
"chars": 4211,
"preview": "from collections import deque\n\nimport pandas as pd\n\nfrom backtrader.dataseries import TimeFrame\nfrom backtrader.feed imp"
},
{
"path": "backtrader_binance/binance_store.py",
"chars": 5669,
"preview": "import time\n\nfrom functools import wraps\nfrom math import floor\n\nfrom backtrader.dataseries import TimeFrame\nfrom binanc"
},
{
"path": "examples/live_trade.py",
"chars": 1277,
"preview": "import datetime as dt\n\nimport backtrader as bt\n\nfrom backtrader_binance import BinanceStore\n\n\nclass RSIStrategy(bt.Strat"
},
{
"path": "requirements.txt",
"chars": 77,
"preview": "backtrader==1.9.76.123\nmatplotlib==3.2.2\npandas==1.2.4\npython-binance==1.0.12"
},
{
"path": "setup.py",
"chars": 751,
"preview": "import os\nfrom setuptools import setup\n\nwith open(os.path.join('README.md')) as desc:\n LONG_DESCRIPTION = desc.read()"
}
]
About this extraction
This page contains the full source code of the lindomar-oliveira/backtrader-binance GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (19.0 KB), approximately 4.8k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.