| Overall Statistics |
|
Total Trades 141 Average Win 5.64% Average Loss -1.19% Compounding Annual Return 23.963% Drawdown 40.500% Expectancy 3.399 Net Profit 1486.776% Sharpe Ratio 1.158 Probabilistic Sharpe Ratio 58.192% Loss Rate 23% Win Rate 77% Profit-Loss Ratio 4.73 Alpha 0.218 Beta -0.043 Annual Standard Deviation 0.184 Annual Variance 0.034 Information Ratio 0.44 Tracking Error 0.269 Treynor Ratio -4.992 Total Fees $2360.01 |
"""
Based on 'In & Out' strategy by Peter Guenther 4 Oct 2020
expanded/inspired by Tentor Testivis, Dan Whitnable (Quantopian), Vladimir, and Thomas Chang.
https://www.quantopian.com/posts/new-strategy-in-and-out
https://www.quantconnect.com/forum/discussion/9597/the-in-amp-out-strategy-continued-from-quantopian/p1
"""
# Import packages
import numpy as np
import pandas as pd
import scipy as sc
class InOut(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
res = Resolution.Daily
# Feed-in constants
self.INI_WAIT_DAYS = 15 # out for 3 trading weeks
# Holdings
### 'Out' holdings and weights
self.TLT = self.AddEquity('TLT', res).Symbol
self.IEF = self.AddEquity('IEF', res).Symbol
self.HLD_OUT = {self.TLT: .5, self.IEF: .5}
### 'In' holdings and weights (static stock selection strategy)
self.STKS = self.AddEquity('SPY', res).Symbol
self.HLD_IN = {self.STKS: 1}
### combined holdings dictionary
self.wt = {**self.HLD_IN, **self.HLD_OUT}
#New Vars
self.ret_init = 80
self.vola_span = 126
# Market and list of signals based on ETFs
self.MRKT = self.AddEquity('QQQ', res).Symbol # market
self.PRDC = self.AddEquity('XLI', res).Symbol # production (industrials)
self.METL = self.AddEquity('DBB', res).Symbol # input prices (metals)
self.NRES = self.AddEquity('IGE', res).Symbol # input prices (natural res)
self.DEBT = self.AddEquity('SHY', res).Symbol # cost of debt (bond yield)
self.USDX = self.AddEquity('UUP', res).Symbol # safe haven (USD)
self.GOLD = self.AddEquity('GLD', res).Symbol # gold
self.SLVA = self.AddEquity('SLV', res).Symbol # VS silver
self.UTIL = self.AddEquity('XLU', res).Symbol # utilities
self.INDU = self.PRDC # vs industrials
self.SHCU = self.AddEquity('FXF', res).Symbol # safe haven currency (CHF)
self.RICU = self.AddEquity('FXA', res).Symbol # vs risk currency (AUD)
self.FORPAIRS = [self.GOLD, self.SLVA, self.UTIL, self.SHCU, self.RICU]
self.SIGNALS = [self.PRDC, self.METL, self.NRES, self.DEBT, self.USDX]
# Initialize variables
## 'In'/'out' indicator
self.be_in = 1
## Day count variables
self.dcount = 0 # count of total days since start
self.outday = 0 # dcount when self.be_in=0
## Flexi wait days
self.WDadjvar = self.INI_WAIT_DAYS
self.Schedule.On(
self.DateRules.EveryDay(),
self.TimeRules.AfterMarketOpen('SPY', 120),
self.rebalance_when_out_of_the_market
)
self.Schedule.On(
self.DateRules.WeekEnd(),
self.TimeRules.AfterMarketOpen('SPY', 120),
self.rebalance_when_in_the_market
)
def rebalance_when_out_of_the_market(self):
#das ganze hier anders aufziehen, so wie im code.txt
vola = self.History(self.MRKT, self.vola_span, Resolution.Daily)['close'].pct_change().std() * np.sqrt(252)
WAIT_DAYS = int(vola * self.ret_init)
RET = int((1.0 - vola) * self.ret_init)
# Returns sample to detect extreme observations
hist = self.History(
[self.SLVA] + [self.GOLD] + [self.PRDC] + [self.UTIL], RET+2, Resolution.Daily)['close'].unstack(level=0).dropna()
ratio_ab = (hist[self.SLVA].iloc[-1] / hist[self.SLVA].iloc[0]) / (hist[self.GOLD].iloc[-1] / hist[self.GOLD].iloc[0])
ratio_cd = (hist[self.PRDC].iloc[-1] / hist[self.PRDC].iloc[0]) / (hist[self.UTIL].iloc[-1] / hist[self.UTIL].iloc[0])
# self.Debug('{}'.format(self.WDadjvar))
# Determine whether 'in' or 'out' of the market
if ratio_ab < 1 and ratio_cd < 1:
self.be_in = False
self.outday = self.dcount
elif self.dcount >= self.outday + WAIT_DAYS:
self.be_in = True
self.dcount += 1
#self.be_in = True # for testing; sets the algo to being always in
wt = self.wt
# Swap to 'out' assets if applicable
if not self.be_in:
# Close 'In' holdings
#for asset, weight in self.HLD_IN.items():
# self.SetHoldings(asset, 0)
#for asset, weight in self.HLD_OUT.items():
# self.SetHoldings(asset, weight)
wt[self.MRKT] = 0
wt[self.TLT] = .5
wt[self.IEF] = .5
# Thomas's reducing unnecessary trades
for sec, weight in wt.items():
cond1 = (self.Portfolio[sec].Quantity > 0) and (weight == 0)
cond2 = (self.Portfolio[sec].Quantity == 0) and (weight > 0)
if cond1 or cond2:
self.SetHoldings(sec, weight)
self.Plot("In Out", "in_market", int(self.be_in))
#self.Plot("In Out", "num_out_signals", extreme_b[self.SIGNALS + self.pairlist].sum())
#self.Plot("Wait Days", "waitdays", adjwaitdays)
def rebalance_when_in_the_market(self):
# Swap to 'in' assets if applicable
wt = self.wt
if self.be_in:
# Close 'Out' holdings
#for asset, weight in self.HLD_OUT.items():
# self.SetHoldings(asset, 0)
#for asset, weight in self.HLD_IN.items():
# self.SetHoldings(asset, weight)
wt[self.MRKT] = 1
wt[self.TLT] = 0
wt[self.IEF] = 0
# Thomas's reducing unnecessary trades
for sec, weight in wt.items():
cond1 = (self.Portfolio[sec].Quantity > 0) and (weight == 0)
cond2 = (self.Portfolio[sec].Quantity == 0) and (weight > 0)
if cond1 or cond2:
self.SetHoldings(sec, weight)