Overall Statistics |
Total Trades 46 Average Win 0.21% Average Loss -0.18% Compounding Annual Return 3.426% Drawdown 6.900% Expectancy 0.148 Net Profit 0.524% Sharpe Ratio 0.249 Probabilistic Sharpe Ratio 36.369% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.19 Alpha 0.021 Beta -0.627 Annual Standard Deviation 0.131 Annual Variance 0.017 Information Ratio 0.202 Tracking Error 0.248 Treynor Ratio -0.052 Total Fees $72.98 Estimated Strategy Capacity $2900000.00 Lowest Capacity Asset BA R735QTJ8XC9X |
from Execution.ImmediateExecutionModel import ImmediateExecutionModel from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel from Risk.MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity from Selection.QC500UniverseSelectionModel import QC500UniverseSelectionModel class SimpleRSITestQC500Universe(QCAlgorithm): def Initialize(self): self.SetStartDate(2010, 1, 1) # Set Start Date self.SetEndDate(2010, 2, 28) # Set End Date self.SetCash(100000) # Set Strategy Cash self.SetExecution(ImmediateExecutionModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.05)) symbols = [ Symbol.Create("SPY", SecurityType.Equity, Market.USA), Symbol.Create("GE", SecurityType.Equity, Market.USA), Symbol.Create("BA", SecurityType.Equity, Market.USA) ] self.SetUniverseSelection(ManualUniverseSelectionModel(symbols)) self.alpha_model = RsiAlphaModelTest() self.AddAlpha(self.alpha_model) def OnEndOfAlgorithm(self): self.Debug(f"Printing {len(self.alpha_model.symbolDataBySymbol)} mapping(s)...") for symbol, _symbolData in self.alpha_model.symbolDataBySymbol.items(): self.Debug( str(symbol) + " " + str(_symbolData)) class RsiAlphaModelTest(AlphaModel): def __init__(self, period = 14, resolution = Resolution.Daily): self.period = period self.resolution = resolution self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), period) self.symbolDataBySymbol = {} self.closeWindows = {} resolutionString = Extensions.GetEnumString(resolution, Resolution) self.Name = '{}({},{})'.format(self.__class__.__name__, period, resolutionString) def Update(self, algorithm, data): insights = [] for symbol, symbolData in self.symbolDataBySymbol.items(): if data.ContainsKey(symbol) and data[symbol] is not None: self.closeWindows[symbol].Add(data[symbol].Close) if self.closeWindows[symbol].Count>2: pass #algorithm.Debug(self.closeWindows[symbol][2]) rsi = symbolData.RSI previous_state = symbolData.State state = self.GetState(rsi, previous_state) if state != previous_state and rsi.IsReady: if state == State.TrippedLow: insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Up)) if state == State.TrippedHigh: insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Down)) symbolData.State = state return insights def OnSecuritiesChanged(self, algorithm, changes): # clean up data for removed securities symbols = [ x.Symbol for x in changes.RemovedSecurities ] if len(symbols) > 0: for subscription in algorithm.SubscriptionManager.Subscriptions: if subscription.Symbol in symbols: self.symbolDataBySymbol.pop(subscription.Symbol, None) subscription.Consolidators.Clear() # initialize data for added securities addedSymbols = [ x.Symbol for x in changes.AddedSecurities if x.Symbol not in self.symbolDataBySymbol] if len(addedSymbols) == 0: return history = algorithm.History(addedSymbols, self.period, self.resolution) for symbol in addedSymbols: rsi = algorithm.RSI(symbol, self.period, MovingAverageType.Wilders, self.resolution) self.closeWindows[symbol] = RollingWindow[float](4) # need to figure out how to load historical data into rolling window if not history.empty: ticker = SymbolCache.GetTicker(symbol) if ticker not in history.index.levels[0]: Log.Trace(f'RsiAlphaModel.OnSecuritiesChanged: {ticker} not found in history data frame.') continue for tuple in history.loc[ticker].itertuples(): rsi.Update(tuple.Index, tuple.close) self.symbolDataBySymbol[symbol] = SymbolData(symbol, rsi) def GetState(self, rsi, previous): if rsi.Current.Value > 70: return State.TrippedHigh if rsi.Current.Value < 30: return State.TrippedLow if previous == State.TrippedLow: if rsi.Current.Value > 35: return State.Middle if previous == State.TrippedHigh: if rsi.Current.Value < 65: return State.Middle return previous class SymbolData: def __init__(self, symbol, rsi): self.Symbol = symbol self.RSI = rsi self.State = State.Middle def __str__(self): return f"RSI: {self.RSI}, State: {self.State}" class State(Enum): '''Defines the state. This is used to prevent signal spamming and aid in bounce detection.''' TrippedLow = 0 Middle = 1 TrippedHigh = 2