| Overall Statistics |
|
Total Trades 434 Average Win 3.89% Average Loss -0.64% Compounding Annual Return 31.099% Drawdown 18.000% Expectancy 2.586 Net Profit 4342.852% Sharpe Ratio 1.457 Probabilistic Sharpe Ratio 92.621% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 6.03 Alpha 0.206 Beta 0.15 Annual Standard Deviation 0.15 Annual Variance 0.023 Information Ratio 0.631 Tracking Error 0.206 Treynor Ratio 1.464 Total Fees $20960.40 Estimated Strategy Capacity $21000000.00 Lowest Capacity Asset TLT SGNKIKYGE9NP Portfolio Turnover 7.25% |
#region imports
from AlgorithmImports import *
#endregion
"""
v2.5 Dual Momentum with Out Days by Vladimir
inspired by Peter Guenther, Tentor Testivis, Dan Whitnable, Thomas Chang and T Smith.
converted to QC Algorithm Framework (Alpha Model + Portfolio Construction Model) by Joao Antunes
based on Intersection of ROC comparison using OUT_DAY approach by Vladimir
modified parameters BASE_RET = 83;
https://www.quantconnect.com/forum/discussion/10039/dual-momentum-with-out-days/p1/comment-29928
"""
import numpy as np
class DualMomentumWithOutDaysAlphaModel(AlphaModel):
def __init__(self,
algorithm,
VOLA = 126,
BASE_RET = 83,
resolution = Resolution.Daily):
self.VOLA = VOLA
self.BASE_RET = BASE_RET
self.resolution = resolution
self.MKT = algorithm.AddEquity('SPY', resolution).Symbol
self.SLV = algorithm.AddEquity('SLV', resolution).Symbol
self.GLD = algorithm.AddEquity('GLD', resolution).Symbol
self.XLI = algorithm.AddEquity('XLI', resolution).Symbol
self.XLU = algorithm.AddEquity('XLU', resolution).Symbol
self.DBB = algorithm.AddEquity('DBB', resolution).Symbol
self.UUP = algorithm.AddEquity('UUP', resolution).Symbol
self.count = 0
pairs = [self.MKT, self.SLV, self.GLD, self.XLI, self.XLU, self.DBB, self.UUP]
for symbol in pairs:
self.consolidator = TradeBarConsolidator(timedelta(days=1))
self.consolidator.DataConsolidated += self.consolidation_handler
algorithm.SubscriptionManager.AddConsolidator(symbol, self.consolidator)
self.history = algorithm.History(pairs, self.VOLA + 1, resolution)
self.history = self.history['close'].unstack(level=0).dropna()
self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), 30)
resolutionString = Extensions.GetEnumString(resolution, Resolution)
self.Name = f"{self.__class__.__name__}({resolutionString})"
# Force alpha to only produce insights daily at 11.10am
self.set_flag = False
algorithm.Schedule.On(algorithm.DateRules.EveryDay(),
algorithm.TimeRules.AfterMarketOpen('SPY', 100),
self.SetFlag)
def SetFlag(self):
self.set_flag = True
def consolidation_handler(self, sender, consolidated):
self.history.loc[consolidated.EndTime, consolidated.Symbol] = consolidated.Close
self.history = self.history.iloc[-(self.VOLA + 1):]
def Update(self, algorithm, _data):
if algorithm.IsWarmingUp or not self.set_flag:
return []
self.set_flag = False
insights = []
vola = self.history[self.MKT].pct_change().std() * np.sqrt(252)
wait_days = int(vola * self.BASE_RET)
period = int((1.0 - vola) * self.BASE_RET)
r = self.history.pct_change(period).iloc[-1]
exit_market = r[self.SLV] < r[self.GLD] and r[self.XLI] < r[self.XLU] and r[self.DBB] < r[self.UUP]
direction = InsightDirection.Down
if exit_market:
self.count = 0
else:
self.count += 1
if self.count >= wait_days:
direction = InsightDirection.Up
insights.append(Insight.Price(self.MKT, self.predictionInterval, direction))
return insights#region imports
from AlgorithmImports import *
#endregion
"""
v2.5 Dual Momentum with Out Days by Vladimir
inspired by Peter Guenther, Tentor Testivis, Dan Whitnable, Thomas Chang and T Smith.
converted to QC Algorithm Framework (Alpha Model + Portfolio Construction Model) by Joao Antunes
based on Intersection of ROC comparison using OUT_DAY approach by Vladimir
modified parameters BASE_RET = 83;
https://www.quantconnect.com/forum/discussion/10039/dual-momentum-with-out-days/p1/comment-29928
"""
from itertools import groupby
from dual_momentum_with_out_days_alpha import DualMomentumWithOutDaysAlphaModel
VOLA = 126; BASE_RET = 83; RET = 252; EXCL = 21; LEV = 1.00;
class HorizontalQuantumCoil(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1)
self.SetEndDate(2022, 1, 1)
self.SetCash(100_000)
self.SetWarmUp(timedelta(350))
# self.Portfolio.MarginCallModel = MarginCallModel.Null
self.SetAlpha(DualMomentumWithOutDaysAlphaModel(self, VOLA, BASE_RET, Resolution.Daily))
symbols = [
Symbol.Create('QQQ', SecurityType.Equity, Market.USA),
Symbol.Create('FDN', SecurityType.Equity, Market.USA),
Symbol.Create('TLT', SecurityType.Equity, Market.USA),
Symbol.Create('TLH', SecurityType.Equity, Market.USA),
]
self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
self.UniverseSettings.Resolution = Resolution.Hour
self.SetPortfolioConstruction(OutDays(self, RET, EXCL, LEV, Resolution.Hour))
self.createPlots("SPY")
def createPlots(self, benchmark):
self.__benchmark = benchmark
self.__plot_every_n_days = 5
self.__plot_every_n_days_i = 0
plot = Chart('Performance')
plot.AddSeries(Series(self.__benchmark, SeriesType.Line, 0, '%'))
plot.AddSeries(Series("Algorithm", SeriesType.Line, 0, '%'))
self.AddChart(plot)
self.ResetPlot()
def ResetPlot(self):
self.year = self.Time.year
self.__cost_portfolio = None
self.__cost_benchmark = None
def CalculateBenchmarkPerformance(self):
price = self.Securities[self.__benchmark].Price
if self.__cost_benchmark == None:
self.__cost_benchmark = price
return 100.0 * ((price / self.__cost_benchmark) - 1.0)
def CalculatePortfolioPerformance(self):
if self.__cost_portfolio == None:
self.__cost_portfolio = self.Portfolio.TotalPortfolioValue
return 100.0 * ((self.Portfolio.TotalPortfolioValue / self.__cost_portfolio) - 1.0)
def OnEndOfDay(self):
if self.IsWarmingUp or not self.Securities[self.__benchmark].HasData:
return
if self.Time.year != self.year:
self.ResetPlot()
self.__plot_every_n_days_i == -1
self.__plot_every_n_days_i += 1
if self.__plot_every_n_days_i % self.__plot_every_n_days != 0:
return
self.Plot('Performance', self.__benchmark, self.CalculateBenchmarkPerformance())
self.Plot('Performance', "Algorithm", self.CalculatePortfolioPerformance())
class OutDays(PortfolioConstructionModel):
def __init__(self,
algorithm,
RET=252,
EXCL=21,
LEV=1.00,
resolution = Resolution.Hour):
self.algorithm = algorithm
self.RET = RET
self.EXCL = EXCL
self.LEV = LEV
self.STK1 = self.algorithm.AddEquity('QQQ', resolution).Symbol
self.STK2 = self.algorithm.AddEquity('FDN', resolution).Symbol
self.BND1 = self.algorithm.AddEquity('TLT', resolution).Symbol
self.BND2 = self.algorithm.AddEquity('TLH', resolution).Symbol
self.ASSETS = [self.STK1, self.STK2, self.BND1, self.BND2]
def returns(self, symbol):
prices = self.algorithm.History(symbol, TimeSpan.FromDays(self.RET + self.EXCL), Resolution.Daily).close
return prices[-self.EXCL] / prices[0]
def CreateTargets(self, algorithm, insights):
if algorithm.IsWarmingUp:
return []
targets = []
# We expect at most only one active insight since we only
# generate insights for one equity.
assert len(insights) <= 1
if len(insights) == 1:
insight = insights[0]
self.bull = insight.Direction == InsightDirection.Up
if self.bull:
if self.returns(self.STK1) < self.returns(self.STK2):
selected = self.STK2
else:
selected = self.STK1
else:
if self.returns(self.BND1) < self.returns(self.BND2):
selected = self.BND2
else:
selected = self.BND1
for asset in self.ASSETS:
if asset != selected:
targets.append(PortfolioTarget.Percent(algorithm, asset, 0.0))
targets.append(PortfolioTarget.Percent(algorithm, selected, 1.0))
return targets