Dear all
I am trying to implement this Dual Momentum algorithm and receive “index out of bound” Runtime Error
untime Error: IndexError : index out of bounds
at pandas._libs.util.validate_indexer
File "util.pxd" in util.pxd: line 88
(Open Stacktrace)
I suspect the issue lies in the Rebalance(), in the parts with history data slices. I try to use custom made routines for calculating percentage momentums (there are 3 momentums:
1) for the return over the last month (the most recent Close to the one of the 21st working day assuming 21 business days a month),
2) for the previous month (comparing the Close of 22nd day to the one of the 42nd day before),
3) for the period 3 month before (comparing the Close of 43rd day to the one of the 63rd day before).
But I cannot fix the bug yet. Any help would be much appreciated. The code is below.
import pandas as pd
from datetime import datetime
import numpy
class AssetClassMomentumAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2012, 1, 1)
self.SetEndDate(datetime.now())
self.InitCash = 10000
self.SetCash(self.InitCash)
self.MKT = self.AddEquity("SPY", Resolution.Daily).Symbol
self.mkt = [] # for plotting stretegy vs SPY benchmark
# the only risk off ticker to park all money during risk off regime
self.risk_off_ticker = self.AddEquity("TYD", Resolution.Hour).Symbol
# brokerage model
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.data_mom_resultant = {}
self.bil_mom_resultant = 0
self.dbb_mom_resultant = 0
# UNIVERSE FOR RISK ON MARKET REGIME
self.symbols_risk_on = ["XLF", # finance
"QQQ", # Nasdaq-100
"IJK", # mid-cap 400 growth
"IJS", # small-cap 600 value
"IJT", # small-cap 600 growth
"IJJ", # mid-cap 400 value
"SPYG", # US large-cap growth
"SPYV", # US large-cap value
"XHB" # homebuilders
"XRT", # retail
"SMH", # semiconductors
"PPH" # pharma
]
# Tickers used as indicators for Risk-On Risk-Off regimes
self.risk_off_bil = self.AddEquity("BIL", Resolution.Hour).Symbol # cash equivalent
self.risk_off_dbb = self.AddEquity("DBB", Resolution.Hour).Symbol # base metals (risk on regime)
# Subscribe to all risk on tickers
for symbol in self.symbols_risk_on:
#subscribe to a symbol
self.AddEquity(symbol, Resolution.Minute).Symbol
# warm up for 4 months each 21 business day
self.SetWarmUp(int(4*21))
# schedule for rebalance
self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", 10),
self.Rebalance)
def Rebalance(self):
if self.IsWarmingUp: return
# CALC MOMENTUM SCORES
for symbol in self.symbols_risk_on:
# if tradable
if self.Securities[symbol].IsTradable == True:
# fetch history data only for a given ticker
self.df = self.History([symbol], timedelta(days=int(4*21)), Resolution.Daily) # fetch data for the last 63 days for a given ticker
#self.Debug(str(self.Time) + str(self.df))
if not self.df.empty:
symbol_quotebar = self.df.loc[symbol]
mom_one_month = (symbol_quotebar["close"][0] - symbol_quotebar["close"][20])/symbol_quotebar["close"][20] # 1st and 21st close price
mom_second_month = (symbol_quotebar["close"][21] - symbol_quotebar["close"][41])/symbol_quotebar["close"][41] # 22nd and 42nd price
mom_three_month = (symbol_quotebar["close"][42] - symbol_quotebar["close"][62])/symbol_quotebar["close"][62] # 43rd and 63rd price
# calculate resultant MOMP and populate the dictionary
resultant_momentum = 3*mom_one_month + 2*mom_second_month + 1*mom_three_month
self.data_mom_resultant[symbol] = resultant_momentum
# current market indicators risk on/off momentums and calculating the resultant momentums
# BIL
# fetch history data last 4 months
self.df = self.History("BIL", timedelta(days=int(4*21)), Resolution.Daily)
# calc resultant mon for BIL
symbol_quotebar = self.df.loc["BIL"]
mom_one_month = (symbol_quotebar["close"][0] - symbol_quotebar["close"][20])/symbol_quotebar["close"][20] # 1st and 21st close price
mom_second_month = (symbol_quotebar["close"][21] - symbol_quotebar["close"][41])/symbol_quotebar["close"][41] # 22nd and 42nd price
mom_three_month = (symbol_quotebar["close"][42] - symbol_quotebar["close"][62])/symbol_quotebar["close"][62] # 43rd and 63rd price
# calculate resultant MOMP
bil_resultant_momentum = 3*mom_one_month + 2*mom_second_month + 1*mom_three_month
self.bil_mom_resultant = bil_resultant_momentum
# DBB
# fetch history data last 4 months
self.df = self.History("DBB", timedelta(days=int(4*21)), Resolution.Daily)
# calc resultant mon for DBB
symbol_quotebar = self.df.loc["DBB"]
mom_one_month = (symbol_quotebar["close"][0] - symbol_quotebar["close"][20])/symbol_quotebar["close"][20] # 1st and 21st close price
mom_second_month = (symbol_quotebar["close"][21] - symbol_quotebar["close"][41])/symbol_quotebar["close"][41] # 22nd and 42nd price
mom_three_month = (symbol_quotebar["close"][42] - symbol_quotebar["close"][62])/symbol_quotebar["close"][62] # 43rd and 63rd price
# calculate resultant MOMP
dbb_resultant_momentum = 3*mom_one_month + 2*mom_second_month + 1*mom_three_month
self.dbb_mom_resultant = dbb_resultant_momentum
# picking top performers by momentum by creating Series with top N rows in each Series for top performers, symbol is index, 1 column with mom
top_symbols_risk_on = pd.Series(self.data_mom_resultant).sort_values(ascending = False)[:4] # returns Series of top momentums (a series with N rows), here we take only symbols of top N performers
# Case 1: mom DBB > mom BIL (strategy is in risk on regime)
if self.dbb_mom_resultant > self.bil_mom_resultant:
# if Portfolio has open positions
if self.Portfolio.Invested:
for sec in self.Portfolio.Keys:
if sec not in top_symbols_risk_on.index.tolist(): # here and throughout try to flatten index eg pd.Series.index.tolist()
self.Liquidate(sec)
for sec in top_symbols_risk_on.index:
self.SetHoldings(sec, 1 / len(top_symbols_risk_on))
# if nothing to rebalance, i.e. open position(s) are same as suggested, continue keeping a sec as open position
else:
pass
# Portfolio is all cash, not invested
else:
# open positions from top n ETFs during risk on regime
for sec in top_symbols_risk_on.index:
self.SetHoldings(sec, 1 / len(top_symbols_risk_on))
# Case 2: mom DBB < mom BIL (strategy is in risk off regime)
if self.dbb_mom_resultant <= self.bil_mom_resultant:
# if Portfolio has open positions
if self.Portfolio.Invested:
for sec in self.Portfolio.Keys:
# all positions are closed
self.Liquidate(sec)
# buy risk off ticker using all money
self.SetHoldings(self.risk_off_ticker, 1)
# Portfolio is all cash, not invested
else:
# buy risk off ticker using all money
self.SetHoldings(self.risk_off_ticker, 1)
# just plotting strategy vs the SPY benchmark
def OnEndOfDay(self):
mkt_price = self.Securities[self.MKT].Close
self.mkt.append(mkt_price)
mkt_perf = self.InitCash * self.mkt[-1] / self.mkt[0]
self.Plot('Strategy Equity', self.MKT, mkt_perf)
account_leverage = self.Portfolio.TotalHoldingsValue / self.Portfolio.TotalPortfolioValue
self.Plot('Holdings', 'leverage', round(account_leverage, 2))
self.Plot('Holdings', 'Target Leverage', 1)
Varad Kabade
Hi Dmitry Kishkinev,
The above error occurs because the data frame does not contain enough data for the given calculation i.e. we need 62 rows to do the required calculation, but the data for the symbols XHBXRT, SMH, PPH is only 27, which means there is no prior data. To resolve this, we need to check if there are enough rows in the data frame by adding the above snippet:
refer to the attached backtest.
Best,
Varad Kabade
Vladimir
Dmitry Kishkinev,
Here is the "Weighted Momentum Indicator" from my library.
It can help you with your strategy.
I have back tested it with almost your setup.
Please accept it in memory of the great Quantopian community.
Spacetime
Hi Vladimir , Just wanted to convey my thanks to you for your contributions to QC.
Dmitry Kishkinev
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!