Overall Statistics |
Total Orders 503 Average Win 0.32% Average Loss -0.31% Compounding Annual Return 0.819% Drawdown 2.000% Expectancy 0.023 Start Equity 100000 End Equity 101487.50 Net Profit 1.487% Sharpe Ratio -2.673 Sortino Ratio -2.463 Probabilistic Sharpe Ratio 15.988% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.01 Alpha -0.038 Beta -0.01 Annual Standard Deviation 0.015 Annual Variance 0 Information Ratio -0.789 Tracking Error 0.146 Treynor Ratio 3.98 Total Fees $508.42 Estimated Strategy Capacity $190000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 38.50% |
#region imports from AlgorithmImports import * #endregion from datetime import timedelta, datetime class SMAPairsTrading(QCAlgorithm): def Initialize(self): self.SetStartDate(2022, 6, 1) self.SetEndDate(2024, 6, 1) self.SetCash(100000) symbols = [ Symbol.Create("QQQQ", SecurityType.Equity, Market.USA), Symbol.Create("SPY", SecurityType.Equity, Market.USA) ] self.AddUniverseSelection(ManualUniverseSelectionModel(symbols)) self.UniverseSettings.Resolution = Resolution.Hour self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw self.AddAlpha(PairsTradingAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) def OnEndOfDay(self, symbol): self.Log("Taking a position of " + str(self.Portfolio[symbol].Quantity) + " units of symbol " + str(symbol)) class PairsTradingAlphaModel(AlphaModel): def __init__(self): self.pair = [ ] self.spreadMean1 = SimpleMovingAverage(12) self.spreadMean2 = SimpleMovingAverage(26) self.period = timedelta(hours=4) def Update(self, algorithm, data): spread = self.pair[1].Price - self.pair[0].Price self.spreadMean1.Update(algorithm.Time, spread) self.spreadMean2.Update(algorithm.Time, spread) sprmean1_t1 = self.spreadMean1[1] sprmean2_t1 = self.spreadMean2[1] sprmean1_t2 = self.spreadMean1[2] sprmean2_t2 = self.spreadMean2[2] sprmean1_t3 = self.spreadMean1[3] sprmean2_t3 = self.spreadMean2[3] indicator_valid = all([ ele is not None for ele in [sprmean1_t1, sprmean2_t1, sprmean1_t2, sprmean2_t2, sprmean1_t3, sprmean2_t3] ]) if indicator_valid: buy_signal = (sprmean1_t1>sprmean2_t1) and (sprmean1_t2<sprmean2_t2) and (sprmean1_t3<sprmean2_t3) sell_signal = (sprmean1_t1<sprmean2_t1) and (sprmean1_t2>sprmean2_t2) and (sprmean1_t3>sprmean2_t3) else: buy_signal = False sell_signal = False if indicator_valid and buy_signal: return Insight.Group( [ Insight.Price(self.pair[0].Symbol, self.period, InsightDirection.Up), Insight.Price(self.pair[1].Symbol, self.period, InsightDirection.Down) ]) if indicator_valid and sell_signal: return Insight.Group( [ Insight.Price(self.pair[0].Symbol, self.period, InsightDirection.Down), Insight.Price(self.pair[1].Symbol, self.period, InsightDirection.Up) ]) return [] def OnSecuritiesChanged(self, algorithm, changes): self.pair = [x for x in changes.AddedSecurities] #1. Call for 500 bars of history data for each symbol in the pair and save to the variable history history = algorithm.History([x.Symbol for x in self.pair], 500) #2. Unstack the Pandas data frame to reduce it to the history close price history = history.close.unstack(level=0) #3. Iterate through the history tuple and update the mean and standard deviation with historical data for tuple in history.itertuples(): self.spreadMean1.Update(tuple[0], tuple[2]-tuple[1]) self.spreadMean2.Update(tuple[0], tuple[2]-tuple[1])