| Overall Statistics |
|
Total Trades 67 Average Win 11.87% Average Loss -1.51% Compounding Annual Return 26.052% Drawdown 20.400% Expectancy 6.265 Net Profit 1943.236% Sharpe Ratio 1.662 Probabilistic Sharpe Ratio 95.317% Loss Rate 18% Win Rate 82% Profit-Loss Ratio 7.88 Alpha 0.259 Beta 0.106 Annual Standard Deviation 0.168 Annual Variance 0.028 Information Ratio 0.34 Tracking Error 0.26 Treynor Ratio 2.631 Total Fees $97.75 |
"""
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.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
self.SetStartDate(2008, 1, 1) # Set Start Date
self.SetCash(5000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
res = Resolution.Hour
self.SetBenchmark("QQQ")
# Feed-in constants
self.INI_WAIT_DAYS = 15 # out for 3 trading weeks
self.be_in = True
self.last = True
# Holdings
### 'Out' holdings and weights
self.BND1 = self.AddEquity('TLT', res).Symbol #TLT; TMF for 3xlev
self.BND2 = self.AddEquity('TMF', res).Symbol #IEF; TYD for 3xlev
self.HLD_OUT = {self.BND1: 1}
### 'In' holdings and weights (static stock selection strategy)
self.STKS = self.AddEquity('QQQ', res).Symbol #SPY or QQQ; TQQQ for 3xlev
self.STKS2 = self.AddEquity('TQQQ', res).Symbol #SPY or QQQ; TQQQ for 3xlev
self.HLD_IN = {self.STKS: 1}
### combined holdings dictionary
self.wt = {**self.HLD_IN, **self.HLD_OUT}
# Market and list of signals based on ETFs
self.MRKT = self.AddEquity('SPY', res).Symbol # market
false = False
true = True
self.sig = [{"date": "2008-01-02", "be_in": false}, {"date": "2008-02-19", "be_in": true}, {"date": "2008-06-02", "be_in": false}, {"date": "2008-10-13", "be_in": true}, {"date": "2008-10-16", "be_in": false}, {"date": "2009-03-23", "be_in": true}, {"date": "2010-06-09", "be_in": false}, {"date": "2010-08-31", "be_in": true}, {"date": "2011-06-14", "be_in": false}, {"date": "2012-01-04", "be_in": true}, {"date": "2012-05-04", "be_in": false}, {"date": "2012-09-11", "be_in": true}, {"date": "2013-04-03", "be_in": false}, {"date": "2013-06-05", "be_in": true}, {"date": "2014-03-17", "be_in": false}, {"date": "2014-05-30", "be_in": true}, {"date": "2014-06-06", "be_in": false}, {"date": "2014-06-30", "be_in": true}, {"date": "2014-09-23", "be_in": false}, {"date": "2015-02-27", "be_in": true}, {"date": "2015-06-18", "be_in": false}, {"date": "2015-07-01", "be_in": true}, {"date": "2015-07-08", "be_in": false}, {"date": "2015-10-23", "be_in": true}, {"date": "2015-12-21", "be_in": false}, {"date": "2016-03-09", "be_in": true}, {"date": "2017-05-10", "be_in": false}, {"date": "2017-05-26", "be_in": true}, {"date": "2017-06-05", "be_in": false}, {"date": "2017-07-03", "be_in": true}, {"date": "2018-04-10", "be_in": false}, {"date": "2018-06-05", "be_in": true}, {"date": "2018-08-01", "be_in": false}, {"date": "2019-02-19", "be_in": true}, {"date": "2019-05-09", "be_in": false}, {"date": "2019-09-11", "be_in": true}, {"date": "2020-02-03", "be_in": false}, {"date": "2020-07-09", "be_in": true}]
self.Schedule.On(
self.DateRules.EveryDay(),
self.TimeRules.AfterMarketOpen('SPY', 1),
self.generate_signal
)
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', 121),
self.rebalance_when_in_the_market
)
def generate_signal(self):
if(len(self.sig) > 0):
currentDate = self.UtcTime.date()
signalDate = datetime.strptime(self.sig[0]["date"], '%Y-%m-%d').date()
if currentDate == signalDate:
self.be_in = self.sig[0]["be_in"]
self.sig = self.sig[1:]
def rebalance_when_out_of_the_market(self):
# Swap to 'out' assets if applicable
if not self.be_in:
self.wt = {**dict.fromkeys(self.HLD_IN, 0), **self.HLD_OUT}
# Only trade when changing from in to out
for sec, weight in self.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)
def rebalance_when_in_the_market(self):
# Swap to 'in' assets if applicable
if self.be_in:
self.wt = {**self.HLD_IN, **dict.fromkeys(self.HLD_OUT, 0)}
# Only trade when changing from out to in
for sec, weight in self.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)