Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b8dda02
proposed fix for compression only resampling
backtrader Jul 30, 2018
cc2751a
Fix regression introduced with 8f537a1c2c271eb5cfc592b373697732597d26d6
backtrader Aug 17, 2018
32cdac1
Allow rollover to distinguish between no values temporarily (with Non…
backtrader Aug 17, 2018
bf40525
Avoid math domain error for negative returns in logarithmic calculations
backtrader Aug 25, 2018
5754534
Fix local variable declaration for compound returns
backtrader Aug 25, 2018
f087a55
Fix typo in date2num tz conversion which shows up in direct usage
backtrader Sep 6, 2018
0ae1abd
Release 1.9.66.122
backtrader Sep 6, 2018
e621a35
Implement CCXT feed and broker
bartosh Oct 28, 2017
68a6f80
Made the code to work on Python3
bartosh Nov 27, 2017
0c5901b
Added property to monitor last timestamp
cydan33 Dec 3, 2017
8f4aea4
Move check to _fetch_ohlcv
cydan33 Dec 13, 2017
79a1f95
Fetch the ohlcv data only since the last timestamp
cydan33 Dec 13, 2017
bdb93b1
Fetch OHLCV data in chunks
bartosh Dec 17, 2017
93c1ae7
Add config to CCXT feed parameters
rmihael Dec 20, 2017
846c8f6
Move ccxt API calls to ccxtstore
bartosh Dec 23, 2017
d635901
Implement retrying queries
bartosh Dec 23, 2017
0f95cc1
Fix Pylint warnings&errors
bartosh Dec 23, 2017
e85744d
Check if exchange supports requested time frame
bartosh Dec 24, 2017
918741e
return None when next bar is not available
bartosh Jan 7, 2018
1897cea
skip incomplete ohlcv bars
bartosh Jan 8, 2018
3d6acd5
fix default ohlcv_limit
napmany Jan 12, 2018
98e622c
sorted fetch_ohlcv output to ensure order
bartosh Jan 16, 2018
8782594
ccxtbroker: don't use unparsed order properties
bartosh Jan 17, 2018
0f044c8
Fix for hasFetchOHLCV tag, so we work correctly with exchanges like K…
Jan 24, 2018
10fb4c4
ccxtbroker: implement get_orders_open
bartosh Feb 3, 2018
0af8220
Retry when exchange errors occur
bartosh Feb 27, 2018
bf2b59f
Fix AttributeError: 'CCXTBroker' object has no attribute 'get_value'
bartosh Apr 21, 2018
ead42b3
Set default value 0.0 for getcash and getvalue methods
bartosh Jun 16, 2018
d486460
fix possible KeyError exceptions
bartosh Aug 5, 2018
4ffbea7
Moved CCXTOrder class and fix cancel method.
JaCoderX Aug 7, 2018
cc4ba67
parse order in create_order method
bartosh Aug 14, 2018
c836840
minor fix on load_ticks
JaCoderX Aug 20, 2018
69f74a1
Revert "minor fix on load_ticks"
bartosh Sep 5, 2018
85ad19f
Fixed "if self._last_id is None" condition
bartosh Sep 5, 2018
7ac035d
do not create new story object for the same exchange
bartosh Aug 28, 2018
3c32fc1
ccxt: check if 'amount' field is set for the order
bartosh Sep 9, 2018
81a5fca
fix matplotlib.dates can't import warnings
pofenglin079 Sep 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions backtrader/analyzers/returns.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,17 @@ def stop(self):
self._value_end = self.strategy.broker.fundvalue

# Compound return
self.rets['rtot'] = rtot = (
math.log(self._value_end / self._value_start))
try:
nlrtot = self._value_end / self._value_start
except ZeroDivisionError:
rtot = float('-inf')
else:
if nlrtot < 0.0:
rtot = float('-inf')
else:
rtot = math.log(nlrtot)

self.rets['rtot'] = rtot

# Average return
self.rets['ravg'] = ravg = rtot / self._tcount
Expand All @@ -135,7 +144,11 @@ def stop(self):
if tann is None:
tann = self._TANN.get(self.data._timeframe, 1.0) # assign default

self.rets['rnorm'] = rnorm = math.expm1(ravg * tann)
if ravg > float('-inf'):
self.rets['rnorm'] = rnorm = math.expm1(ravg * tann)
else:
self.rets['rnorm'] = rnorm = ravg

self.rets['rnorm100'] = rnorm * 100.0 # human readable %

def _on_dt_over(self):
Expand Down
2 changes: 1 addition & 1 deletion backtrader/analyzers/tradeanalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def notify_trade(self, trade):
# Trade just closed

won = res.won = int(trade.pnlcomm >= 0.0)
lost = res.lost = int(won)
lost = res.lost = int(not won)
tlong = res.tlong = trade.long
tshort = res.tshort = not trade.long

Expand Down
5 changes: 5 additions & 0 deletions backtrader/brokers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@
from .oandabroker import OandaBroker
except ImportError as e:
pass # The user may not have something installed

try:
from .ccxtbroker import CCXTBroker
except ImportError as e:
pass # The user may not have something installed
98 changes: 98 additions & 0 deletions backtrader/brokers/ccxtbroker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015, 2016, 2017 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
unicode_literals)

