Overall Statistics |
Total Trades 966 Average Win 0.94% Average Loss -0.22% Compounding Annual Return 5.788% Drawdown 17.400% Expectancy 0.605 Net Profit 70.604% Sharpe Ratio 0.502 Probabilistic Sharpe Ratio 4.120% Loss Rate 69% Win Rate 31% Profit-Loss Ratio 4.23 Alpha 0.078 Beta -0.177 Annual Standard Deviation 0.106 Annual Variance 0.011 Information Ratio -0.439 Tracking Error 0.202 Treynor Ratio -0.301 Total Fees $977.92 Estimated Strategy Capacity $410000.00 Lowest Capacity Asset SJB UV40M49093Z9 |
import math import numpy as np from RiskManager import StopLimit class DeterminedLightBrownAlpaca(QCAlgorithm): def Initialize(self): self.SetCash(10000) self.SetStartDate(2012, 1, 1) self.AddRiskManagement( StopLimit(self, UpPct = 0.15, DownPct = 1.0) ) self.initials = {} self.baseline_set = False self.SymbolData = [] self.Keeps = int(self.GetParameter("Keeps")) self.CashReservePct = float(self.GetParameter("CashReservePct")) self.WeightRa = float(self.GetParameter("WeightRa")) self.WeightRb = float(self.GetParameter("WeightRb")) self.WeightV = float(self.GetParameter("WeightV")) self.FilterSMA = int(self.GetParameter("FirstSMA")) self.LookbackRa = self.GetParameter("LookbackRa") self.LookbackRb = self.GetParameter("LookbackRb") self.LookbackV = self.GetParameter("LookbackV") self.Url = "" self.RebalancePeriod = self.GetParameter("RebalancePeriod") self.GrowthSymbols = [ #"HYG", # corporate "IEF", # 7-10 #"IGIB", # 5-10 #"JNK", "LQD", "TBF", #"TBX", "TLT" ] self.Inverses = [ "TYO", #"DFVS", # data issue #"JNK", "SJB", "TMV", #"TBX", # data issue "TTT" ] for i in self.Inverses: self.AddEquity(i) self.TargetDict = dict(zip(self.GrowthSymbols, self.Inverses)) #TODO: not in love with this self.SafetySymbol = self.AddEquity("SHY", Resolution.Daily).Symbol # List<string> GrowthSymbols = new List<String>{ # // safety symbol # "SHY" # }; self.rebalance = True self.LastRotationTime = self.Time self.Debug("WeightRa: " + str(self.WeightRa)) self.Debug("WeightRb: " + str(self.WeightRb)) self.Debug("WeightV: " + str(self.WeightV)) self.Debug("SymbolUrl: " + str(self.Url)) self.Debug("RebalancePeriod: " + str(self.RebalancePeriod)) periodADays = self.DaysFromLookback(self.LookbackRa) periodBDays = self.DaysFromLookback(self.LookbackRb) periodVolDays = self.DaysFromLookback(self.LookbackV) for symbol in self.GrowthSymbols: self.Debug("adding symbol to universe: " + symbol) symbolObject = self.AddSecurity(SecurityType.Equity, symbol, Resolution.Daily).Symbol sData = SymbolData(self, symbolObject, periodADays, periodBDays, periodVolDays, self.FilterSMA) self.SymbolData.append(sData) # we don't look back more than a year self.SetWarmup(timedelta(days=365)) self.lastDay = -1 self.newWeek = True self.newMonth = True self.counter = 2 self.newBiWeek = True self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol self.SetBenchmark('SPY') self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.At(0, 0), self.TrackMonth) self.Schedule.On(self.DateRules.WeekStart("SPY"), self.TimeRules.At(0, 0), self.TrackWeek) self.Schedule.On(self.DateRules.WeekStart("SPY"), self.TimeRules.At(0, 0), self.TrackBiWeekly) self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen('SPY', 0), # plot performance from prev day self.PlotStuff) def TrackMonth(self): self.newMonth = True def TrackWeek(self): self.newWeek = True def TrackBiWeekly(self): if self.counter == 2: self.newBiWeek = True self.counter = 1 else: self.counter += 1 def DaysFromLookback(self, period): strAmount = period[0:len(period) - 1] # strAmount = period.substr(0, len(period)-1) amount = np.int32(strAmount) # amount = int32.Parse(strAmount) multiplier = period.endswith("M") if not multiplier: multiplier = 1 else: multiplier = 21 return multiplier * amount def GetEtfReplayRankings(self): # orderedReturnA = self.SymbolData.OrderByDescending(x.ReturnA) orderedReturnA = sorted(self.SymbolData, key=lambda x: x.ReturnA.Current.Value, reverse=True) # orderedReturnB = self.SymbolData.OrderByDescending(x.ReturnB) orderedReturnB = sorted(self.SymbolData, key=lambda x: x.ReturnB.Current.Value, reverse=True) # orderedVol = self.SymbolData.OrderBy(x.Volatility) orderedVol = sorted(self.SymbolData, key=lambda x: x.Volatility.Current.Value, reverse=False) rankings = {} # // add all the symbols for sd in self.SymbolData: rankings[sd.Symbol] = 0.0 for i in range(0, len(orderedReturnA), 1): current = orderedReturnA[i] rankings[current.Symbol] += i * self.WeightRa for i in range(0, len(orderedReturnB), 1): current = orderedReturnB[i] rankings[current.Symbol] += i * self.WeightRb for i in range(0, len(orderedVol), 1): current = orderedVol[i] rankings[current.Symbol] += i * self.WeightV return rankings def OnData(self, data): if self.IsWarmingUp: return self.Log("OnData slice: date=" + str(self.Time)) # + ", cnt=" + data.Count); if not self.rebalance: # // var delta = Time.Subtract(LastRotationTime); # // rebalance = delta > RotationInterval; self.rebalance = self.LastRotationTime.month != self.Time.month if (self.RebalancePeriod == "Quarterly"): # // January - 1 # // April - 4 # // July - 7 # // Oct - 10 self.rebalance = (self.rebalance and ((self.Time.month == 1) or (self.Time.month == 4) or (self.Time.month == 7) or (self.Time.month == 10))) if self.rebalance: self.Debug("Rebalancing Quarterly on " + str(self.Time)) elif (self.RebalancePeriod == "Daily"): if self.Time.day != self.lastDay: self.rebalance = True self.lastDay = self.Time.day self.Debug("Rebalancing Daily on " + str(self.Time)) else: self.rebalance = False elif (self.RebalancePeriod == "Weekly"): if self.newWeek: self.rebalance = True self.newWeek = False self.Debug("Rebalancing Weekly on " + str(self.Time)) else: self.rebalance = False elif (self.RebalancePeriod == "Bi-Monthly"): if self.newBiWeek: self.rebalance = True self.newBiWeek = False self.Debug("Rebalancing Bi Weekly on " + str(self.Time)) else: self.rebalance = False elif (self.RebalancePeriod == "Monthly"): if self.newMonth: self.rebalance = True self.newMonth = False self.Debug("Rebalancing Monthly on " + str(self.Time)) else: self.rebalance = False if self.rebalance: self.rebalance = False self.LastRotationTime = self.Time # pick which one is best from growth and safety symbols rankings = self.GetEtfReplayRankings() # orderedObjScores = self.SymbolData.OrderBy(rankings[x.Symbol]).ToList() orderedObjScores = sorted(self.SymbolData, key=lambda x: rankings[x.Symbol], reverse=False) nextHoldings = [] self.Debug("OrderedObjScores Count: " + str(len(orderedObjScores))) for i in range(0, self.Keeps, 1): currentSymbolData = orderedObjScores[i] # if currentSymbolData.Symbol not in data.Bars:continue lastClose = self.Securities[currentSymbolData.Symbol].Close maFilter = lastClose > currentSymbolData.FilterSMA.Current.Value if maFilter: # // this meets our investment criteria if self.Portfolio[self.SafetySymbol].Invested: # exit shy self.Liquidate(self.SafetySymbol) # short inverse mapped to this ticker k = str(currentSymbolData.Symbol).split(' ')[0] v = self.TargetDict[k] nextHoldings.append(v) else: if self.SafetySymbol not in nextHoldings: nextHoldings.append(self.SafetySymbol) # FIXME - allocate to "Safety" symbol, right now saftey symbol is part of the growth symbols so it should bubble up but that doesn't match replay # else: # if not self.Portfolio[self.SafetySymbol].Invested: # self.Liquidate() # self.SetHoldings(self.SafetySymbol, 1) # break # if self.Portfolio[self.SafetySymbol].Invested: # break if self.Portfolio[self.SafetySymbol].Invested: return self.Log(">>NextPositions<<") for position in nextHoldings: self.Log("\t>>" + str(position)) if len(nextHoldings) == 0: # go to 100% cash self.Log("LIQUIDATE>>CASH") self.Liquidate() else: for kvp in self.Portfolio.Securities.Values: # liquidate anything we are currently invested in but not part of our next portfolio state if kvp.Invested and not (kvp.Symbol in nextHoldings): self.Log("LIQUIDATE>>" + str(kvp.Symbol)) self.Liquidate(kvp.Symbol) allocationPercentage = (1.0 - self.CashReservePct) / self.Keeps for symbol in nextHoldings: self.Log("BUY>>" + str(symbol)) self.SetHoldings([PortfolioTarget(symbol, -allocationPercentage)]) def PlotStuff(self): # normalize data for plotting if not self.baseline_set: self.initials['SPY'] = self.Securities['SPY'].Price if self.initials['SPY'] != 0: self.baseline_set = True if not self.baseline_set: return self.Plot("Performance", 'Portfolio', (self.Portfolio.TotalPortfolioValue - 10000) / 10000 * 100) self.Plot("Performance", 'SPY', (self.Securities['SPY'].Price - self.initials['SPY']) / self.initials['SPY'] * 100) class SymbolData: def __init__(self, algo, symbol, periodADays, periodBDays, periodVolDays, FilterSMA): self.Symbol = symbol self.algo = algo self.ReturnA = self.algo.MOMP(symbol, periodADays, Resolution.Daily) self.ReturnB = self.algo.MOMP(symbol, periodBDays, Resolution.Daily) # self.Volatility = Volatility self.FilterSMA = SimpleMovingAverage(FilterSMA) consolidator = TradeBarConsolidator(CalendarType.Monthly) self.algo.RegisterIndicator(self.Symbol, self.FilterSMA, consolidator) std = StandardDeviation(periodVolDays) annualizationFactor = float(math.sqrt(252)) logReturns = self.algo.LOGR(symbol, periodVolDays, Resolution.Daily) # CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor); volatility_ = IndicatorExtensions.Of(std, logReturns) self.Volatility = IndicatorExtensions.Times(volatility_, annualizationFactor)
from clr import AddReference AddReference("System") AddReference("QuantConnect.Common") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Algorithm.Framework") from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework import * from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel class StopLimit(RiskManagementModel): '''Provides an implementation of IRiskManagementModel that limits the drawdown per holding to the specified percentage''' def __init__(self, algo, UpPct = 0.05, DownPct = 0.05): '''Initializes a new instance of the MaximumDrawdownPercentPerSecurity class Args: maximumDrawdownPercent: The maximum percentage drawdown allowed for any single security holding''' self.maxDown = -abs(DownPct) self.maxUp = abs(UpPct) self.algo = algo def ManageRisk(self, algorithm, targets): '''Manages the algorithm's risk at each time step Args: algorithm: The algorithm instance targets: The current portfolio targets to be assessed for risk''' targets = [] for kvp in algorithm.Securities: security = kvp.Value if not security.Invested: continue pnl = security.Holdings.UnrealizedProfitPercent if pnl < self.maxDown: # liquidate self.algo.Debug(f'Cut Loss {security.Symbol}. Loss: {self.algo.Portfolio[security.Symbol].UnrealizedProfitPercent*100:.2f}%') targets.append(PortfolioTarget(security.Symbol, 0)) elif pnl > self.maxUp: # liquidate self.algo.Debug(f'Take Profit {security.Symbol}. Profit: {self.algo.Portfolio[security.Symbol].UnrealizedProfitPercent*100:.2f}%') targets.append(PortfolioTarget(security.Symbol, 0)) return targets