slim-backtrader 2.0.0__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/__init__.py +88 -0
- backtrader/analyzer.py +444 -0
- backtrader/analyzers/__init__.py +40 -0
- backtrader/analyzers/annualreturn.py +86 -0
- backtrader/analyzers/calmar.py +115 -0
- backtrader/analyzers/drawdown.py +190 -0
- backtrader/analyzers/leverage.py +67 -0
- backtrader/analyzers/logreturnsrolling.py +138 -0
- backtrader/analyzers/periodstats.py +109 -0
- backtrader/analyzers/positions.py +83 -0
- backtrader/analyzers/returns.py +153 -0
- backtrader/analyzers/sharpe.py +216 -0
- backtrader/analyzers/sqn.py +84 -0
- backtrader/analyzers/timereturn.py +140 -0
- backtrader/analyzers/tradeanalyzer.py +205 -0
- backtrader/analyzers/transactions.py +102 -0
- backtrader/analyzers/vwr.py +171 -0
- backtrader/broker.py +199 -0
- backtrader/brokers/__init__.py +8 -0
- backtrader/brokers/bbroker.py +1287 -0
- backtrader/btrun/__init__.py +22 -0
- backtrader/btrun/btrun.py +872 -0
- backtrader/cerebro.py +1771 -0
- backtrader/comminfo.py +322 -0
- backtrader/commissions/__init__.py +50 -0
- backtrader/dataseries.py +230 -0
- backtrader/errors.py +52 -0
- backtrader/feed.py +828 -0
- backtrader/feeds/__init__.py +16 -0
- backtrader/feeds/btcsv.py +61 -0
- backtrader/feeds/chainer.py +102 -0
- backtrader/feeds/csvgeneric.py +157 -0
- backtrader/feeds/mt4csv.py +50 -0
- backtrader/feeds/pandafeed.py +264 -0
- backtrader/feeds/rollover.py +203 -0
- backtrader/feeds/sierrachart.py +37 -0
- backtrader/feeds/yahoo.py +362 -0
- backtrader/fillers.py +112 -0
- backtrader/filters/__init__.py +32 -0
- backtrader/filters/bsplitter.py +108 -0
- backtrader/filters/calendardays.py +120 -0
- backtrader/filters/datafiller.py +173 -0
- backtrader/filters/datafilter.py +72 -0
- backtrader/filters/daysteps.py +83 -0
- backtrader/filters/heikinashi.py +52 -0
- backtrader/filters/renko.py +136 -0
- backtrader/filters/session.py +244 -0
- backtrader/flt.py +50 -0
- backtrader/functions.py +258 -0
- backtrader/indicator.py +158 -0
- backtrader/indicators/__init__.py +88 -0
- backtrader/indicators/accdecoscillator.py +57 -0
- backtrader/indicators/aroon.py +205 -0
- backtrader/indicators/atr.py +124 -0
- backtrader/indicators/awesomeoscillator.py +62 -0
- backtrader/indicators/basicops.py +520 -0
- backtrader/indicators/bollinger.py +85 -0
- backtrader/indicators/cci.py +71 -0
- backtrader/indicators/contrib/__init__.py +26 -0
- backtrader/indicators/contrib/vortex.py +54 -0
- backtrader/indicators/crossover.py +139 -0
- backtrader/indicators/dema.py +89 -0
- backtrader/indicators/deviation.py +114 -0
- backtrader/indicators/directionalmove.py +392 -0
- backtrader/indicators/dma.py +81 -0
- backtrader/indicators/dpo.py +67 -0
- backtrader/indicators/dv2.py +52 -0
- backtrader/indicators/ema.py +56 -0
- backtrader/indicators/envelope.py +133 -0
- backtrader/indicators/hadelta.py +69 -0
- backtrader/indicators/heikinashi.py +88 -0
- backtrader/indicators/hma.py +68 -0
- backtrader/indicators/hurst.py +94 -0
- backtrader/indicators/ichimoku.py +91 -0
- backtrader/indicators/kama.py +83 -0
- backtrader/indicators/kst.py +89 -0
- backtrader/indicators/lrsi.py +109 -0
- backtrader/indicators/mabase.py +89 -0
- backtrader/indicators/macd.py +90 -0
- backtrader/indicators/momentum.py +127 -0
- backtrader/indicators/ols.py +131 -0
- backtrader/indicators/oscillator.py +132 -0
- backtrader/indicators/percentchange.py +44 -0
- backtrader/indicators/percentrank.py +40 -0
- backtrader/indicators/pivotpoint.py +277 -0
- backtrader/indicators/prettygoodoscillator.py +65 -0
- backtrader/indicators/priceoscillator.py +127 -0
- backtrader/indicators/psar.py +168 -0
- backtrader/indicators/rmi.py +67 -0
- backtrader/indicators/rsi.py +242 -0
- backtrader/indicators/sma.py +47 -0
- backtrader/indicators/smma.py +61 -0
- backtrader/indicators/stochastic.py +156 -0
- backtrader/indicators/trix.py +92 -0
- backtrader/indicators/tsi.py +73 -0
- backtrader/indicators/ultimateoscillator.py +80 -0
- backtrader/indicators/vortex.py +55 -0
- backtrader/indicators/williams.py +90 -0
- backtrader/indicators/wma.py +56 -0
- backtrader/indicators/zlema.py +53 -0
- backtrader/indicators/zlind.py +93 -0
- backtrader/linebuffer.py +829 -0
- backtrader/lineiterator.py +491 -0
- backtrader/lineroot.py +358 -0
- backtrader/lineseries.py +641 -0
- backtrader/mathsupport.py +63 -0
- backtrader/metabase.py +326 -0
- backtrader/observer.py +64 -0
- backtrader/observers/__init__.py +32 -0
- backtrader/observers/benchmark.py +117 -0
- backtrader/observers/broker.py +143 -0
- backtrader/observers/buysell.py +118 -0
- backtrader/observers/drawdown.py +138 -0
- backtrader/observers/logreturns.py +99 -0
- backtrader/observers/timereturn.py +85 -0
- backtrader/observers/trades.py +207 -0
- backtrader/order.py +764 -0
- backtrader/plot/__init__.py +39 -0
- backtrader/plot/finance.py +724 -0
- backtrader/plot/formatters.py +121 -0
- backtrader/plot/locator.py +290 -0
- backtrader/plot/multicursor.py +362 -0
- backtrader/plot/plot.py +970 -0
- backtrader/plot/scheme.py +186 -0
- backtrader/plot/utils.py +107 -0
- backtrader/position.py +201 -0
- backtrader/resamplerfilter.py +751 -0
- backtrader/signal.py +67 -0
- backtrader/signals/__init__.py +20 -0
- backtrader/sizer.py +82 -0
- backtrader/sizers/__init__.py +26 -0
- backtrader/sizers/fixedsize.py +104 -0
- backtrader/sizers/percents_sizer.py +84 -0
- backtrader/store.py +91 -0
- backtrader/stores/__init__.py +11 -0
- backtrader/strategies/__init__.py +22 -0
- backtrader/strategies/sma_crossover.py +73 -0
- backtrader/strategy.py +1831 -0
- backtrader/studies/__init__.py +23 -0
- backtrader/studies/contrib/__init__.py +26 -0
- backtrader/studies/contrib/fractal.py +70 -0
- backtrader/talib.py +236 -0
- backtrader/timer.py +220 -0
- backtrader/trade.py +349 -0
- backtrader/tradingcal.py +289 -0
- backtrader/utils/__init__.py +27 -0
- backtrader/utils/autodict.py +143 -0
- backtrader/utils/date.py +49 -0
- backtrader/utils/dateintern.py +267 -0
- backtrader/utils/flushfile.py +56 -0
- backtrader/utils/ordereddefaultdict.py +46 -0
- backtrader/version.py +24 -0
- backtrader/writer.py +231 -0
- slim_backtrader-2.0.0.dist-info/METADATA +213 -0
- slim_backtrader-2.0.0.dist-info/RECORD +159 -0
- slim_backtrader-2.0.0.dist-info/WHEEL +5 -0
- slim_backtrader-2.0.0.dist-info/entry_points.txt +2 -0
- slim_backtrader-2.0.0.dist-info/licenses/LICENSE +674 -0
- slim_backtrader-2.0.0.dist-info/top_level.txt +1 -0
backtrader/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8; py-indent-offset:4 -*-
|
|
3
|
+
###############################################################################
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2015-2023 Daniel Rodriguez
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
from .version import __version__, __btversion__
|
|
23
|
+
|
|
24
|
+
from .errors import *
|
|
25
|
+
from . import errors as errors
|
|
26
|
+
|
|
27
|
+
from .utils import num2date, date2num, time2num, num2time
|
|
28
|
+
|
|
29
|
+
from .linebuffer import *
|
|
30
|
+
from .functions import *
|
|
31
|
+
|
|
32
|
+
from .order import *
|
|
33
|
+
from .comminfo import *
|
|
34
|
+
from .trade import *
|
|
35
|
+
from .position import *
|
|
36
|
+
|
|
37
|
+
from .store import Store
|
|
38
|
+
|
|
39
|
+
from . import broker as broker
|
|
40
|
+
from .broker import *
|
|
41
|
+
|
|
42
|
+
from .lineseries import *
|
|
43
|
+
|
|
44
|
+
from .dataseries import *
|
|
45
|
+
from .feed import *
|
|
46
|
+
from .resamplerfilter import *
|
|
47
|
+
|
|
48
|
+
from .lineiterator import *
|
|
49
|
+
from .indicator import *
|
|
50
|
+
from .analyzer import *
|
|
51
|
+
from .observer import *
|
|
52
|
+
from .sizer import *
|
|
53
|
+
from .sizers import SizerFix # old sizer for compatibility
|
|
54
|
+
from .strategy import *
|
|
55
|
+
|
|
56
|
+
from .writer import *
|
|
57
|
+
|
|
58
|
+
from .signal import *
|
|
59
|
+
|
|
60
|
+
from .cerebro import *
|
|
61
|
+
from .timer import *
|
|
62
|
+
from .flt import *
|
|
63
|
+
|
|
64
|
+
from . import utils as utils
|
|
65
|
+
|
|
66
|
+
from . import feeds as feeds
|
|
67
|
+
from . import indicators as indicators
|
|
68
|
+
from . import indicators as ind
|
|
69
|
+
from . import studies as studies
|
|
70
|
+
from . import strategies as strategies
|
|
71
|
+
from . import strategies as strats
|
|
72
|
+
from . import observers as observers
|
|
73
|
+
from . import observers as obs
|
|
74
|
+
from . import analyzers as analyzers
|
|
75
|
+
from . import commissions as commissions
|
|
76
|
+
from . import commissions as comms
|
|
77
|
+
from . import filters as filters
|
|
78
|
+
from . import signals as signals
|
|
79
|
+
from . import sizers as sizers
|
|
80
|
+
from . import stores as stores
|
|
81
|
+
from . import brokers as brokers
|
|
82
|
+
from . import timer as timer
|
|
83
|
+
|
|
84
|
+
from . import talib as talib
|
|
85
|
+
|
|
86
|
+
# Load contributed indicators and studies
|
|
87
|
+
import backtrader.indicators.contrib
|
|
88
|
+
import backtrader.studies.contrib
|
backtrader/analyzer.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8; py-indent-offset:4 -*-
|
|
3
|
+
###############################################################################
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2015-2023 Daniel Rodriguez
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
import calendar
|
|
25
|
+
from collections import OrderedDict
|
|
26
|
+
import datetime
|
|
27
|
+
import pprint as pp
|
|
28
|
+
|
|
29
|
+
import backtrader as bt
|
|
30
|
+
from backtrader import TimeFrame
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MetaAnalyzer(bt.MetaParams):
|
|
34
|
+
def donew(cls, *args, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
Intercept the strategy parameter
|
|
37
|
+
"""
|
|
38
|
+
# Create the object and set the params in place
|
|
39
|
+
_obj, args, kwargs = super(MetaAnalyzer, cls).donew(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
_obj._children = list()
|
|
42
|
+
|
|
43
|
+
_obj.strategy = strategy = bt.metabase.findowner(_obj, bt.Strategy)
|
|
44
|
+
_obj._parent = bt.metabase.findowner(_obj, Analyzer)
|
|
45
|
+
|
|
46
|
+
# Register with a master observer if created inside one
|
|
47
|
+
masterobs = bt.metabase.findowner(_obj, bt.Observer)
|
|
48
|
+
if masterobs is not None:
|
|
49
|
+
masterobs._register_analyzer(_obj)
|
|
50
|
+
|
|
51
|
+
_obj.datas = strategy.datas
|
|
52
|
+
|
|
53
|
+
# For each data add aliases: for first data: data and data0
|
|
54
|
+
if _obj.datas:
|
|
55
|
+
_obj.data = data = _obj.datas[0]
|
|
56
|
+
|
|
57
|
+
for l, line in enumerate(data.lines):
|
|
58
|
+
linealias = data._getlinealias(l)
|
|
59
|
+
if linealias:
|
|
60
|
+
setattr(_obj, "data_%s" % linealias, line)
|
|
61
|
+
setattr(_obj, "data_%d" % l, line)
|
|
62
|
+
|
|
63
|
+
for d, data in enumerate(_obj.datas):
|
|
64
|
+
setattr(_obj, "data%d" % d, data)
|
|
65
|
+
|
|
66
|
+
for l, line in enumerate(data.lines):
|
|
67
|
+
linealias = data._getlinealias(l)
|
|
68
|
+
if linealias:
|
|
69
|
+
setattr(_obj, "data%d_%s" % (d, linealias), line)
|
|
70
|
+
setattr(_obj, "data%d_%d" % (d, l), line)
|
|
71
|
+
|
|
72
|
+
_obj.create_analysis()
|
|
73
|
+
|
|
74
|
+
# Return to the normal chain
|
|
75
|
+
return _obj, args, kwargs
|
|
76
|
+
|
|
77
|
+
def dopostinit(cls, _obj, *args, **kwargs):
|
|
78
|
+
_obj, args, kwargs = super(MetaAnalyzer, cls).dopostinit(_obj, *args, **kwargs)
|
|
79
|
+
|
|
80
|
+
if _obj._parent is not None:
|
|
81
|
+
_obj._parent._register(_obj)
|
|
82
|
+
|
|
83
|
+
# Return to the normal chain
|
|
84
|
+
return _obj, args, kwargs
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Analyzer(metaclass=MetaAnalyzer):
|
|
88
|
+
"""Analyzer base class. All analyzers are subclass of this one
|
|
89
|
+
|
|
90
|
+
An Analyzer instance operates in the frame of a strategy and provides an
|
|
91
|
+
analysis for that strategy.
|
|
92
|
+
|
|
93
|
+
Automagically set member attributes:
|
|
94
|
+
|
|
95
|
+
- ``self.strategy`` (giving access to the *strategy* and anything
|
|
96
|
+
accessible from it)
|
|
97
|
+
|
|
98
|
+
- ``self.datas[x]`` giving access to the array of data feeds present in
|
|
99
|
+
the the system, which could also be accessed via the strategy reference
|
|
100
|
+
|
|
101
|
+
- ``self.data``, giving access to ``self.datas[0]``
|
|
102
|
+
|
|
103
|
+
- ``self.dataX`` -> ``self.datas[X]``
|
|
104
|
+
|
|
105
|
+
- ``self.dataX_Y`` -> ``self.datas[X].lines[Y]``
|
|
106
|
+
|
|
107
|
+
- ``self.dataX_name`` -> ``self.datas[X].name``
|
|
108
|
+
|
|
109
|
+
- ``self.data_name`` -> ``self.datas[0].name``
|
|
110
|
+
|
|
111
|
+
- ``self.data_Y`` -> ``self.datas[0].lines[Y]``
|
|
112
|
+
|
|
113
|
+
This is not a *Lines* object, but the methods and operation follow the same
|
|
114
|
+
design
|
|
115
|
+
|
|
116
|
+
- ``__init__`` during instantiation and initial setup
|
|
117
|
+
|
|
118
|
+
- ``start`` / ``stop`` to signal the begin and end of operations
|
|
119
|
+
|
|
120
|
+
- ``prenext`` / ``nextstart`` / ``next`` family of methods that follow
|
|
121
|
+
the calls made to the same methods in the strategy
|
|
122
|
+
|
|
123
|
+
- ``notify_trade`` / ``notify_order`` / ``notify_cashvalue`` /
|
|
124
|
+
``notify_fund`` which receive the same notifications as the equivalent
|
|
125
|
+
methods of the strategy
|
|
126
|
+
|
|
127
|
+
The mode of operation is open and no pattern is preferred. As such the
|
|
128
|
+
analysis can be generated with the ``next`` calls, at the end of operations
|
|
129
|
+
during ``stop`` and even with a single method like ``notify_trade``
|
|
130
|
+
|
|
131
|
+
The important thing is to override ``get_analysis`` to return a *dict-like*
|
|
132
|
+
object containing the results of the analysis (the actual format is
|
|
133
|
+
implementation dependent)
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
csv = True
|
|
138
|
+
|
|
139
|
+
def __len__(self):
|
|
140
|
+
"""Support for invoking ``len`` on analyzers by actually returning the
|
|
141
|
+
current length of the strategy the analyzer operates on"""
|
|
142
|
+
return len(self.strategy)
|
|
143
|
+
|
|
144
|
+
def _register(self, child):
|
|
145
|
+
self._children.append(child)
|
|
146
|
+
|
|
147
|
+
def _prenext(self):
|
|
148
|
+
for child in self._children:
|
|
149
|
+
child._prenext()
|
|
150
|
+
|
|
151
|
+
self.prenext()
|
|
152
|
+
|
|
153
|
+
def _notify_cashvalue(self, cash, value):
|
|
154
|
+
for child in self._children:
|
|
155
|
+
child._notify_cashvalue(cash, value)
|
|
156
|
+
|
|
157
|
+
self.notify_cashvalue(cash, value)
|
|
158
|
+
|
|
159
|
+
def _notify_fund(self, cash, value, fundvalue, shares):
|
|
160
|
+
for child in self._children:
|
|
161
|
+
child._notify_fund(cash, value, fundvalue, shares)
|
|
162
|
+
|
|
163
|
+
self.notify_fund(cash, value, fundvalue, shares)
|
|
164
|
+
|
|
165
|
+
def _notify_trade(self, trade):
|
|
166
|
+
for child in self._children:
|
|
167
|
+
child._notify_trade(trade)
|
|
168
|
+
|
|
169
|
+
self.notify_trade(trade)
|
|
170
|
+
|
|
171
|
+
def _notify_order(self, order):
|
|
172
|
+
for child in self._children:
|
|
173
|
+
child._notify_order(order)
|
|
174
|
+
|
|
175
|
+
self.notify_order(order)
|
|
176
|
+
|
|
177
|
+
def _nextstart(self):
|
|
178
|
+
for child in self._children:
|
|
179
|
+
child._nextstart()
|
|
180
|
+
|
|
181
|
+
self.nextstart()
|
|
182
|
+
|
|
183
|
+
def _next(self):
|
|
184
|
+
for child in self._children:
|
|
185
|
+
child._next()
|
|
186
|
+
|
|
187
|
+
self.next()
|
|
188
|
+
|
|
189
|
+
def _start(self):
|
|
190
|
+
for child in self._children:
|
|
191
|
+
child._start()
|
|
192
|
+
|
|
193
|
+
self.start()
|
|
194
|
+
|
|
195
|
+
def _stop(self):
|
|
196
|
+
for child in self._children:
|
|
197
|
+
child._stop()
|
|
198
|
+
|
|
199
|
+
self.stop()
|
|
200
|
+
|
|
201
|
+
def notify_cashvalue(self, cash, value):
|
|
202
|
+
"""Receives the cash/value notification before each next cycle"""
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
def notify_fund(self, cash, value, fundvalue, shares):
|
|
206
|
+
"""Receives the current cash, value, fundvalue and fund shares"""
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
def notify_order(self, order):
|
|
210
|
+
"""Receives order notifications before each next cycle"""
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def notify_trade(self, trade):
|
|
214
|
+
"""Receives trade notifications before each next cycle"""
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
def next(self):
|
|
218
|
+
"""Invoked for each next invocation of the strategy, once the minum
|
|
219
|
+
preiod of the strategy has been reached"""
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
def prenext(self):
|
|
223
|
+
"""Invoked for each prenext invocation of the strategy, until the minimum
|
|
224
|
+
period of the strategy has been reached
|
|
225
|
+
|
|
226
|
+
The default behavior for an analyzer is to invoke ``next``
|
|
227
|
+
"""
|
|
228
|
+
self.next()
|
|
229
|
+
|
|
230
|
+
def nextstart(self):
|
|
231
|
+
"""Invoked exactly once for the nextstart invocation of the strategy,
|
|
232
|
+
when the minimum period has been first reached
|
|
233
|
+
"""
|
|
234
|
+
self.next()
|
|
235
|
+
|
|
236
|
+
def start(self):
|
|
237
|
+
"""Invoked to indicate the start of operations, giving the analyzer
|
|
238
|
+
time to setup up needed things"""
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
def stop(self):
|
|
242
|
+
"""Invoked to indicate the end of operations, giving the analyzer
|
|
243
|
+
time to shut down needed things"""
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
def create_analysis(self):
|
|
247
|
+
"""Meant to be overriden by subclasses. Gives a chance to create the
|
|
248
|
+
structures that hold the analysis.
|
|
249
|
+
|
|
250
|
+
The default behaviour is to create a ``OrderedDict`` named ``rets``
|
|
251
|
+
"""
|
|
252
|
+
self.rets = OrderedDict()
|
|
253
|
+
|
|
254
|
+
def get_analysis(self):
|
|
255
|
+
"""Returns a *dict-like* object with the results of the analysis
|
|
256
|
+
|
|
257
|
+
The keys and format of analysis results in the dictionary is
|
|
258
|
+
implementation dependent.
|
|
259
|
+
|
|
260
|
+
It is not even enforced that the result is a *dict-like object*, just
|
|
261
|
+
the convention
|
|
262
|
+
|
|
263
|
+
The default implementation returns the default OrderedDict ``rets``
|
|
264
|
+
created by the default ``create_analysis`` method
|
|
265
|
+
|
|
266
|
+
"""
|
|
267
|
+
return self.rets
|
|
268
|
+
|
|
269
|
+
def print(self, *args, **kwargs):
|
|
270
|
+
"""Prints the results returned by ``get_analysis`` via a standard
|
|
271
|
+
``Writerfile`` object, which defaults to writing things to standard
|
|
272
|
+
output
|
|
273
|
+
"""
|
|
274
|
+
writer = bt.WriterFile(*args, **kwargs)
|
|
275
|
+
writer.start()
|
|
276
|
+
pdct = dict()
|
|
277
|
+
pdct[self.__class__.__name__] = self.get_analysis()
|
|
278
|
+
writer.writedict(pdct)
|
|
279
|
+
writer.stop()
|
|
280
|
+
|
|
281
|
+
def pprint(self, *args, **kwargs):
|
|
282
|
+
"""Prints the results returned by ``get_analysis`` using the pretty
|
|
283
|
+
print Python module (*pprint*)
|
|
284
|
+
"""
|
|
285
|
+
pp.pprint(self.get_analysis(), *args, **kwargs)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class MetaTimeFrameAnalyzerBase(Analyzer.__class__):
|
|
289
|
+
def __new__(meta, name, bases, dct):
|
|
290
|
+
# Hack to support original method name
|
|
291
|
+
if "_on_dt_over" in dct:
|
|
292
|
+
dct["on_dt_over"] = dct.pop("_on_dt_over") # rename method
|
|
293
|
+
|
|
294
|
+
return super(MetaTimeFrameAnalyzerBase, meta).__new__(meta, name, bases, dct)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class TimeFrameAnalyzerBase(Analyzer, metaclass=MetaTimeFrameAnalyzerBase):
|
|
298
|
+
params = (
|
|
299
|
+
("timeframe", None),
|
|
300
|
+
("compression", None),
|
|
301
|
+
("_doprenext", True),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def _start(self):
|
|
305
|
+
# Override to add specific attributes
|
|
306
|
+
self.timeframe = self.p.timeframe or self.data._timeframe
|
|
307
|
+
self.compression = self.p.compression or self.data._compression
|
|
308
|
+
|
|
309
|
+
self.dtcmp, self.dtkey = self._get_dt_cmpkey(datetime.datetime.min)
|
|
310
|
+
super(TimeFrameAnalyzerBase, self)._start()
|
|
311
|
+
|
|
312
|
+
def _prenext(self):
|
|
313
|
+
for child in self._children:
|
|
314
|
+
child._prenext()
|
|
315
|
+
|
|
316
|
+
if self._dt_over():
|
|
317
|
+
self.on_dt_over()
|
|
318
|
+
|
|
319
|
+
if self.p._doprenext:
|
|
320
|
+
self.prenext()
|
|
321
|
+
|
|
322
|
+
def _nextstart(self):
|
|
323
|
+
for child in self._children:
|
|
324
|
+
child._nextstart()
|
|
325
|
+
|
|
326
|
+
if self._dt_over() or not self.p._doprenext: # exec if no prenext
|
|
327
|
+
self.on_dt_over()
|
|
328
|
+
|
|
329
|
+
self.nextstart()
|
|
330
|
+
|
|
331
|
+
def _next(self):
|
|
332
|
+
for child in self._children:
|
|
333
|
+
child._next()
|
|
334
|
+
|
|
335
|
+
if self._dt_over():
|
|
336
|
+
self.on_dt_over()
|
|
337
|
+
|
|
338
|
+
self.next()
|
|
339
|
+
|
|
340
|
+
def on_dt_over(self):
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
def _dt_over(self):
|
|
344
|
+
if self.timeframe == TimeFrame.NoTimeFrame:
|
|
345
|
+
dtcmp, dtkey = sys.maxsize, datetime.datetime.max
|
|
346
|
+
else:
|
|
347
|
+
# With >= 1.9.x the system datetime is in the strategy
|
|
348
|
+
dt = self.strategy.datetime.datetime()
|
|
349
|
+
dtcmp, dtkey = self._get_dt_cmpkey(dt)
|
|
350
|
+
|
|
351
|
+
if self.dtcmp is None or dtcmp > self.dtcmp:
|
|
352
|
+
self.dtkey, self.dtkey1 = dtkey, self.dtkey
|
|
353
|
+
self.dtcmp, self.dtcmp1 = dtcmp, self.dtcmp
|
|
354
|
+
return True
|
|
355
|
+
|
|
356
|
+
return False
|
|
357
|
+
|
|
358
|
+
def _get_dt_cmpkey(self, dt):
|
|
359
|
+
if self.timeframe == TimeFrame.NoTimeFrame:
|
|
360
|
+
return None, None
|
|
361
|
+
|
|
362
|
+
if self.timeframe == TimeFrame.Years:
|
|
363
|
+
dtcmp = dt.year
|
|
364
|
+
dtkey = datetime.date(dt.year, 12, 31)
|
|
365
|
+
|
|
366
|
+
elif self.timeframe == TimeFrame.Months:
|
|
367
|
+
dtcmp = dt.year * 100 + dt.month
|
|
368
|
+
_, lastday = calendar.monthrange(dt.year, dt.month)
|
|
369
|
+
dtkey = datetime.datetime(dt.year, dt.month, lastday)
|
|
370
|
+
|
|
371
|
+
elif self.timeframe == TimeFrame.Weeks:
|
|
372
|
+
isoyear, isoweek, isoweekday = dt.isocalendar()
|
|
373
|
+
dtcmp = isoyear * 100 + isoweek
|
|
374
|
+
sunday = dt + datetime.timedelta(days=7 - isoweekday)
|
|
375
|
+
dtkey = datetime.datetime(sunday.year, sunday.month, sunday.day)
|
|
376
|
+
|
|
377
|
+
elif self.timeframe == TimeFrame.Days:
|
|
378
|
+
dtcmp = dt.year * 10000 + dt.month * 100 + dt.day
|
|
379
|
+
dtkey = datetime.datetime(dt.year, dt.month, dt.day)
|
|
380
|
+
|
|
381
|
+
else:
|
|
382
|
+
dtcmp, dtkey = self._get_subday_cmpkey(dt)
|
|
383
|
+
|
|
384
|
+
return dtcmp, dtkey
|
|
385
|
+
|
|
386
|
+
def _get_subday_cmpkey(self, dt):
|
|
387
|
+
# Calculate intraday position
|
|
388
|
+
point = dt.hour * 60 + dt.minute
|
|
389
|
+
|
|
390
|
+
if self.timeframe < TimeFrame.Minutes:
|
|
391
|
+
point = point * 60 + dt.second
|
|
392
|
+
|
|
393
|
+
if self.timeframe < TimeFrame.Seconds:
|
|
394
|
+
point = point * 1e6 + dt.microsecond
|
|
395
|
+
|
|
396
|
+
# Apply compression to update point position (comp 5 -> 200 // 5)
|
|
397
|
+
point = point // self.compression
|
|
398
|
+
|
|
399
|
+
# Move to next boundary
|
|
400
|
+
point += 1
|
|
401
|
+
|
|
402
|
+
# Restore point to the timeframe units by de-applying compression
|
|
403
|
+
point *= self.compression
|
|
404
|
+
|
|
405
|
+
# Get hours, minutes, seconds and microseconds
|
|
406
|
+
if self.timeframe == TimeFrame.Minutes:
|
|
407
|
+
ph, pm = divmod(point, 60)
|
|
408
|
+
ps = 0
|
|
409
|
+
pus = 0
|
|
410
|
+
elif self.timeframe == TimeFrame.Seconds:
|
|
411
|
+
ph, pm = divmod(point, 60 * 60)
|
|
412
|
+
pm, ps = divmod(pm, 60)
|
|
413
|
+
pus = 0
|
|
414
|
+
elif self.timeframe == TimeFrame.MicroSeconds:
|
|
415
|
+
ph, pm = divmod(point, 60 * 60 * 1e6)
|
|
416
|
+
pm, psec = divmod(pm, 60 * 1e6)
|
|
417
|
+
ps, pus = divmod(psec, 1e6)
|
|
418
|
+
|
|
419
|
+
extradays = 0
|
|
420
|
+
if ph > 23: # went over midnight:
|
|
421
|
+
extradays = ph // 24
|
|
422
|
+
ph %= 24
|
|
423
|
+
|
|
424
|
+
# moving 1 minor unit to the left to be in the boundary
|
|
425
|
+
# pm -= self.timeframe == TimeFrame.Minutes
|
|
426
|
+
# ps -= self.timeframe == TimeFrame.Seconds
|
|
427
|
+
# pus -= self.timeframe == TimeFrame.MicroSeconds
|
|
428
|
+
|
|
429
|
+
tadjust = datetime.timedelta(
|
|
430
|
+
minutes=self.timeframe == TimeFrame.Minutes,
|
|
431
|
+
seconds=self.timeframe == TimeFrame.Seconds,
|
|
432
|
+
microseconds=self.timeframe == TimeFrame.MicroSeconds,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Add extra day if present
|
|
436
|
+
if extradays:
|
|
437
|
+
dt += datetime.timedelta(days=extradays)
|
|
438
|
+
|
|
439
|
+
# Replace intraday parts with the calculated ones and update it
|
|
440
|
+
dtcmp = dt.replace(hour=ph, minute=pm, second=ps, microsecond=pus)
|
|
441
|
+
dtcmp -= tadjust
|
|
442
|
+
dtkey = dtcmp
|
|
443
|
+
|
|
444
|
+
return dtcmp, dtkey
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8; py-indent-offset:4 -*-
|
|
3
|
+
###############################################################################
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2015-2023 Daniel Rodriguez
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
# The modules below should/must define __all__ with the objects wishes
|
|
23
|
+
# or prepend an "_" (underscore) to private classes/variables
|
|
24
|
+
|
|
25
|
+
from .annualreturn import *
|
|
26
|
+
from .drawdown import *
|
|
27
|
+
from .timereturn import *
|
|
28
|
+
from .sharpe import *
|
|
29
|
+
from .tradeanalyzer import *
|
|
30
|
+
from .sqn import *
|
|
31
|
+
from .leverage import *
|
|
32
|
+
from .positions import *
|
|
33
|
+
from .transactions import *
|
|
34
|
+
from .returns import *
|
|
35
|
+
from .vwr import *
|
|
36
|
+
|
|
37
|
+
from .logreturnsrolling import *
|
|
38
|
+
|
|
39
|
+
from .calmar import *
|
|
40
|
+
from .periodstats import *
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8; py-indent-offset:4 -*-
|
|
3
|
+
###############################################################################
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2015-2023 Daniel Rodriguez
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
from collections import OrderedDict
|
|
23
|
+
|
|
24
|
+
from backtrader import Analyzer
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AnnualReturn(Analyzer):
|
|
28
|
+
"""
|
|
29
|
+
This analyzer calculates the AnnualReturns by looking at the beginning
|
|
30
|
+
and end of the year
|
|
31
|
+
|
|
32
|
+
Params:
|
|
33
|
+
|
|
34
|
+
- (None)
|
|
35
|
+
|
|
36
|
+
Member Attributes:
|
|
37
|
+
|
|
38
|
+
- ``rets``: list of calculated annual returns
|
|
39
|
+
|
|
40
|
+
- ``ret``: dictionary (key: year) of annual returns
|
|
41
|
+
|
|
42
|
+
**get_analysis**:
|
|
43
|
+
|
|
44
|
+
- Returns a dictionary of annual returns (key: year)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def stop(self):
|
|
48
|
+
# Must have stats.broker
|
|
49
|
+
cur_year = -1
|
|
50
|
+
|
|
51
|
+
value_start = 0.0
|
|
52
|
+
value_cur = 0.0
|
|
53
|
+
value_end = 0.0
|
|
54
|
+
|
|
55
|
+
self.rets = list()
|
|
56
|
+
self.ret = OrderedDict()
|
|
57
|
+
|
|
58
|
+
for i in range(len(self.data) - 1, -1, -1):
|
|
59
|
+
dt = self.data.datetime.date(-i)
|
|
60
|
+
value_cur = self.strategy.stats.broker.value[-i]
|
|
61
|
+
|
|
62
|
+
if dt.year > cur_year:
|
|
63
|
+
if cur_year >= 0:
|
|
64
|
+
annualret = (value_end / value_start) - 1.0
|
|
65
|
+
self.rets.append(annualret)
|
|
66
|
+
self.ret[cur_year] = annualret
|
|
67
|
+
|
|
68
|
+
# changing between real years, use last value as new start
|
|
69
|
+
value_start = value_end
|
|
70
|
+
else:
|
|
71
|
+
# No value set whatsoever, use the currently loaded value
|
|
72
|
+
value_start = value_cur
|
|
73
|
+
|
|
74
|
+
cur_year = dt.year
|
|
75
|
+
|
|
76
|
+
# No matter what, the last value is always the last loaded value
|
|
77
|
+
value_end = value_cur
|
|
78
|
+
|
|
79
|
+
if cur_year not in self.ret:
|
|
80
|
+
# finish calculating pending data
|
|
81
|
+
annualret = (value_end / value_start) - 1.0
|
|
82
|
+
self.rets.append(annualret)
|
|
83
|
+
self.ret[cur_year] = annualret
|
|
84
|
+
|
|
85
|
+
def get_analysis(self):
|
|
86
|
+
return self.ret
|