Overall Statistics |
Total Trades 1276 Average Win 3.55% Average Loss -1.90% Compounding Annual Return -15.572% Drawdown 66.900% Expectancy -0.015 Net Profit -48.852% Sharpe Ratio -0.047 Probabilistic Sharpe Ratio 0.608% Loss Rate 66% Win Rate 34% Profit-Loss Ratio 1.86 Alpha 0 Beta 0 Annual Standard Deviation 0.438 Annual Variance 0.192 Information Ratio -0.047 Tracking Error 0.438 Treynor Ratio 0 Total Fees $12522.28 Estimated Strategy Capacity $20000000.00 Lowest Capacity Asset SQQQ UK280CGTCB51 |
from AlgorithmImports import * import datetime as dt class RsiMacdAlphaModel(AlphaModel): '''Alpha model that uses an STO_RSI and MACD values to create insights''' last_consolidation_time = None minute = 0 def __init__(self, algo, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9, rsiPeriod = 14, movingAverageType = MovingAverageType.Exponential, resolution = Resolution.Minute): ''' Initializes a new instance of the MacdAlphaModel class Args: fastPeriod: The MACD fast period slowPeriod: The MACD slow period</param> signalPeriod: The smoothing period for the MACD signal rsiPeriod: period for STO_RSI calculation movingAverageType: The type of moving average to use in the MACD''' self.algo = algo self.fastPeriod = fastPeriod self.slowPeriod = slowPeriod self.signalPeriod = signalPeriod self.rsiPeriod = rsiPeriod self.movingAverageType = movingAverageType self.resolution = resolution self.insightPeriod = timedelta(days=3) self.bounceThresholdPercent = 0 self.symbolData = {} self.Fminute = None resolutionString = Extensions.GetEnumString(resolution, Resolution) movingAverageTypeString = Extensions.GetEnumString(movingAverageType, MovingAverageType) self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageTypeString, resolutionString) def Update(self, algorithm, data): ''' Determines an insight for each security based on it's current MACD signal Args: algorithm: The algorithm instance data: The new data available Returns: The new insights generated''' insights = [] if algorithm.Portfolio.Invested: if self.last_fconsolidation_time is not None and self.Fminute != self.last_fconsolidation_time.minute: self.Fminute = self.last_fconsolidation_time.minute for key, sd in self.symbolData.items(): normalized_signal = sd.MACD_15.Signal.Current.Value / sd.Security.Price is_macd_bullish = normalized_signal > self.bounceThresholdPercent is_rsi_bullish = sd.STO_RSI_15.Current.Value >= 80 is_rsi_bearish = sd.STO_RSI_15.Current.Value <= 20 if algorithm.Portfolio[self.algo.long_symbol].Invested: if not is_macd_bullish and is_rsi_bearish: self.algo.rm_model.maximumDrawdownPercent = 0.12 elif algorithm.Portfolio[self.algo.short_symbol].Invested: if is_macd_bullish and is_rsi_bullish: self.algo.rm_model.maximumDrawdownPercent = 0.12 if self.last_consolidation_time is not None and self.minute != self.last_consolidation_time.minute: self.minute = self.last_consolidation_time.minute for key, sd in self.symbolData.items(): self.key = key if sd.Security.Price == 0: continue normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price is_macd_bullish = normalized_signal > self.bounceThresholdPercent is_rsi_bullish = sd.STO_RSI.Current.Value >= 80 is_rsi_bearish = sd.STO_RSI.Current.Value <= 20 if is_macd_bullish and is_rsi_bullish: insights.append(Insight.Price(self.algo.long_symbol, self.insightPeriod, InsightDirection.Up)) insights.append(Insight.Price(self.algo.short_symbol, self.insightPeriod, InsightDirection.Flat)) self.algo.rm_model.maximumDrawdownPercent = 0.05 elif not is_macd_bullish and is_rsi_bearish: insights.append(Insight.Price(self.algo.short_symbol, self.insightPeriod, InsightDirection.Up)) insights.append(Insight.Price(self.algo.long_symbol, self.insightPeriod, InsightDirection.Flat)) self.algo.rm_model.maximumDrawdownPercent = 0.05 elif ((algorithm.Time.weekday() == 4) and (algorithm.Time.time()>dt.time(15,45))) or self.algo.rm_liquidation: insights.append(Insight.Price(self.algo.short_symbol, self.insightPeriod, InsightDirection.Flat)) insights.append(Insight.Price(self.algo.long_symbol, self.insightPeriod, InsightDirection.Flat)) self.algo.rm_liquidation = False return insights def consolidation_handler(self, sender, bar): self.last_consolidation_time = bar.Time def fifteenconsolidation_handler(self, sender, bar): self.last_fconsolidation_time = bar.Time def OnSecuritiesChanged(self, algorithm, changes): '''Event fired each time the we add/remove securities from the data feed. This initializes the MACD for each added security and cleans up the indicator for each removed security. Args: algorithm: The algorithm instance that experienced the change in securities changes: The security additions and removals from the algorithm''' for added in changes.AddedSecurities: if added.Symbol.Value == 'QQQ': self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.rsiPeriod, self.movingAverageType, self.resolution) self.consolidator = TradeBarConsolidator(timedelta(minutes=30)) self.consolidator.DataConsolidated += self.consolidation_handler algorithm.SubscriptionManager.AddConsolidator(added.Symbol, self.consolidator) self.fifteenconsolidator = TradeBarConsolidator(timedelta(minutes=15)) self.fifteenconsolidator.DataConsolidated += self.fifteenconsolidation_handler algorithm.SubscriptionManager.AddConsolidator(added.Symbol, self.fifteenconsolidator) for removed in changes.RemovedSecurities: if added.Symbol.Value == 'QQQ': data = self.symbolData.pop(removed.Symbol, None) if data is not None: # clean up our consolidator algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator) class SymbolData: def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, rsiPeriod, movingAverageType, resolution): self.Security = security self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType) self.MACD_15 = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType) self.RSI = RelativeStrengthIndex(rsiPeriod, MovingAverageType.Wilders) self.Stochastic = Stochastic(period=14, kPeriod=3, dPeriod=3) self.STO_RSI = IndicatorExtensions.Of(self.Stochastic,self.RSI) self.STO_RSI_15 = IndicatorExtensions.Of(self.Stochastic,self.RSI) self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, timedelta(minutes=30)) self.FifteenConsolidator = algorithm.ResolveConsolidator(security.Symbol, timedelta(minutes=15)) algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator) algorithm.WarmUpIndicator(security.Symbol, self.MACD, timedelta(days=5)) algorithm.RegisterIndicator(security.Symbol, self.STO_RSI, self.Consolidator) algorithm.WarmUpIndicator(security.Symbol, self.STO_RSI, timedelta(days=5)) algorithm.RegisterIndicator(security.Symbol, self.MACD_15, self.FifteenConsolidator) algorithm.WarmUpIndicator(security.Symbol, self.MACD_15, timedelta(days=5)) algorithm.RegisterIndicator(security.Symbol, self.STO_RSI_15, self.FifteenConsolidator) algorithm.WarmUpIndicator(security.Symbol, self.STO_RSI_15, timedelta(days=5)) algorithm.PlotIndicator('STORSI',self.STO_RSI) algorithm.PlotIndicator('MACD',self.MACD) algorithm.PlotIndicator('STORSI15',self.STO_RSI_15) algorithm.PlotIndicator('MACD_15',self.MACD_15) self.PreviousDirection = None
#region imports from AlgorithmImports import * import datetime as dt #endregion CASH = 100000 START_DATE = '01-01-2019' #'DD-MM-YYYY' END_DATE = None #'05-01-2019' FREE_PORTFOLIO_VALUE_PCT = 0.025 ############### Parsing Logic ############# ######## Do not edit anything below ###### START_DATE = dt.datetime.strptime(START_DATE, '%d-%m-%Y') if END_DATE: END_DATE = dt.datetime.strptime(END_DATE, '%d-%m-%Y')
from AlgorithmImports import * # Import from files from constants import * from alpha import * from riskmanagement import * ################################################################################ class MACDRSIQQQ(QCAlgorithm): def Initialize(self): """Initialize algorithm.""" # Set backtest details self.SetBacktestDetails() self.AddInstruments() self.AddModels() self.AddOperationalParameters() # self.AddSchedules() # self.AddPlots() #------------------------------------------------------------------------------- def SetBacktestDetails(self): """Set the backtest details.""" self.SetStartDate(START_DATE) if END_DATE: self.SetEndDate(END_DATE) self.SetCash(CASH) self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # Adjust the cash buffer from the default 2.5% to custom setting self.Settings.FreePortfolioValuePercentage = FREE_PORTFOLIO_VALUE_PCT self.Settings.DataSubscriptionLimit = 500 def AddInstruments(self): self.underlying = self.AddEquity('QQQ',Resolution.Minute).Symbol self.long_symbol = self.AddEquity('TQQQ',Resolution.Minute).Symbol self.short_symbol = self.AddEquity('SQQQ',Resolution.Minute).Symbol def AddModels(self): self.SetAlpha(RsiMacdAlphaModel(self)) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(rebalancingFunc=self.Rebalance())) self.rm_model = TrailingStopRiskManagementModel(self,maximumDrawdownPercent=0.05) self.AddRiskManagement(self.rm_model) def Rebalance(self): return None def AddOperationalParameters(self): self.rm_liquidation = False
#region imports from AlgorithmImports import * #endregion class TrailingStopRiskManagementModel(RiskManagementModel): '''Provides an implementation of IRiskManagementModel that limits the maximum possible loss measured from the highest unrealized profit''' def __init__(self, algo, maximumDrawdownPercent = 0.05): '''Initializes a new instance of the TrailingStopRiskManagementModel class Args: maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown''' self.algo = algo self.maximumDrawdownPercent = abs(maximumDrawdownPercent) self.trailing = dict() 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''' riskAdjustedTargets = list() for kvp in algorithm.Securities: symbol = kvp.Key security = kvp.Value # Remove if not invested if not security.Invested: self.trailing.pop(symbol, None) continue profitPercent = security.Holdings.UnrealizedProfitPercent # Add newly invested securities value = self.trailing.get(symbol) if value == None: newValue = profitPercent if profitPercent > 0 else 0 self.trailing[symbol] = newValue continue # Check for new high and update if value < profitPercent: self.trailing[symbol] = profitPercent continue # If unrealized profit percent deviates from local max for more than affordable percentage if profitPercent < value - self.maximumDrawdownPercent: # liquidate algorithm.Log(f'Liquidating at {algorithm.Time} as stop-loss has been breached') self.algo.rm_liquidation = True riskAdjustedTargets.append(PortfolioTarget(symbol, 0)) return riskAdjustedTargets