Still stuck...
I gave up the avenue I tried witht he HoldAllStrategy of the previous post.
instead I tried to understand when and therefore where I have to filter out the stocks going to next().
Strategy
class Sur(bt.Strategy):
params = dict(
selcperc=0.10, # percentage of stocks to select from the universe
rperiod=1, # period for the returns calculation, default 1 period
vperiod=55, # lookback period for volatility - default 36 periods
mperiod=195, # lookback period for momentum - default 90 periods
momentum=Momentum, # parametrize the momentum and its period
reserve=globalparams["reserve"], # 5% reserve capital
monthdays=[1],
monthcarry=True,
when=bt.timer.SESSION_START,
benchmarkstop=False, # If true, no stocks will be bought and no rebalancing will be done if benchmark is below SMAperiod
SMAperiod=200,
benchmark_bond=True, # Sell all Stocks and buy Bonds
jump_momentum=True, # If true, after a time of jump_one (30 days x jump_one) in every month, all the money will be directed to the best performing stock. Rule for that:
# In Excel, this is a 0.6 x month return of fund with best past 3 month return plus 0.4 x return of fund with best return, month to date.
jump_one=0.6,
printlog=True,
)
def __init__(self):
self.bench = self.data0
self.bond = self.data1
self.stocks = self.datas[2:]
# calculate 1st the amount of stocks that will be selected
self.selnum = int(len(self.stocks) * self.p.selcperc)
# allocation perc per stock
# reserve kept to make sure orders are not rejected due to
# margin. Prices are calculated when known (close), but orders can only
# be executed next day (opening price). Price can gap upwards
self.perctarget = (1.0 - self.p.reserve) / self.selnum
# This is the set up of the timer that makes the strategy being executed at the given time
self.add_timer(
when=self.p.when,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry
)
self.stocks_len = []
jump = True
# returns, volatilities and momentums
rs = [bt.ind.PctChange(d, period=self.p.rperiod) for d in self.stocks]
vs = [bt.ind.StdDev(ret, period=self.p.vperiod) for ret in rs]
#ms = [bt.ind.ROC(d, period=self.p.mperiod) for d in self.datas]
ms = [self.p.momentum(d, period=self.p.mperiod) for d in self.stocks]
self.bench_sma = bt.ind.SMA(self.data0, period=self.p.SMAperiod)
# simple rank formula: (momentum * net payout) / volatility
# the highest ranked: low vol, large momentum, large payout
self.ranks = {d: m / v for d, v, m in zip(self.stocks, vs, ms)}
#TODO: does it perform better without the volatility?
self.bench_filter = self.bench < self.bench_sma
def log(self, arg):
if self.p.printlog:
print('{} {}'.format(self.datetime.date(), arg))
# This is the function using the timer to execute the rebalance
def notify_timer(self, timer, when, *args, **kwargs):
print('strategy notify_timer with tid {}, when {} _getminperstatus {}'.
format(timer.p.tid, when, int(self._getminperstatus())))
print("timer")
if self._getminperstatus() < 0:
self.rebalance()
def nextstart(self):
self.ranks_filter = self.ranks
print("nextstart")
self.next()
def prenext(self):
self.stocks_len = [d for d in self.stocks if len(d)]
self.ranks_filter = dict(zip(self.stocks_len, [self.ranks[k] for k in self.stocks_len]))
self.next()
def next(self):
print("next")
pass # must be filled with a pass
# Actual order giving by a ranking takes place here
def rebalance(self):
print("rebalance")
#if jump == True:
# Enter Jump Code here
# sort data and current rank
ranks = sorted(
self.ranks_filter.items(), # get the (d, rank), pair
key=lambda x: x[1][0], # use rank (elem 1) and current time "0"
reverse=True, # highest ranked 1st ... please
)
# put top ranked in dict with data as key to test for presence
rtop = dict(ranks[:self.selnum])
# For logging purposes of stocks leaving the portfolio
rbot = dict(ranks[self.selnum:])
# prepare quick lookup list of stocks currently holding a position
posdata = [d for d, pos in self.getpositions().items() if pos]
if self.p.benchmarkstop:
for d in (d for d in posdata):
if "Bond" == d._name and self.bench_filter:
return
else:
if "Bond" == d._name and not self.bench_filter:
self.order_target_percent("Bond", target=0.0)
self.log('Leave {} due to end of down period'.format(d._name))
return
# Triple Momentum: If Benchmark index is below SMA, nothing will be bought or rebalanced
if self.p.benchmarkstop:
if self.bench_filter:
#print('SMA {} - Bench {}'.format(self.bench_sma[0], self.bench[0]))
if self.p.benchmark_bond:
for d in posdata:
self.log('Leave {} due to switch to Bonds'.format(d._name))
self.order_target_percent(d, target=0.0)
self.order_target_percent("Bond", target=0.95)
self.log('Buy Bond')
bond_flag = True
return #Code stops here and skips rebalancing und buying
# remove those no longer top ranked
# do this first to issue sell orders and free cash
for d in (d for d in posdata if d not in rtop):
self.log('Leave {} - Rank {:.2f}'.format(d._name, rbot[d][0]))
self.order_target_percent(d, target=0.0)
# rebalance those already top ranked and still there
for d in (d for d in posdata if d in rtop):
self.log('Rebal {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
self.order_target_percent(d, target=self.perctarget)
del rtop[d] # remove it, to simplify next iteration
# issue a target order for the newly top ranked stocks
# do this last, as this will generate buy orders consuming cash
for d in rtop:
self.log('Enter {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
self.order_target_percent(d, target=self.perctarget)
def stop(self):
pnl = round(self.broker.getvalue() - globalparams["cash"],2)
print('Final PnL: {}'.format(
pnl))
Error Message
strategy notify_timer with tid 0, when 2014-11-03 00:00:00 _getminperstatus 199
timer
next
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-7-8fb965d11696> in <module>
312 # <<<Execute starting section>>>
313 if __name__ == '__main__':
--> 314 run()
<ipython-input-7-8fb965d11696> in run(args)
284 timeframe=bt.TimeFrame.NoTimeFrame)
285
--> 286 results = cerebro.run(maxcpus=1)#maxcpu=1 otherwise pickling multiprocessing errors
287
288 # <<<Performance analysing section section>>>
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\cerebro.py in run(self, **kwargs)
1125 # let's skip process "spawning"
1126 for iterstrat in iterstrats:
-> 1127 runstrat = self.runstrategies(iterstrat)
1128 self.runstrats.append(runstrat)
1129 if self._dooptimize:
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\cerebro.py in runstrategies(self, iterstrat, predata)
1296 self._runnext_old(runstrats)
1297 else:
-> 1298 self._runnext(runstrats)
1299
1300 for strat in runstrats:
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\cerebro.py in _runnext(self, runstrats)
1628 self._check_timers(runstrats, dt0, cheat=False)
1629 for strat in runstrats:
-> 1630 strat._next()
1631 if self._event_stop: # stop if requested
1632 return
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\strategy.py in _next(self)
348
349 minperstatus = self._getminperstatus()
--> 350 self._next_analyzers(minperstatus)
351 self._next_observers(minperstatus)
352
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\strategy.py in _next_analyzers(self, minperstatus, once)
386 analyzer._nextstart() # only called for the 1st value
387 else:
--> 388 analyzer._prenext()
389
390 def _settz(self, tz):
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\analyzer.py in _prenext(self)
148 def _prenext(self):
149 for child in self._children:
--> 150 child._prenext()
151
152 self.prenext()
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\analyzer.py in _prenext(self)
150 child._prenext()
151
--> 152 self.prenext()
153
154 def _notify_cashvalue(self, cash, value):
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\analyzer.py in prenext(self)
227 The default behavior for an analyzer is to invoke ``next``
228 '''
--> 229 self.next()
230
231 def nextstart(self):
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\analyzers\positions.py in next(self)
76
77 def next(self):
---> 78 pvals = [self.strategy.broker.get_value([d]) for d in self.datas]
79 if self.p.cash:
80 pvals.append(self.strategy.broker.get_cash())
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\analyzers\positions.py in <listcomp>(.0)
76
77 def next(self):
---> 78 pvals = [self.strategy.broker.get_value([d]) for d in self.datas]
79 if self.p.cash:
80 pvals.append(self.strategy.broker.get_cash())
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\brokers\bbroker.py in get_value(self, datas, mkt, lever)
413 return self._value if not lever else self._valuelever
414
--> 415 return self._get_value(datas=datas, lever=lever)
416
417 getvalue = get_value
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\brokers\bbroker.py in _get_value(self, datas, lever)
437 dvalue = comminfo.getvalue(position, data.close[0])
438 else:
--> 439 dvalue = comminfo.getvaluesize(position.size, data.close[0])
440
441 dunrealized = comminfo.profitandloss(position.size, position.price,
c:\users\mmd\appdata\local\programs\python\python36\lib\site-packages\backtrader\linebuffer.py in __getitem__(self, ago)
161
162 def __getitem__(self, ago):
--> 163 return self.array[self.idx + ago]
164
165 def get(self, ago=0, size=1):
IndexError: array index out of range
So here I think the issue is something that doesn't have data gets into next.
But since no prenext or nextstart is printed, I have no clue how to filter the inputs of next before next starts...