| Overall Statistics |
|
Total Trades 387 Average Win 0.80% Average Loss -1.01% Compounding Annual Return 7.275% Drawdown 18.500% Expectancy 0.244 Net Profit 66.637% Sharpe Ratio 0.579 Probabilistic Sharpe Ratio 9.744% Loss Rate 31% Win Rate 69% Profit-Loss Ratio 0.79 Alpha 0.062 Beta 0.035 Annual Standard Deviation 0.115 Annual Variance 0.013 Information Ratio -0.341 Tracking Error 0.191 Treynor Ratio 1.886 Total Fees $1168.52 Estimated Strategy Capacity $67000.00 Lowest Capacity Asset DWAS V8DKFGNVD0TH |
""" Rules: The assets are ranked according to an average of the asset's 1, 3, 6, and 12-month
total returns (Momentum factor).
The model invests 33% of capital into the top 3 assets given that the asset's price is greater than
the asset's 10-month SMA (Trend factor), else the allocation is held in cash or T-bills.
"""
class EmotionalFluorescentYellowAnt(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2014, 5, 1)
self.SetCash(100000)
self.leverage = 1.0
#\\\\\ ASSETS //// MTUM=APR2013
assets = ["VTV", "MTUM", "VBR", "DWAS", "EFA", "EEM", "IEF", "IGOV", "LQD", "TLT", "GSG", "IAU", "VNQ"]
self.AddEquity("SPY", Resolution.Daily) #SchedulingTasks
#\\\\\ SIGNALS ////
self.DataBySymbol = {}
for ticker in assets:
if ticker == "IEF":
self.ief = self.AddEquity(ticker, Resolution.Daily).Symbol
self.DataBySymbol[self.ief] = SymbolData(self.ief, self)
else:
symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
self.DataBySymbol[symbol] = SymbolData(symbol, self)
#\\\\\ TIMING ////
Timing = 60
self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", Timing), self.MonthlyRebalance)
#\\\\\ RISK MANAGEMENT / LOGGING ////
#self.SetRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.25))
self.hwm = self.Portfolio.TotalPortfolioValue
self.marginRemaining = int(self.Portfolio.MarginRemaining)
self.ClosingPortValue = int(self.Portfolio.TotalPortfolioValue)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose('SPY', 1), self.CheckDailyPerformance)
def OnData(self, data):
value = self.Portfolio.TotalPortfolioValue
if value > self.hwm:
self.hwm = value
def MonthlyRebalance(self):
# Rank assets according to weighted 1-3-6-12month momentum
momentum_scores = [self.DataBySymbol[x].Momentum for x in self.DataBySymbol]
momentum_sort = sorted([k for k,v in self.DataBySymbol.items()], key=lambda x: self.DataBySymbol[x].Momentum, reverse=True)
targets = momentum_sort[:3]
# Check if assets are above or below MovingAverage
for symbol in self.DataBySymbol:
self.DataBySymbol[symbol].Weight = 0
if symbol in targets:
price = self.Securities[symbol].Price
# If asset is below, hold bonds 33%, else hold asset 33%
if price != 0 and self.DataBySymbol[symbol].MovAvg.IsReady:
if price > self.DataBySymbol[symbol].MovAvg.Current.Value:
self.DataBySymbol[symbol].Weight = 0.33
else:
self.DataBySymbol[self.ief].Weight += 0.33
else:
self.DataBySymbol[self.ief].Weight += 0.33
self.PlaceTrades()
def PlaceTrades(self):
weights = {}
for symbol in self.DataBySymbol:
weights[symbol] = self.DataBySymbol[symbol].Weight
self.SetHoldings([PortfolioTarget(target, weight*self.leverage) for target, weight in weights.items()])
self.Notify.Sms("+61411067329", "GTAA13(3) Rebalancing...")
def CheckDailyPerformance(self):
### Closing Plotting
leverage = round(self.Portfolio.TotalHoldingsValue / self.Portfolio.TotalPortfolioValue, 2 )
percent_DD = round( ( self.Portfolio.TotalPortfolioValue / self.hwm - 1 ) * 100, 1)
perf = round( (self.Portfolio.TotalPortfolioValue / self.ClosingPortValue - 1) * 100, 1)
self.ClosingPortValue = int(self.Portfolio.TotalPortfolioValue)
for symbol in self.DataBySymbol:
if self.DataBySymbol[symbol].Momentum is not None:
self.Plot('MovAvg', f'{str(symbol)}', self.DataBySymbol[symbol].MovAvg.Current.Value)
self.Plot('Momentum', f'{str(symbol)}', self.DataBySymbol[symbol].Momentum)
for kvp in self.Portfolio:
symbol = kvp.Key
holding = kvp.Value
self.Plot('AssetWeights %', f"{str(holding.Symbol)}%", holding.HoldingsValue/self.Portfolio.TotalPortfolioValue)
#\\\\\ INDICATORS ////
class SymbolData:
def __init__(self, symbol, algorithm):
self.Symbol = symbol
self.Weight = None
self.algorithm = algorithm
self.MovAvg = SimpleMovingAverage(210)
self.Momentum = None
self.MOMPone = MomentumPercent(21)
self.MOMPthree = MomentumPercent(63)
self.MOMPsix = MomentumPercent(126)
self.MOMPtwelve = MomentumPercent(252)
# Warm up MA
history = algorithm.History([self.Symbol], 253, Resolution.Daily).loc[self.Symbol]
# Use history to build our SMA
for time, row in history.iterrows():
self.MovAvg.Update(time, row["close"])
self.MOMPone.Update(time, row["close"])
self.MOMPthree.Update(time, row["close"])
self.MOMPsix.Update(time, row["close"])
self.MOMPtwelve.Update(time, row["close"])
# Setup indicator consolidator
self.consolidator = TradeBarConsolidator(timedelta(1))
self.consolidator.DataConsolidated += self.CustomDailyHandler
algorithm.SubscriptionManager.AddConsolidator(self.Symbol, self.consolidator)
def CustomDailyHandler(self, sender, consolidated):
self.MovAvg.Update(consolidated.Time, consolidated.Close)
self.MOMPone.Update(consolidated.Time, consolidated.Close)
self.MOMPthree.Update(consolidated.Time, consolidated.Close)
self.MOMPsix.Update(consolidated.Time, consolidated.Close)
self.MOMPtwelve.Update(consolidated.Time, consolidated.Close)
self.Momentum = self.MOMPone.Current.Value * 12 + self.MOMPthree.Current.Value * 4 + \
self.MOMPsix.Current.Value * 2 + self.MOMPtwelve.Current.Value
def dispose(self):
self.algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.consolidator)