Hi
I tested the effect of the window lenght used for percentage change of the switching pairs. (pairs used to decide on In-OUT)
First test shows the difference between a window length of 2-800 days for the whole period from 2008 until today (one return for the whole period and variation of the window lenght)
Second test, I calculated the return with a rolling window of one year and than looked for the best performing window and its return.
Both test were done for different single pairs. IN-OUT was done with just the SPY. In the first test I switched between SPY and IEF and in the second test between SPY and Cash.
From the first test it looked that around 60 days give the best result/return for the whole period.
The second test, with a rolling window return and picking the optimal window, showed that the optimal window lenght ist ALL OVER THE PLACE….
And now? How should we decide which window length to use and to avoid fitting the window to the best historical result?
Please don't understand me wrong, I'm personally using this method for my investment and I really want to improve the method, but don't know how.
Here the result for the pair DBB/UUP. Upper graphic shows the optimal window lenght.

Following the notebook with the code:
# Make sure the plot shows up
%matplotlib inline
# Import libraries that we need
import pandas as pd
import numpy as np
import yfinance as yf
import pyfolio as pf
import matplotlib.pyplot as plt
symbols_to_use = '^VIX ^VXV SPY QQQ MDY IEF TLT DBB UUP XLY XLP SLV GLD XLI XLU FXA FXF FXC FXY SSO QLD MVV SPXL TQQQ MIDU'
# indicator from in-out Quantopian
# self.slv_sid = symbol('SLV').sid # silver
# self.gld_sid = symbol('GLD').sid # gold
# self.xli_sid = symbol('XLI').sid # Consumer Industrial Select Sector SPDR Fund
# self.xlu_sid = symbol('XLU').sid # Consumer Utility Select Sector SPDR Fund
# self.fxa_sid = symbol('FXA').sid # Swiss franc
# self.fxf_sid = symbol('FXF').sid # Ausi dollar
# indicator from Toma Hentea
# self.dbb_sid = symbol('DBB').sid # Invesco DB Base Metals Fund
# self.uup_sid = symbol('UUP').sid # Invesco DB US Dollar Index Fund
# self.xly_sid = symbol('XLY').sid # Consumer Discretionary Select Sector SPDR Fund
# self.xlp_sid = symbol('XLP').sid # Consumer Staples Select Sector SPDR Fund
def timing_test(data,symbl_1, symbl_2,start,end):
data['eqity_ret'] = data['QQQ'].pct_change(1)
data['bonds_ret'] = data['IEF'].pct_change(1)
cagr = np.zeros(end)
for i in range(start,end):
window_risk_on_off = i
# definition of momentum of tickers used to define risk on risk off
data['a_mom'] = data[symbl_1].pct_change(window_risk_on_off)
data['b_mom'] = data[symbl_2].pct_change(window_risk_on_off)
#shift 1 day to avoid hindsight bias
data['a_mom'] = data['a_mom'].shift()
data['b_mom'] = data['b_mom'].shift()
# now we make the decision if risk is on or off
data["risk_off"] = np.where ((data['a_mom']<data['b_mom']),1,0)
data["dummy"] = np.where(data['risk_off'] == 1, data['bonds_ret'], data['eqity_ret'])
strategy_ret_daily = data["dummy"][i:]
days=len(strategy_ret_daily)
cagr[i] = (((((strategy_ret_daily+1).prod())**(1/days))**252-1)*100).round(2)
fig, ax = plt.subplots(figsize=(20,5))
ax.plot(cagr)
ax.set(xlabel='window', ylabel='cagr',
title= symbl_1 + ' vs '+ symbl_2 )
ax.grid()
fig.savefig("test.png")
plt.show()
def timing_test_2(data,roll_wind,symbl_1, symbl_2,start,end):
strategy_ret_daily_rolling = pd.DataFrame()
result = pd.DataFrame(columns=['best_window','best_returns','sec_best_window','sec_best_returns'])
# ticker of benchmark investment
data['eqity_ret'] = data['SPY'].pct_change(1)
data['bonds_ret'] = data['IEF'].pct_change(1)
data['cash_ret'] = data['bonds_ret']
data['cash_ret'] = 0.0
cagr = np.zeros(end)
#strategy_ret_daily_rolling = np.zeros((end,len_data))
# running the strategie for all momentum windows
for i in range(start,end,10):
window_risk_on_off = i
#print(i)
# definition of momentum of tickers used to define risk on risk off
data['a_mom'] = data[symbl_1].pct_change(window_risk_on_off)
data['b_mom'] = data[symbl_2].pct_change(window_risk_on_off)
#shift 1 day to avoid hindsight bias
data['a_mom'] = data['a_mom'].shift()
data['b_mom'] = data['b_mom'].shift()
# now we make the decision if risk is on or off
data["risk_off"] = np.where ((data['a_mom']<data['b_mom']),1,0)
#data["dummy"] = np.where(data['risk_off'] == 1, data['bonds_ret'], data['eqity_ret'])
data["dummy"] = np.where(data['risk_off'] == 1, data['cash_ret'], data['eqity_ret'])
strategy_ret_daily = data["dummy"][i:]
# 22 days rolling window to see the gains
days=roll_wind
# calculating the rolling returns, on yearly basis
strategy_ret_daily_rolling[i] = strategy_ret_daily.rolling(days).apply(lambda x: (((np.prod(1+x)**(1/days))**252-1)*100).round(2))
len_x = strategy_ret_daily_rolling.shape[0]
#print(strategy_ret_daily_rolling.head())
#strategy_ret_daily_rolling.to_csv('out.csv', index=True)
result = pd.DataFrame(np.zeros([len_x, 12]),columns=[
'window_1','returns_1','window_2','returns_2','window_3','returns_3','window_4','returns_4','window_5','returns_5','window_6','returns_6'
])
result = result.reindex(strategy_ret_daily_rolling.index)
for j in range(0,len_x):
if len(strategy_ret_daily_rolling.iloc[j,:].loc[strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()]) > 0:
# first max
a=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
# set max to zero
strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]=0
#second max
b=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
# set max to zero
strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]=0
# third max
c=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
# set max to zero
strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]=0
# 4 max
d=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
# set max to zero
strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]=0
# 5 max
e=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
# set max to zero
strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]=0
# 6 max
f=strategy_ret_daily_rolling.iloc[j,(strategy_ret_daily_rolling.iloc[j,:]==strategy_ret_daily_rolling.iloc[j,:].max()).values]
if len(a)>1 or len(b)>1 or len(c)>1 or len(d)>1 or len(e)>1 or len(f)>1 :
result.window_1.iloc[j]=np.nan
result.returns_1.iloc[j]=np.nan
result.window_2.iloc[j]=np.nan
result.returns_2.iloc[j]=np.nan
result.window_3.iloc[j]=np.nan
result.returns_3.iloc[j]=np.nan
result.window_4.iloc[j]=np.nan
result.returns_4.iloc[j]=np.nan
result.window_5.iloc[j]=np.nan
result.returns_5.iloc[j]=np.nan
result.window_6.iloc[j]=np.nan
result.returns_6.iloc[j]=np.nan
else:
result.window_1.iloc[j]=a.index[0]
result.returns_1.iloc[j]=a
result.window_2.iloc[j]=b.index[0]
result.returns_2.iloc[j]=b
result.window_3.iloc[j]=c.index[0]
result.returns_3.iloc[j]=c
result.window_4.iloc[j]=d.index[0]
result.returns_4.iloc[j]=d
result.window_5.iloc[j]=e.index[0]
result.returns_5.iloc[j]=e
result.window_6.iloc[j]=f.index[0]
result.returns_6.iloc[j]=f
fig, ax = plt.subplots(figsize=(20,5))
ax.plot(result.window_6,'b+', result.window_5,'b+',result.window_4,'b+', result.window_3,'b+', result.window_2,'g+',result.window_1,'r+')
ax.set(xlabel='time', ylabel='best_windows= 1. red, 2. green, 3-6 blue',
title= symbl_1 + ' vs '+ symbl_2 )
ax.grid()
fig.savefig("test.png")
plt.show()
fig, ax = plt.subplots(figsize=(20,5))
ax.plot(result.returns_6,'b+', result.returns_5,'b+', result.returns_4,'b+',result.returns_3,'b+', result.returns_2,'g+', result.returns_1,'r+')
ax.set(xlabel='time', ylabel='return for best_window = 1. red, 2. green, 3-6 blue',
title= symbl_1 + ' vs '+ symbl_2 )
ax.grid()
#fig.savefig("test.png")
plt.show()
# import data from yahoo
data = yf.download(symbols_to_use, "2008-01-01", "2021-11-01")['Adj Close']
### testing the best window to caluculate percentage change of the two pairs
timing_test(data,'DBB','UUP',2,800)
timing_test(data,'XLY','XLP',2,800)
timing_test(data,'SLV','GLD',2,800)
timing_test(data,'XLI','XLU',2,800)
timing_test(data,'FXA','FXF',2,800)
timing_test(data,'FXA','FXY',2,800)
timing_test(data,'FXC','FXF',2,800)
timing_test(data,'FXC','FXY',2,800)
### running a rolling window for retuns and selecting the best performing windows during that time
timing_test_2(data,250,'DBB','UUP',20,400)
timing_test_2(data,250,'XLY','XLP',20,400)
timing_test_2(data,250,'SLV','GLD',20,400)
timing_test_2(data,250,'XLI','XLU',20,400)
timing_test_2(data,250,'FXA','FXF',20,400)
timing_test_2(data,250,'FXA','FXY',20,400)
timing_test_2(data,250,'FXC','FXF',20,400)
timing_test_2(data,250,'FXC','FXY',20,400)