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.
Files changed (159) hide show
  1. backtrader/__init__.py +88 -0
  2. backtrader/analyzer.py +444 -0
  3. backtrader/analyzers/__init__.py +40 -0
  4. backtrader/analyzers/annualreturn.py +86 -0
  5. backtrader/analyzers/calmar.py +115 -0
  6. backtrader/analyzers/drawdown.py +190 -0
  7. backtrader/analyzers/leverage.py +67 -0
  8. backtrader/analyzers/logreturnsrolling.py +138 -0
  9. backtrader/analyzers/periodstats.py +109 -0
  10. backtrader/analyzers/positions.py +83 -0
  11. backtrader/analyzers/returns.py +153 -0
  12. backtrader/analyzers/sharpe.py +216 -0
  13. backtrader/analyzers/sqn.py +84 -0
  14. backtrader/analyzers/timereturn.py +140 -0
  15. backtrader/analyzers/tradeanalyzer.py +205 -0
  16. backtrader/analyzers/transactions.py +102 -0
  17. backtrader/analyzers/vwr.py +171 -0
  18. backtrader/broker.py +199 -0
  19. backtrader/brokers/__init__.py +8 -0
  20. backtrader/brokers/bbroker.py +1287 -0
  21. backtrader/btrun/__init__.py +22 -0
  22. backtrader/btrun/btrun.py +872 -0
  23. backtrader/cerebro.py +1771 -0
  24. backtrader/comminfo.py +322 -0
  25. backtrader/commissions/__init__.py +50 -0
  26. backtrader/dataseries.py +230 -0
  27. backtrader/errors.py +52 -0
  28. backtrader/feed.py +828 -0
  29. backtrader/feeds/__init__.py +16 -0
  30. backtrader/feeds/btcsv.py +61 -0
  31. backtrader/feeds/chainer.py +102 -0
  32. backtrader/feeds/csvgeneric.py +157 -0
  33. backtrader/feeds/mt4csv.py +50 -0
  34. backtrader/feeds/pandafeed.py +264 -0
  35. backtrader/feeds/rollover.py +203 -0
  36. backtrader/feeds/sierrachart.py +37 -0
  37. backtrader/feeds/yahoo.py +362 -0
  38. backtrader/fillers.py +112 -0
  39. backtrader/filters/__init__.py +32 -0
  40. backtrader/filters/bsplitter.py +108 -0
  41. backtrader/filters/calendardays.py +120 -0
  42. backtrader/filters/datafiller.py +173 -0
  43. backtrader/filters/datafilter.py +72 -0
  44. backtrader/filters/daysteps.py +83 -0
  45. backtrader/filters/heikinashi.py +52 -0
  46. backtrader/filters/renko.py +136 -0
  47. backtrader/filters/session.py +244 -0
  48. backtrader/flt.py +50 -0
  49. backtrader/functions.py +258 -0
  50. backtrader/indicator.py +158 -0
  51. backtrader/indicators/__init__.py +88 -0
  52. backtrader/indicators/accdecoscillator.py +57 -0
  53. backtrader/indicators/aroon.py +205 -0
  54. backtrader/indicators/atr.py +124 -0
  55. backtrader/indicators/awesomeoscillator.py +62 -0
  56. backtrader/indicators/basicops.py +520 -0
  57. backtrader/indicators/bollinger.py +85 -0
  58. backtrader/indicators/cci.py +71 -0
  59. backtrader/indicators/contrib/__init__.py +26 -0
  60. backtrader/indicators/contrib/vortex.py +54 -0
  61. backtrader/indicators/crossover.py +139 -0
  62. backtrader/indicators/dema.py +89 -0
  63. backtrader/indicators/deviation.py +114 -0
  64. backtrader/indicators/directionalmove.py +392 -0
  65. backtrader/indicators/dma.py +81 -0
  66. backtrader/indicators/dpo.py +67 -0
  67. backtrader/indicators/dv2.py +52 -0
  68. backtrader/indicators/ema.py +56 -0
  69. backtrader/indicators/envelope.py +133 -0
  70. backtrader/indicators/hadelta.py +69 -0
  71. backtrader/indicators/heikinashi.py +88 -0
  72. backtrader/indicators/hma.py +68 -0
  73. backtrader/indicators/hurst.py +94 -0
  74. backtrader/indicators/ichimoku.py +91 -0
  75. backtrader/indicators/kama.py +83 -0
  76. backtrader/indicators/kst.py +89 -0
  77. backtrader/indicators/lrsi.py +109 -0
  78. backtrader/indicators/mabase.py +89 -0
  79. backtrader/indicators/macd.py +90 -0
  80. backtrader/indicators/momentum.py +127 -0
  81. backtrader/indicators/ols.py +131 -0
  82. backtrader/indicators/oscillator.py +132 -0
  83. backtrader/indicators/percentchange.py +44 -0
  84. backtrader/indicators/percentrank.py +40 -0
  85. backtrader/indicators/pivotpoint.py +277 -0
  86. backtrader/indicators/prettygoodoscillator.py +65 -0
  87. backtrader/indicators/priceoscillator.py +127 -0
  88. backtrader/indicators/psar.py +168 -0
  89. backtrader/indicators/rmi.py +67 -0
  90. backtrader/indicators/rsi.py +242 -0
  91. backtrader/indicators/sma.py +47 -0
  92. backtrader/indicators/smma.py +61 -0
  93. backtrader/indicators/stochastic.py +156 -0
  94. backtrader/indicators/trix.py +92 -0
  95. backtrader/indicators/tsi.py +73 -0
  96. backtrader/indicators/ultimateoscillator.py +80 -0
  97. backtrader/indicators/vortex.py +55 -0
  98. backtrader/indicators/williams.py +90 -0
  99. backtrader/indicators/wma.py +56 -0
  100. backtrader/indicators/zlema.py +53 -0
  101. backtrader/indicators/zlind.py +93 -0
  102. backtrader/linebuffer.py +829 -0
  103. backtrader/lineiterator.py +491 -0
  104. backtrader/lineroot.py +358 -0
  105. backtrader/lineseries.py +641 -0
  106. backtrader/mathsupport.py +63 -0
  107. backtrader/metabase.py +326 -0
  108. backtrader/observer.py +64 -0
  109. backtrader/observers/__init__.py +32 -0
  110. backtrader/observers/benchmark.py +117 -0
  111. backtrader/observers/broker.py +143 -0
  112. backtrader/observers/buysell.py +118 -0
  113. backtrader/observers/drawdown.py +138 -0
  114. backtrader/observers/logreturns.py +99 -0
  115. backtrader/observers/timereturn.py +85 -0
  116. backtrader/observers/trades.py +207 -0
  117. backtrader/order.py +764 -0
  118. backtrader/plot/__init__.py +39 -0
  119. backtrader/plot/finance.py +724 -0
  120. backtrader/plot/formatters.py +121 -0
  121. backtrader/plot/locator.py +290 -0
  122. backtrader/plot/multicursor.py +362 -0
  123. backtrader/plot/plot.py +970 -0
  124. backtrader/plot/scheme.py +186 -0
  125. backtrader/plot/utils.py +107 -0
  126. backtrader/position.py +201 -0
  127. backtrader/resamplerfilter.py +751 -0
  128. backtrader/signal.py +67 -0
  129. backtrader/signals/__init__.py +20 -0
  130. backtrader/sizer.py +82 -0
  131. backtrader/sizers/__init__.py +26 -0
  132. backtrader/sizers/fixedsize.py +104 -0
  133. backtrader/sizers/percents_sizer.py +84 -0
  134. backtrader/store.py +91 -0
  135. backtrader/stores/__init__.py +11 -0
  136. backtrader/strategies/__init__.py +22 -0
  137. backtrader/strategies/sma_crossover.py +73 -0
  138. backtrader/strategy.py +1831 -0
  139. backtrader/studies/__init__.py +23 -0
  140. backtrader/studies/contrib/__init__.py +26 -0
  141. backtrader/studies/contrib/fractal.py +70 -0
  142. backtrader/talib.py +236 -0
  143. backtrader/timer.py +220 -0
  144. backtrader/trade.py +349 -0
  145. backtrader/tradingcal.py +289 -0
  146. backtrader/utils/__init__.py +27 -0
  147. backtrader/utils/autodict.py +143 -0
  148. backtrader/utils/date.py +49 -0
  149. backtrader/utils/dateintern.py +267 -0
  150. backtrader/utils/flushfile.py +56 -0
  151. backtrader/utils/ordereddefaultdict.py +46 -0
  152. backtrader/version.py +24 -0
  153. backtrader/writer.py +231 -0
  154. slim_backtrader-2.0.0.dist-info/METADATA +213 -0
  155. slim_backtrader-2.0.0.dist-info/RECORD +159 -0
  156. slim_backtrader-2.0.0.dist-info/WHEEL +5 -0
  157. slim_backtrader-2.0.0.dist-info/entry_points.txt +2 -0
  158. slim_backtrader-2.0.0.dist-info/licenses/LICENSE +674 -0
  159. 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