from backtrader import BrokerBase, Order
from backtrader.utils.py3 import queue
from backtrader.stores.ccxtstore import CCXTStore, CCXTOrder

class CCXTBroker(BrokerBase):
'''Broker implementation for CCXT cryptocurrency trading library.

This class maps the orders/positions from CCXT to the
internal API of ``backtrader``.
'''

order_types = {Order.Market: 'market',
Order.Limit: 'limit',
Order.Stop: 'stop',
Order.StopLimit: 'stop limit'}

def __init__(self, exchange, currency, config, retries=5):
super(CCXTBroker, self).__init__()

self.store = CCXTStore.get_store(exchange, config, retries)

self.currency = currency

self.notifs = queue.Queue() # holds orders which are notified

def getcash(self):
return self.store.getcash(self.currency)

def getvalue(self, datas=None):
return self.store.getvalue(self.currency)

def get_notification(self):
try:
return self.notifs.get(False)
except queue.Empty:
return None

def notify(self, order):
self.notifs.put(order)

def getposition(self, data):
currency = data.symbol.split('/')[0]
return self.store.getposition(currency)

def get_value(self, datas=None, mkt=False, lever=False):
return self.store.getvalue(self.currency)

def get_cash(self):
return self.store.getcash(self.currency)

def _submit(self, owner, data, exectype, side, amount, price, params):
order_type = self.order_types.get(exectype)
_order = self.store.create_order(symbol=data.symbol, order_type=order_type, side=side,
amount=amount, price=price, params=params)
order = CCXTOrder(owner, data, amount, _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, exectype, 'buy', size, price, kwargs)

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, exectype, 'sell', size, price, kwargs)

def cancel(self, order):
return self.store.cancel_order(order)

def get_orders_open(self, safe=False):
return self.store.fetch_open_orders()
2 changes: 1 addition & 1 deletion backtrader/dataseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TimeFrame(object):
names = Names # support old naming convention

@classmethod
def getname(cls, tframe, compression=None):
def getname(cls, tframe, compression=1):
tname = cls.Names[tframe]
if compression > 1 or tname == cls.Names[-1]:
return tname # for plural or 'NoTimeFrame' return plain entry
Expand Down
5 changes: 5 additions & 0 deletions backtrader/feeds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@

from .rollover import RollOver
from .chainer import Chainer

try:
from .ccxt import CCXT
except ImportError:
pass # The user may not have something installed
192 changes: 192 additions & 0 deletions backtrader/feeds/ccxt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015, 2016, 2017 Daniel Rodriguez
# Copyright (C) 2017 Ed Bartosh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
unicode_literals)

from collections import deque
from datetime import datetime

import backtrader as bt
from backtrader.feed import DataBase
from backtrader.stores.ccxtstore import CCXTStore

class CCXT(DataBase):
"""
CryptoCurrency eXchange Trading Library Data Feed.

Params:

- ``historical`` (default: ``False``)

If set to ``True`` the data feed will stop after doing the first
download of data.

The standard data feed parameters ``fromdate`` and ``todate`` will be
used as reference.

- ``backfill_start`` (default: ``True``)

Perform backfilling at the start. The maximum possible historical data
will be fetched in a single request.
"""

params = (
('historical', False), # only historical download
('backfill_start', False), # do backfilling at the start
)

# States for the Finite State Machine in _load
_ST_LIVE, _ST_HISTORBACK, _ST_OVER = range(3)

