Overall Statistics
Total Orders
154
Average Win
0.28%
Average Loss
-0.20%
Compounding Annual Return
1.728%
Drawdown
2.900%
Expectancy
0.083
Start Equity
100000
End Equity
101183.04
Net Profit
1.183%
Sharpe Ratio
0.115
Sortino Ratio
0.111
Probabilistic Sharpe Ratio
22.846%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
1.38
Alpha
0.007
Beta
-0.017
Annual Standard Deviation
0.044
Annual Variance
0.002
Information Ratio
-0.272
Tracking Error
0.331
Treynor Ratio
-0.301
Total Fees
$254.14
Estimated Strategy Capacity
$15000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
58.04%
from AlgorithmImports import *

class OrderBookImbalanceStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 4)
        self.SetEndDate(2020, 9, 10)
        self.SetCash(100000)
        
        # Add SPY with quote data explicitly requested
        equity = self.AddEquity("SPY", Resolution.Minute)
        equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.symbol = equity.Symbol
        
        # Initialize SMA indicators for bid and ask volumes
        self.bid_volume_sma = SimpleMovingAverage(5)
        self.ask_volume_sma = SimpleMovingAverage(5)
        
        # Position tracking flags
        self.is_long = False
        self.is_short = False
        
        # Schedule trading decisions every 5 minutes
        self.Schedule.On(self.DateRules.EveryDay(self.symbol), 
                         self.TimeRules.Every(timedelta(minutes=5)), 
                         self.ExecuteTrades)
        
        # Create custom chart to track OBI values
        self.Plot("Strategy", "OBI", 0)

    def OnData(self, data):
        # Specifically check for QuoteBars data
        if not data.QuoteBars.ContainsKey(self.symbol):
            return
        
        # Get the quote bar
        quote_bar = data.QuoteBars[self.symbol]
        
        # Update SMAs with bid and ask volumes
        self.bid_volume_sma.Update(self.Time, quote_bar.LastBidSize)
        self.ask_volume_sma.Update(self.Time, quote_bar.LastAskSize)
    
    def ExecuteTrades(self):
        # Check if SMAs are ready
        if not self.bid_volume_sma.IsReady or not self.ask_volume_sma.IsReady:
            return
            
        # Get current values from SMAs
        bid_vol = self.bid_volume_sma.Current.Value
        ask_vol = self.ask_volume_sma.Current.Value
        
        # Avoid division by zero
        if bid_vol + ask_vol == 0:
            self.Debug("Skipping trade evaluation: Bid + Ask volume is zero")
            return
            
        # Calculate Order Book Imbalance using the formula: OBI(t) = (B(t) - A(t)) / (B(t) + A(t))
        obi = (bid_vol - ask_vol) / (bid_vol + ask_vol)
        
        # Plot the current OBI value
        self.Plot("Strategy", "OBI", obi)
        
        # Log OBI value
        self.Debug(f"Time: {self.Time}, OBI: {obi}")
        
        # Trading logic based on OBI thresholds
        # Long signal - OBI > 0.7
        if obi > 0.9 and not self.is_long:
            if self.is_short:
                # Close short position first
                self.Liquidate()
                self.is_short = False
                
            # Enter long position
            self.SetHoldings(self.symbol, 0.95)  # Using 95% of portfolio
            self.is_long = True
            self.Debug(f"LONG signal at OBI: {obi}")
            
        # Short signal - OBI < -0.7
        elif obi < -0.9 and not self.is_short:
            if self.is_long:
                # Close long position first
                self.Liquidate()
                self.is_long = False
                
            # Enter short position
            self.SetHoldings(self.symbol, -0.95)  # Using 95% of portfolio for short
            self.is_short = True
            self.Debug(f"SHORT signal at OBI: {obi}")
            
        # Exit long position when OBI drops below 0.7
        elif self.is_long and obi < 0.7:
            self.Liquidate()
            self.is_long = False
            self.Debug(f"EXIT LONG at OBI: {obi}")
            
        # Exit short position when OBI rises above -0.7
        elif self.is_short and obi > -0.7:
            self.Liquidate()
            self.is_short = False
            self.Debug(f"EXIT SHORT at OBI: {obi}")