backtrader-next 2.1.1__py3-none-any.whl → 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backtrader_next/analyzers/eq.py +34 -53
- backtrader_next/order.py +63 -17
- backtrader_next/version.py +1 -1
- {backtrader_next-2.1.1.dist-info → backtrader_next-2.2.1.dist-info}/METADATA +17 -1
- {backtrader_next-2.1.1.dist-info → backtrader_next-2.2.1.dist-info}/RECORD +7 -7
- {backtrader_next-2.1.1.dist-info → backtrader_next-2.2.1.dist-info}/WHEEL +0 -0
- {backtrader_next-2.1.1.dist-info → backtrader_next-2.2.1.dist-info}/licenses/LICENSE +0 -0
backtrader_next/analyzers/eq.py
CHANGED
|
@@ -7,15 +7,13 @@ import numpy as np
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from pandas import DataFrame as df
|
|
9
9
|
from numbers import Number
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Union
|
|
11
11
|
import math
|
|
12
|
-
# from math import copysign
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Eq(bt.Analyzer):
|
|
16
|
-
'''This analyzer calculates trading system
|
|
17
|
-
|
|
18
|
-
length and drawdown max length
|
|
15
|
+
'''This analyzer calculates comprehensive trading system performance statistics
|
|
16
|
+
including equity curves, drawdown analysis, returns, risk metrics, and trade statistics.
|
|
19
17
|
|
|
20
18
|
Params:
|
|
21
19
|
|
|
@@ -28,20 +26,42 @@ class Eq(bt.Analyzer):
|
|
|
28
26
|
|
|
29
27
|
Set it to ``True`` or ``False`` for a specific behavior
|
|
30
28
|
|
|
29
|
+
- ``data`` (default: ``None``)
|
|
30
|
+
|
|
31
|
+
Specific data feed to analyze. If ``None``, all data is analyzed.
|
|
32
|
+
|
|
33
|
+
- ``cash`` (default: ``True``)
|
|
34
|
+
|
|
35
|
+
Whether to include cash information in equity tracking.
|
|
36
|
+
|
|
31
37
|
Methods:
|
|
32
38
|
|
|
33
|
-
- ``
|
|
39
|
+
- ``compute_stats(risk_free_rate=0.0)``
|
|
40
|
+
|
|
41
|
+
Returns a pandas Series with comprehensive trading statistics including:
|
|
42
|
+
|
|
43
|
+
- Equity metrics: Start, End, Duration, Peak equity
|
|
44
|
+
- Returns: Cumulative return %, Annualized return, CAGR
|
|
45
|
+
- Risk metrics: Volatility (Ann.), Sharpe Ratio, Sortino Ratio, Smart Sharpe Ratio
|
|
46
|
+
- Drawdown stats: Max Drawdown %, Avg Drawdown %, Drawdown Duration
|
|
47
|
+
- Trade statistics: Win Rate, Best/Worst/Avg Trade, # of Trades
|
|
48
|
+
- Performance ratios: Calmar Ratio, VWR Ratio, Profit Factor, SQN, Kelly Criterion
|
|
49
|
+
|
|
50
|
+
- ``gen_eq()``
|
|
51
|
+
|
|
52
|
+
Returns equity curve as a pandas DataFrame with datetime index.
|
|
53
|
+
|
|
54
|
+
- ``gen_eq_dd()``
|
|
55
|
+
|
|
56
|
+
Returns equity and drawdown data as a pandas DataFrame.
|
|
34
57
|
|
|
35
|
-
|
|
36
|
-
drawdown stats as values, the following keys/attributes are available:
|
|
58
|
+
- ``gen_trades(data_name=None, pct=False)``
|
|
37
59
|
|
|
38
|
-
-
|
|
39
|
-
- ``moneydown`` - drawdown value in monetary units
|
|
40
|
-
- ``len`` - drawdown length
|
|
60
|
+
Returns trade-level statistics as a pandas DataFrame.
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
- ``gen_orders(data_name=None)``
|
|
63
|
+
|
|
64
|
+
Returns order history as a pandas DataFrame.
|
|
45
65
|
'''
|
|
46
66
|
|
|
47
67
|
params = (
|
|
@@ -136,7 +156,6 @@ class Eq(bt.Analyzer):
|
|
|
136
156
|
def gen_trades(self, data_name=None, pct=False) -> 'DataFrame':
|
|
137
157
|
if self.trades_df is None:
|
|
138
158
|
self.trades_df = df.from_records(self.trades, columns=self.trades_header)
|
|
139
|
-
# self.trades_df = df.from_records(self.trades, index=self.trades_header[0], columns=self.trades_header)
|
|
140
159
|
if data_name is None:
|
|
141
160
|
rdf = self.trades_df.copy()
|
|
142
161
|
else:
|
|
@@ -211,7 +230,6 @@ class Eq(bt.Analyzer):
|
|
|
211
230
|
# our risk doesn't; they use the simpler approach below.
|
|
212
231
|
annualized_return = (1 + gmean_day_return) ** annual_trading_days - 1
|
|
213
232
|
s.loc['Return (Ann.) [%]'] = round(annualized_return * 100, 4)
|
|
214
|
-
# s.loc['Risk (Ann.) [%]'] = day_returns.std(ddof=1) * np.sqrt(annual_trading_days) * 100
|
|
215
233
|
s.loc['Volatility (Ann.) [%]'] = volatility = round(day_returns.std(ddof=1) * np.sqrt(annual_trading_days) * 100, 4)
|
|
216
234
|
|
|
217
235
|
# CAGR from quantstats
|
|
@@ -286,10 +304,6 @@ class Eq(bt.Analyzer):
|
|
|
286
304
|
else:
|
|
287
305
|
s.loc['Kelly Criterion [%]'] = np.nan
|
|
288
306
|
|
|
289
|
-
# s.loc['_strategy'] = strategy_instance
|
|
290
|
-
# s.loc['_equity_curve'] = equity_df
|
|
291
|
-
# s.loc['_trades'] = trades_df
|
|
292
|
-
#
|
|
293
307
|
return s
|
|
294
308
|
|
|
295
309
|
|
|
@@ -322,39 +336,6 @@ def geometric_mean(returns: pd.Series) -> float:
|
|
|
322
336
|
return np.exp(np.log(returns).sum() / (len(returns) or np.nan)) - 1
|
|
323
337
|
|
|
324
338
|
|
|
325
|
-
def calc_vwr0(eq_days: np.array, sdev_max=2.0, tau=0.20) -> float:
|
|
326
|
-
eq = eq_days #.to_numpy()
|
|
327
|
-
eq_0 = eq_days.shift().to_numpy()
|
|
328
|
-
|
|
329
|
-
try:
|
|
330
|
-
nlrtot = eq[-1] / eq[0]
|
|
331
|
-
except ZeroDivisionError:
|
|
332
|
-
rtot = float('-inf')
|
|
333
|
-
else:
|
|
334
|
-
if nlrtot <= 0.0:
|
|
335
|
-
rtot = float('-inf')
|
|
336
|
-
else:
|
|
337
|
-
rtot = math.log(nlrtot)
|
|
338
|
-
|
|
339
|
-
ravg = rtot / len(eq)
|
|
340
|
-
rnorm = math.expm1(ravg * 252)
|
|
341
|
-
rnorm100 = rnorm * 100.0
|
|
342
|
-
|
|
343
|
-
dts = []
|
|
344
|
-
for n, zip_data in enumerate(zip(eq_0, eq), 0):
|
|
345
|
-
eq0, eq1 = zip_data
|
|
346
|
-
if (n > 0):
|
|
347
|
-
_v = (eq0 * math.exp(ravg * n))
|
|
348
|
-
if _v != 0:
|
|
349
|
-
dt = eq1 / (eq0 * math.exp(ravg * n)) - 1.0
|
|
350
|
-
dts.append(dt)
|
|
351
|
-
else:
|
|
352
|
-
dts.append(0.0)
|
|
353
|
-
|
|
354
|
-
sdev_p = np.array(dts).std(ddof=True)
|
|
355
|
-
vwr = rnorm100 * (1.0 - pow(sdev_p / sdev_max, tau))
|
|
356
|
-
return vwr
|
|
357
|
-
|
|
358
339
|
# calc VariabilityWeightedReturn
|
|
359
340
|
# See:
|
|
360
341
|
# - https://www.crystalbull.com/sharpe-ratio-better-with-log-returns/
|
backtrader_next/order.py
CHANGED
|
@@ -283,6 +283,21 @@ class OrderBase(with_metaclass(MetaParams, object)):
|
|
|
283
283
|
self._plimit = val
|
|
284
284
|
|
|
285
285
|
plimit = property(_getplimit, _setplimit)
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def owner(self):
|
|
289
|
+
if self._owner_classname and not self.p.owner:
|
|
290
|
+
class_name = self._owner_classname
|
|
291
|
+
for s in self.broker.cerebro.runningstrategies:
|
|
292
|
+
if s.__class__.__name__ == class_name:
|
|
293
|
+
self.p.owner = s
|
|
294
|
+
self._owner_classname = None
|
|
295
|
+
break
|
|
296
|
+
return self.p.owner
|
|
297
|
+
|
|
298
|
+
@owner.setter
|
|
299
|
+
def owner(self, value):
|
|
300
|
+
self.p.owner = value
|
|
286
301
|
|
|
287
302
|
def __getattr__(self, name):
|
|
288
303
|
# Return attr from params if not found in order
|
|
@@ -323,6 +338,9 @@ class OrderBase(with_metaclass(MetaParams, object)):
|
|
|
323
338
|
self.info = AutoOrderedDict()
|
|
324
339
|
self.comminfo = None
|
|
325
340
|
self.triggered = False
|
|
341
|
+
self.parent_ref = None
|
|
342
|
+
self._owner_classname = None
|
|
343
|
+
self._restored = False
|
|
326
344
|
|
|
327
345
|
self._active = self.parent is None
|
|
328
346
|
self.status = Order.Created
|
|
@@ -337,10 +355,10 @@ class OrderBase(with_metaclass(MetaParams, object)):
|
|
|
337
355
|
|
|
338
356
|
# Set a reference price if price is not set using
|
|
339
357
|
# the close price
|
|
340
|
-
pclose = self.data.close[0] if not self.p.simulated else self.price
|
|
358
|
+
pclose = self.data.close[0] if not self.p.simulated and not self._restored else self.price
|
|
341
359
|
price = pclose if not self.price and not self.pricelimit else self.price
|
|
342
360
|
|
|
343
|
-
dcreated = self.data.datetime[0] if not self.p.simulated else 0.0
|
|
361
|
+
dcreated = self.data.datetime[0] if not self.p.simulated and not self._restored else 0.0
|
|
344
362
|
self.created = OrderData(dt=dcreated,
|
|
345
363
|
size=self.size,
|
|
346
364
|
price=price,
|
|
@@ -382,7 +400,7 @@ class OrderBase(with_metaclass(MetaParams, object)):
|
|
|
382
400
|
else: # assume float
|
|
383
401
|
valid = self.data.datetime[0] + self.valid
|
|
384
402
|
|
|
385
|
-
if not self.p.simulated:
|
|
403
|
+
if not self.p.simulated and not self._restored:
|
|
386
404
|
# provisional end-of-session
|
|
387
405
|
# get next session end
|
|
388
406
|
dtime = self.data.datetime.datetime(0)
|
|
@@ -655,52 +673,80 @@ class Order(OrderBase):
|
|
|
655
673
|
'price': v.price,
|
|
656
674
|
'pricelimit': v.pricelimit,
|
|
657
675
|
'remsize': v.remsize,
|
|
676
|
+
'pclose': v.pclose,
|
|
658
677
|
'trailamount': v.trailamount,
|
|
659
678
|
'trailpercent': v.trailpercent,
|
|
679
|
+
'value': v.value,
|
|
680
|
+
'comm': v.comm,
|
|
681
|
+
'margin': v.margin,
|
|
682
|
+
'pnl': v.pnl,
|
|
683
|
+
'psize': v.psize,
|
|
684
|
+
'pprice': v.pprice,
|
|
660
685
|
}
|
|
661
|
-
elif k in ('broker', 'data'):
|
|
686
|
+
elif k in ('broker', 'data','params','comminfo','info',):
|
|
662
687
|
# handled below
|
|
663
688
|
pass
|
|
664
|
-
elif k
|
|
665
|
-
odict[
|
|
689
|
+
elif k == 'parent':
|
|
690
|
+
odict['parent'] = None
|
|
691
|
+
if v is not None:
|
|
692
|
+
odict['parent_ref'] = v.ref
|
|
666
693
|
# handled below
|
|
667
|
-
|
|
694
|
+
elif k in ('p',):
|
|
695
|
+
odict['params'] = vars(v).copy()
|
|
696
|
+
odict['params']['_owner_classname'] = v.owner.__class__.__qualname__ if v.owner else None
|
|
697
|
+
odict['params']['owner'] = None
|
|
698
|
+
odict['params']['data'] = v.data._name if v.data else None
|
|
668
699
|
else:
|
|
669
700
|
odict[k] = v
|
|
701
|
+
odict['ordtype'] = self.ordtype
|
|
670
702
|
return odict
|
|
671
703
|
|
|
672
704
|
@classmethod
|
|
673
|
-
def from_dict(cls, odict):
|
|
705
|
+
def from_dict(cls, params, odict):
|
|
674
706
|
'''Loads the order data from a dictionary representation'''
|
|
707
|
+
odict['_restored'] = True
|
|
675
708
|
ordtype = odict.get('ordtype', None)
|
|
676
709
|
ord = None
|
|
677
710
|
if ordtype is None:
|
|
678
|
-
|
|
711
|
+
ord = Order(**params)
|
|
679
712
|
if ordtype == cls.Buy:
|
|
680
|
-
ord = BuyOrder()
|
|
713
|
+
ord = BuyOrder(**params)
|
|
681
714
|
elif ordtype == cls.Sell:
|
|
682
|
-
ord = SellOrder()
|
|
715
|
+
ord = SellOrder(**params)
|
|
683
716
|
else:
|
|
684
|
-
ord = Order()
|
|
717
|
+
ord = Order(**params)
|
|
685
718
|
|
|
686
719
|
for key, val in odict.items():
|
|
687
|
-
if key
|
|
688
|
-
|
|
720
|
+
if key in ('created', 'executed'):
|
|
721
|
+
odata = OrderData(
|
|
689
722
|
dt=val.get('dt', None),
|
|
690
723
|
size=val.get('size', 0),
|
|
691
724
|
price=val.get('price', 0.0),
|
|
692
725
|
pricelimit=val.get('pricelimit', 0.0),
|
|
726
|
+
remsize=val.get('remsize', 0),
|
|
727
|
+
pclose=val.get('pclose', 0.0),
|
|
693
728
|
trailamount=val.get('trailamount', 0.0),
|
|
694
729
|
trailpercent=val.get('trailpercent', 0.0),
|
|
695
730
|
)
|
|
731
|
+
odata.value = val.get('value', 0.0)
|
|
732
|
+
odata.comm = val.get('comm', 0.0)
|
|
733
|
+
odata.margin = val.get('margin', None)
|
|
734
|
+
odata.pnl = val.get('pnl', 0.0)
|
|
735
|
+
odata.psize = val.get('psize', 0)
|
|
736
|
+
odata.pprice = val.get('pprice', 0.0)
|
|
737
|
+
if key == 'created':
|
|
738
|
+
ord.created = odata
|
|
739
|
+
else:
|
|
740
|
+
ord.executed = odata
|
|
741
|
+
elif key in ('ordtype', 'params'):
|
|
742
|
+
pass # already handled
|
|
696
743
|
elif key == 'info':
|
|
697
744
|
for ik, iv in val.items():
|
|
698
745
|
ord.info[ik] = iv
|
|
699
746
|
elif key == 'comminfo':
|
|
700
|
-
ord.comminfo =
|
|
747
|
+
ord.comminfo = None
|
|
701
748
|
elif key == 'parent':
|
|
702
|
-
|
|
703
|
-
ord.parent = None #??TODO
|
|
749
|
+
ord.parent = None
|
|
704
750
|
elif hasattr(ord, key):
|
|
705
751
|
setattr(ord, key, val)
|
|
706
752
|
return ord
|
backtrader_next/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backtrader_next
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Live Trading and backtesting platform in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/smalinin/backtrader_next
|
|
6
6
|
Project-URL: Source, https://github.com/smalinin/backtrader_next
|
|
@@ -37,6 +37,7 @@ Live Trading and backtesting platform written in Python.
|
|
|
37
37
|
```
|
|
38
38
|
pip install backtrader-next
|
|
39
39
|
```
|
|
40
|
+
|
|
40
41
|
## History
|
|
41
42
|
Package is based on [backtrader](https://github.com/mementum/backtrader)
|
|
42
43
|
|
|
@@ -51,6 +52,21 @@ Changes:
|
|
|
51
52
|
- Detailed results
|
|
52
53
|
- Interactive visualizations
|
|
53
54
|
|
|
55
|
+
## Performance
|
|
56
|
+
|
|
57
|
+
Performance comparison using the [perf_compare](https://github.com/smalinin/backtrader_next/tree/master/examples/3_perf_compare) benchmark.
|
|
58
|
+
|
|
59
|
+
- **Backtrader-next** using an optimized **PandasData** feed
|
|
60
|
+
- [Backtrader](https://github.com/mementum/backtrader) with **PandasData** feed
|
|
61
|
+
- [Backtesting.py](https://github.com/kernc/backtesting.py)
|
|
62
|
+
|
|
63
|
+
| Framework | Execution Time | Relative Speed |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Backtesting | 2.95 sec | 14.3x faster than Backtrader |
|
|
66
|
+
| Backtrader-next | 12.33 sec | 3.4x faster than Backtrader |
|
|
67
|
+
| Backtrader | 42.25 sec | Baseline |
|
|
68
|
+
|
|
69
|
+
|
|
54
70
|
## Here a snippet of a Simple Moving Average CrossOver.
|
|
55
71
|
```python
|
|
56
72
|
import pandas as pd
|
|
@@ -17,7 +17,7 @@ backtrader_next/lineseries.py,sha256=-aNlK6FiOm47-cfyH3oIUDI3awsyEmbyAyAg_o-rETA
|
|
|
17
17
|
backtrader_next/mathsupport.py,sha256=jc2fTjaf3dWzQ4ZV-ypvVPrHnKnoJbrWLHd-pIhdQ2w,1923
|
|
18
18
|
backtrader_next/metabase.py,sha256=1g18zfwPOQQlL0MVEYI2pQ-W-FQvfJpG071a_Xbn-N4,11136
|
|
19
19
|
backtrader_next/observer.py,sha256=xlR7JCYgEcZuwpgLbORPVccRD6cA5RhlkXAfKjVQjdY,2255
|
|
20
|
-
backtrader_next/order.py,sha256=
|
|
20
|
+
backtrader_next/order.py,sha256=H2pfRnxDwD9eOq0HdirDBKkzaQ3D_VW8axA5YcSMazk,26866
|
|
21
21
|
backtrader_next/position.py,sha256=odBPNNkWVRHZnNY03SqP0TSlhP1I8xoCMHJCuS2rH7E,7445
|
|
22
22
|
backtrader_next/resamplerfilter.py,sha256=z0hqTqbj4nj7gWQPR9qtBk9Iqz8wcqgF3iqL04cMJdA,26141
|
|
23
23
|
backtrader_next/signal.py,sha256=WwhQUu9L35209K3NEv343dvd57-gBGow1yN9VbmvqPM,1899
|
|
@@ -28,13 +28,13 @@ backtrader_next/talib.py,sha256=Q9t2V-jgp-7I9vvPg2BuCJi73GOa7B2LfMrAygPi2nc,9100
|
|
|
28
28
|
backtrader_next/timer.py,sha256=WrY5dpyXpGUOEeOtr_3pjiOldQyiUcXQTBpl8Jd2QCY,7578
|
|
29
29
|
backtrader_next/trade.py,sha256=3AABrbLCxsMubZfenHyONKDCY6j3e-d7bk0ejQPvD8g,11633
|
|
30
30
|
backtrader_next/tradingcal.py,sha256=X4VQ0dojoKgWoOVTd8aYTyvZYiFZV1TALrQ9sWzb9Rg,9903
|
|
31
|
-
backtrader_next/version.py,sha256=
|
|
31
|
+
backtrader_next/version.py,sha256=rDIJbKzLj-Vi62w32puV0UUISjnN3CTPF8qsGcsi-uA,1105
|
|
32
32
|
backtrader_next/writer.py,sha256=eDyz3xRsDp1i8WIZrXou6MZ80QHpYBzkf_DAqPGOcAw,7712
|
|
33
33
|
backtrader_next/analyzers/__init__.py,sha256=krHAQhnqyHj0MKGyTIDNqXqqVUTX7dnQeAB1eRwcD9I,1568
|
|
34
34
|
backtrader_next/analyzers/annualreturn.py,sha256=iIuQAE0nacVEPMyc1JUqIsfkWOG_8kwW104FlXvYd0s,2784
|
|
35
35
|
backtrader_next/analyzers/calmar.py,sha256=eyfIxodGigJ_BgAhDKC8R9SctGsdUMK9K0WagFVviTM,3826
|
|
36
36
|
backtrader_next/analyzers/drawdown.py,sha256=cHE5_SMuxYQN7WXDQ9SOyEWZcXRWUV_1QJuqHUcKV1w,6523
|
|
37
|
-
backtrader_next/analyzers/eq.py,sha256=
|
|
37
|
+
backtrader_next/analyzers/eq.py,sha256=SKd-0jNiBXTnelocjewaH-bGoe5WfG-OWYrH4fiSiho,15245
|
|
38
38
|
backtrader_next/analyzers/leverage.py,sha256=XjEp6AvSJuALYyIFCLX9U1nZohJ9KmOQmHTxIT4myLY,2402
|
|
39
39
|
backtrader_next/analyzers/logreturnsrolling.py,sha256=DJimm1ywT2moXVgu27zB-hOrtyA5s1hkQ_et5PYlCwU,5025
|
|
40
40
|
backtrader_next/analyzers/periodstats.py,sha256=-i1_wTtsLn-pBNDBpYQSdyx-2zxFmsGhaczTSgW9N0g,3570
|
|
@@ -201,7 +201,7 @@ backtrader_next/utils/dateintern.py,sha256=VcFWhMw_NazuVMHSvSueNgMxRqiEgu5YmRnz5
|
|
|
201
201
|
backtrader_next/utils/flushfile.py,sha256=ekrNdFCs0j4HwRRxho-Fnl0XZBCrpd0uHyq02kV2wjE,1588
|
|
202
202
|
backtrader_next/utils/ordereddefaultdict.py,sha256=8y7FDnqI1mTcr1NwVfgqsaK-42XwC76Mu_iYRVvLmLM,2091
|
|
203
203
|
backtrader_next/utils/py3.py,sha256=hSoodWo9-eUXzUCcLoQWPtTR51QxD-m01QzZNcuK9A8,2416
|
|
204
|
-
backtrader_next-2.
|
|
205
|
-
backtrader_next-2.
|
|
206
|
-
backtrader_next-2.
|
|
207
|
-
backtrader_next-2.
|
|
204
|
+
backtrader_next-2.2.1.dist-info/METADATA,sha256=wk1gzBNxsY6wn3aalMx4YIkcOCNt8oW0qmOPXXRd5D4,11359
|
|
205
|
+
backtrader_next-2.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
206
|
+
backtrader_next-2.2.1.dist-info/licenses/LICENSE,sha256=4cCtcomD2KVzNeUs8QZPGv_R1FQXPYzr0-2LSnK0hwQ,35121
|
|
207
|
+
backtrader_next-2.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|