def __init__(self, exchange, symbol, ohlcv_limit=None, config={}, retries=5):
self.symbol = symbol
self.ohlcv_limit = ohlcv_limit

self.store = CCXTStore.get_store(exchange, config, retries)

self._data = deque() # data queue for price data
self._last_id = '' # last processed trade id for ohlcv
self._last_ts = 0 # last processed timestamp for ohlcv

def start(self, ):
DataBase.start(self)

if self.p.fromdate:
self._state = self._ST_HISTORBACK
self.put_notification(self.DELAYED)

self._fetch_ohlcv(self.p.fromdate)
else:
self._state = self._ST_LIVE
self.put_notification(self.LIVE)

def _load(self):
if self._state == self._ST_OVER:
return False

while True:
if self._state == self._ST_LIVE:
if self._timeframe == bt.TimeFrame.Ticks:
return self._load_ticks()
else:
self._fetch_ohlcv()
return self._load_ohlcv()
elif self._state == self._ST_HISTORBACK:
ret = self._load_ohlcv()
if ret:
return ret
else:
# End of historical data
if self.p.historical: # only historical
self.put_notification(self.DISCONNECTED)
self._state = self._ST_OVER
return False # end of historical
else:
self._state = self._ST_LIVE
self.put_notification(self.LIVE)
continue

def _fetch_ohlcv(self, fromdate=None):
"""Fetch OHLCV data into self._data queue"""
granularity = self.store.get_granularity(self._timeframe, self._compression)

if fromdate:
since = int((fromdate - datetime(1970, 1, 1)).total_seconds() * 1000)
else:
if self._last_ts > 0:
since = self._last_ts
else:
since = None

limit = self.ohlcv_limit

while True:
dlen = len(self._data)
for ohlcv in sorted(self.store.fetch_ohlcv(self.symbol, timeframe=granularity,
since=since, limit=limit)):
if None in ohlcv:
continue

tstamp = ohlcv[0]
if tstamp > self._last_ts:
self._data.append(ohlcv)
self._last_ts = tstamp
since = tstamp + 1

if dlen == len(self._data):
break

def _load_ticks(self):
if self._last_id:
trades = self.store.fetch_trades(self.symbol)
else:
# first time get the latest trade only
trades = [self.store.fetch_trades(self.symbol)[-1]]

for trade in trades:
trade_id = trade['id']

if trade_id > self._last_id:
trade_time = datetime.strptime(trade['datetime'], '%Y-%m-%dT%H:%M:%S.%fZ')
self._data.append((trade_time, float(trade['price']), float(trade['amount'])))
self._last_id = trade_id

try:
trade = self._data.popleft()
except IndexError:
return None # no data in the queue

trade_time, price, size = trade

self.lines.datetime[0] = bt.date2num(trade_time)
self.lines.open[0] = price
self.lines.high[0] = price
self.lines.low[0] = price
self.lines.close[0] = price
self.lines.volume[0] = size

return True

def _load_ohlcv(self):
try:
ohlcv = self._data.popleft()
except IndexError:
return None # no data in the queue

tstamp, open_, high, low, close, volume = ohlcv

dtime = datetime.utcfromtimestamp(tstamp // 1000)

self.lines.datetime[0] = bt.date2num(dtime)
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 haslivedata(self):
return self._state == self._ST_LIVE and self._data

def islive(self):
return not self.p.historical
2 changes: 1 addition & 1 deletion backtrader/feeds/rollover.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def _checkcondition(self, d0, d1):

def _load(self):
while self._d is not None:
if not self._d.next(): # no values from current data source
if self._d.next() is not False: # no values from current data src
if self._ds:
self._d = self._ds.pop(0)
self._dts.pop(0)
Expand Down
3 changes: 2 additions & 1 deletion backtrader/plot/locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
'''

import datetime
import warnings

from matplotlib.dates import AutoDateLocator as ADLocator
from matplotlib.dates import RRuleLocator as RRLocator
Expand All @@ -36,7 +37,7 @@
MONTHS_PER_YEAR, DAYS_PER_WEEK,
SEC_PER_HOUR, SEC_PER_DAY,
num2date, rrulewrapper, YearLocator,
MicrosecondLocator, warnings)
MicrosecondLocator)

from dateutil.relativedelta import relativedelta
import numpy as np
Expand Down
Loading