Overall Statistics |
Total Orders 1371 Average Win 1.69% Average Loss -2.20% Compounding Annual Return 9.305% Drawdown 35.600% Expectancy 0.160 Start Equity 10000 End Equity 82384.21 Net Profit 723.842% Sharpe Ratio 0.356 Sortino Ratio 0.296 Probabilistic Sharpe Ratio 0.104% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 0.77 Alpha 0.025 Beta 0.621 Annual Standard Deviation 0.15 Annual Variance 0.023 Information Ratio 0.057 Tracking Error 0.128 Treynor Ratio 0.086 Total Fees $2563.09 Estimated Strategy Capacity $27000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 15.78% |
from AlgorithmImports import * class IBSMeanReversionOnIndex(QCAlgorithm): """ Internal Bar Strength Strategy 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 = "QQQ" # 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)