Overall Statistics |
Total Trades 15 Average Win 2.78% Average Loss 0% Compounding Annual Return 227.924% Drawdown 7.100% Expectancy 0 Net Profit 11.214% Sharpe Ratio 5.571 Probabilistic Sharpe Ratio 77.708% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 2.677 Beta -1.266 Annual Standard Deviation 0.413 Annual Variance 0.17 Information Ratio 3.007 Tracking Error 0.665 Treynor Ratio -1.815 Total Fees $21.42 |
from MyRsiAlphaModel import MyRsiAlphaModel class ParticleQuantumCompensator(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 6, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash self.AddAlpha(MyRsiAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.ibm = self.AddEquity('IBM', Resolution.Minute).Symbol def OnData(self, data): if self.ibm in data: self.Plot('IBM', 'Close', data.QuoteBars['IBM'].Close) self.Plot('IBM', 'Ask Close', data.QuoteBars['IBM'].Ask.Close) self.Plot('IBM', 'Bid Close', data.QuoteBars['IBM'].Bid.Close) def OnOrderEvent(self, orderEvent): if orderEvent.Direction == OrderDirection.Buy: self.Plot('Price', 'Buy', orderEvent.FillPrice) elif orderEvent.Direction == OrderDirection.Sell: self.Plot('Price', 'Sell', orderEvent.FillPrice)
class MyRsiAlphaModel(AlphaModel): '''Uses Wilder's RSI to create insights. Using default settings, a cross over below 30 or above 70 will trigger a new insight.''' def __init__(self, period = 14, resolution = Resolution.Daily): '''Initializes a new instance of the RsiAlphaModel class Args: period: The RSI indicator period''' self.period = period self.resolution = resolution self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), period) self.symbolDataBySymbol ={} resolutionString = Extensions.GetEnumString(resolution, Resolution) self.Name = '{}({},{})'.format(self.__class__.__name__, period, resolutionString) def Update(self, algorithm, data): '''Updates this alpha model with the latest data from the algorithm. This is called each time the algorithm receives data for subscribed securities Args: algorithm: The algorithm instance data: The new data available Returns: The new insights generated''' insights = [] for symbol, symbolData in self.symbolDataBySymbol.items(): rsi = symbolData.RSI algorithm.Plot('RSI', symbol.Value, rsi.Current.Value) 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): '''Cleans out old security data and initializes the RSI for any newly added securities. Event fired each time the we add/remove securities from the data feed Args: algorithm: The algorithm instance that experienced the change in securities changes: The security additions and removals from the algorithm''' # 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) 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): ''' Determines the new state. This is basically cross-over detection logic that includes considerations for bouncing using the configured bounce tolerance.''' 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: '''Contains data specific to a symbol required by this model''' def __init__(self, symbol, rsi): self.Symbol = symbol self.RSI = rsi self.State = State.Middle class State(Enum): '''Defines the state. This is used to prevent signal spamming and aid in bounce detection.''' TrippedLow = 0 Middle = 1 TrippedHigh = 2