Overall Statistics |
Total Orders 811 Average Win 4.19% Average Loss -5.73% Compounding Annual Return 7.368% Drawdown 61.400% Expectancy 0.112 Start Equity 10000 End Equity 53846.09 Net Profit 438.461% Sharpe Ratio 0.25 Sortino Ratio 0.148 Probabilistic Sharpe Ratio 0.004% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 0.73 Alpha 0.03 Beta 0.76 Annual Standard Deviation 0.263 Annual Variance 0.069 Information Ratio 0.079 Tracking Error 0.237 Treynor Ratio 0.087 Total Fees $10728.49 Estimated Strategy Capacity $11000000.00 Lowest Capacity Asset TQQQ UK280CGTCB51 Portfolio Turnover 9.36% |
from AlgorithmImports import * class IBSMeanReversionOnIndex(QCAlgorithm): """ Inspired by Quantitativo: https://www.quantitativo.com/p/robustness-of-the-211-sharpe-mean Additional reference: https://tradingstrategy.medium.com/the-internal-bar-strength-ibs-indicator-trading-strategies-rules-video-e638f135edb6 Entry rules: ----------------------- Compute the rolling mean of High minus Low over the last 25 days; Compute the IBS indicator: (Close - Low) / (High - Low); Compute a lower band as the rolling High over the last 10 days minus 2.5 x the rolling mean of High mins Low (first bullet); Go long whenever SPY closes under the lower band (3rd bullet) and IBS is lower than 0.3; Exit rules ----------------------- - Close the trade whenever the SPY close is higher than yesterday's high; - Close the trade whenever the price is lower than the 300-SMA. """ ## Initialize the algo ## ------------------------ def Initialize(self): # Init backtest params, etc self.ticker = "TQQQ" # Ticker symbol to trade self.SetBenchmark("SPY") # Benchmark for reporting (buy and hold) self.SetStartDate(2000, 12, 1) # Backtest start date # self.SetEndDate(2023, 7, 11) # Backtest end date self.SetCash(10000) # Starting portfolio balance # Subscrbe to a minute data feed (minute bars) self.symbol = self.AddEquity(self.ticker, Resolution.Minute).symbol # Set up a rollingwindow to store consolidated daily bars self.dailyBars = RollingWindow[TradeBar](2) # Set up the daily bar consolidator self.dailyConsolidator = TradeBarConsolidator(timedelta(days=1)) self.dailyConsolidator.DataConsolidated += self.OnDailyBarFormed self.SubscriptionManager.AddConsolidator(self.symbol, self.dailyConsolidator) # 300 SMA self.sma_300 = self.sma(self.symbol, 300, Resolution.Daily) # Schedule a daily chron job to check for signals at the open self.Schedule.On(self.DateRules.EveryDay(), \ self.TimeRules.AfterMarketOpen(self.ticker, 5), self.CheckForSignals) def OnDailyBarFormed(self, val, dailyBar): self.dailyBars.add(dailyBar) def CheckForSignals(self): if not self.Portfolio.Invested: if self.EntrySignalFired(): self.SetHoldings(self.ticker, 1) else: if self.ExitSignalFired(): self.LiquidateWithMsg(self.symbol,f"IBS Signal Exit") # self.Liquidate(tag=f"IBS Signal Exit") ## Go long when: ## - SPy closes under lower band ## - IBS < 0.3 ## ------------------------------ def EntrySignalFired(self): if self.dailyBars.IsReady: # if SPY closes under the lower band (3rd bullet) # If IBS is lower than 0.3; # IBS= (Close-Low)/(High-Low) lastBar = self.dailyBars[0] if(lastBar.high != lastBar.low): ibsValue = (lastBar.close - lastBar.low) / (lastBar.high - lastBar.low) return (ibsValue < 0.2 ) return False ## Exit the trade when: # - SPY close is higher than yesterday's high; # - price is lower than the 300-SMA. ## ------------------------------------------------------------------------------- def ExitSignalFired(self): if self.dailyBars.IsReady: # if SPY closes under the lower band (3rd bullet) # If IBS is lower than 0.3; # IBS= (Close-Low)/(High-Low) lastBar = self.dailyBars[0] if(lastBar.high != lastBar.low): ibsValue = (lastBar.close - lastBar.low) / (lastBar.high - lastBar.low) return (ibsValue > 0.8 ) return False # if self.Portfolio.Invested: # if (self.dailyBars[0].close > self.dailyBars[1].high): # self.Liquidate(tag=f"Exit @ last close > prev high: {self.dailyBars[0].close} > {self.dailyBars[1].high}") # Convenience method to liquidate with PnL message def LiquidateWithMsg(self, symbol, exitReason): pnl = round(100 * self.Portfolio[symbol].UnrealizedProfitPercent,2) biasText = 'Long' if (self.Portfolio[symbol].IsLong) else 'Short' winlossText = 'win' if pnl > 0 else 'loss' orderNote = f"[{pnl}% {winlossText}] {exitReason} | Exiting {biasText} position" self.liquidate(symbol, tag=orderNote)