The issue is that Combo Leg Limit order is not filled with same parameters in same situation where Limit orders for individual legs are filled. Why?

Debug code 1: Individual legs Limit Order (This fills both orders in backtest)

from AlgorithmImports import *
from datetime import datetime, timedelta


class AlabChainCacheHistoryDebug(QCAlgorithm):
   """
   Minimal debug algorithm to compare, at specific times:
   - OptionContract.BidPrice from slice.OptionChains (chain contracts)
   - Securities[contract].BidPrice / AskPrice (security cache)
   - History(QuoteBar, contract, ...) snapshot

   Target window: 2025-01-06 10:02–10:04 (America/New_York).
   """

   def Initialize(self) -> None:
       # Cover the original entry (Jan 2) and exit (Jan 6) window
       self.SetStartDate(2025, 1, 2)
       self.SetEndDate(2025, 1, 6)
       self.SetCash(100000)

       # Ensure algorithm time is US/Eastern to match your earlier logs
       self.SetTimeZone("America/New_York")

       # Subscriptions
       self.alab = self.AddEquity("ALAB", Resolution.Minute).Symbol

       # Canonical option subscription so we get OptionChains in the Slice
       self.alab_opt = self.AddOption("ALAB", Resolution.Minute)
       self.alab_opt.SetFilter(-50, 50, timedelta(days=0), timedelta(days=60))
       self.alab_opt_symbol = self.alab_opt.Symbol

       # Specific contracts
       self.call_osi = "ALAB 250117C00150000"
       self.put_osi = "ALAB 250117P00115000"

       self.call_symbol = SymbolRepresentation.ParseOptionTickerOSI(
           self.call_osi, SecurityType.Option, OptionStyle.American, Market.USA
       )
       self.put_symbol = SymbolRepresentation.ParseOptionTickerOSI(
           self.put_osi, SecurityType.Option, OptionStyle.American, Market.USA
       )

       # Subscribe to the individual contracts too (so Securities cache has direct contract data)
       self.AddOptionContract(self.call_symbol, Resolution.Minute)
       self.AddOptionContract(self.put_symbol, Resolution.Minute)

       # Reduce noise / synthetic bars
       self.Securities[self.alab].SetDataNormalizationMode(DataNormalizationMode.Raw)
       self.Securities[self.call_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
       self.Securities[self.put_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)

       self.Settings.FillForward = False
       self._printed = set()
       # State for entry/exit sequence
       self._legs_bought = False
       self._legs_sold = False

   def OnData(self, slice: Slice) -> None:
       # 1) Always compute chain and cache quotes so we can reuse them for different dates
       chain = slice.OptionChains.get(self.alab_opt_symbol)
       call_chain_bid = None
       put_chain_bid = None
       if chain is not None:
           contracts_iter = getattr(chain, "Contracts", None)
           if contracts_iter is not None and hasattr(contracts_iter, "values"):
               contracts_iter = contracts_iter.values()
           else:
               contracts_iter = chain
           try:
               contracts_list = list(contracts_iter)
           except Exception:
               contracts_list = []

           call_contract = next((c for c in contracts_list if getattr(c, "Symbol", None) == self.call_symbol), None)
           put_contract = next((c for c in contracts_list if getattr(c, "Symbol", None) == self.put_symbol), None)
           call_chain_bid = getattr(call_contract, "BidPrice", None) if call_contract is not None else None
           put_chain_bid = getattr(put_contract, "BidPrice", None) if put_contract is not None else None

       # 2) Security cache bids/asks (what fill models typically reference)
       call_sec = self.Securities.get(self.call_symbol)
       put_sec = self.Securities.get(self.put_symbol)
       call_cache_bid = float(call_sec.BidPrice) if call_sec is not None and call_sec.BidPrice is not None else None
       call_cache_ask = float(call_sec.AskPrice) if call_sec is not None and call_sec.AskPrice is not None else None
       put_cache_bid = float(put_sec.BidPrice) if put_sec is not None and put_sec.BidPrice is not None else None
       put_cache_ask = float(put_sec.AskPrice) if put_sec is not None and put_sec.AskPrice is not None else None

       # 3) On Jan 6, 10:02–10:04, log the same window diagnostics as before
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 6
           and self.Time.hour == 10
           and self.Time.minute in (2, 3, 4)
       ):
           key = self.Time
           if key not in self._printed:
               self._printed.add(key)

               self.Debug(
                   "ALAB dbg {} chain_bid(call,put)=({}, {}) cache_bidask(call)=({},{}) cache_bidask(put)=({},{}) chain_in_slice={}".format(
                       self.Time,
                       (float(call_chain_bid) if call_chain_bid is not None else None),
                       (float(put_chain_bid) if put_chain_bid is not None else None),
                       call_cache_bid,
                       call_cache_ask,
                       put_cache_bid,
                       put_cache_ask,
                       chain is not None,
                   )
               )

               try:
                   h_call = self.History(QuoteBar, self.call_symbol, 3, Resolution.Minute)
                   h_put = self.History(QuoteBar, self.put_symbol, 3, Resolution.Minute)
                   # In python, History returns a pandas DataFrame in QC; we print a compact tail.
                   if h_call is not None and hasattr(h_call, "tail"):
                       self.Debug("ALAB dbg hist call tail:\n{}".format(h_call.tail(3)))
                   else:
                       self.Debug("ALAB dbg hist call: {}".format(h_call))
                   if h_put is not None and hasattr(h_put, "tail"):
                       self.Debug("ALAB dbg hist put tail:\n{}".format(h_put.tail(3)))
                   else:
                       self.Debug("ALAB dbg hist put: {}".format(h_put))
               except Exception as e:
                   self.Debug("ALAB dbg history error: {}".format(e))

       # 4) On Jan 2, 15:32, open one contract per leg with per-leg limit BUY at the current ask
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 2
           and self.Time.hour == 15
           and self.Time.minute == 32
           and not self._legs_bought
       ):
           if (
               call_cache_ask is not None
               and put_cache_ask is not None
               and call_cache_ask > 0
               and put_cache_ask > 0
           ):
               try:
                   self.Debug(
                       "ALAB dbg submitting entry per-leg buys at {} call_ask={} put_ask={}".format(
                           self.Time, call_cache_ask, put_cache_ask
                       )
                   )
                   # Buy 1 call and 1 put at the current ask
                   self.LimitOrder(
                       self.call_symbol,
                       1,
                       call_cache_ask,
                       tag="ALAB dbg entry call leg",
                   )
                   self.LimitOrder(
                       self.put_symbol,
                       1,
                       put_cache_ask,
                       tag="ALAB dbg entry put leg",
                   )
                   self._legs_bought = True
               except Exception as e:
                   self.Debug("ALAB dbg entry per-leg submit error: {}".format(e))

       # 5) On Jan 6, 10:03, close the legs with per-leg limit SELL at the current bid (Exit-style)
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 6
           and self.Time.hour == 10
           and self.Time.minute == 3
           and self._legs_bought
           and not self._legs_sold
       ):
           if (
               call_cache_bid is not None
               and put_cache_bid is not None
               and call_cache_bid > 0
               and put_cache_bid > 0
           ):
               try:
                   self.Debug(
                       "ALAB dbg submitting exit per-leg sells at {} call_bid={} put_bid={}".format(
                           self.Time, call_cache_bid, put_cache_bid
                       )
                   )
                   self.LimitOrder(
                       self.call_symbol,
                       -1,
                       call_cache_bid,
                       tag="ALAB dbg exit call leg",
                   )
                   self.LimitOrder(
                       self.put_symbol,
                       -1,
                       put_cache_bid,
                       tag="ALAB dbg exit put leg",
                   )
                   self._legs_sold = True
               except Exception as e:
                   self.Debug("ALAB dbg exit per-leg submit error: {}".format(e))


Orders log:
 

TimeSymbolPriceQuantityTypeStatusValueTag2025-01-02T20:32:00ZALAB 250117C001500002.051LimitFilled2.05ALAB dbg entry call leg2025-01-02T20:32:00ZALAB 250117P001150001.61LimitFilled1.6ALAB dbg entry put leg2025-01-06T15:03:00ZALAB 250117C001500005.3-1LimitFilled-5.3ALAB dbg exit call leg2025-01-06T15:03:00ZALAB 250117P001150000.2-1LimitFilled-0.2ALAB dbg exit put leg



Debug code 2: Combo Leg Limit Order (Order remains in Submitted state in backtest, does not fill)

from AlgorithmImports import *
from datetime import datetime, timedelta


class AlabChainCacheHistoryDebug(QCAlgorithm):
   """
   Minimal debug algorithm to compare, at specific times:
   - OptionContract.BidPrice from slice.OptionChains (chain contracts)
   - Securities[contract].BidPrice / AskPrice (security cache)
   - History(QuoteBar, contract, ...) snapshot

   Target window: 2025-01-06 10:02–10:04 (America/New_York).
   """

   def Initialize(self) -> None:
       # Cover the original entry (Jan 2) and exit (Jan 6) window
       self.SetStartDate(2025, 1, 2)
       self.SetEndDate(2025, 1, 6)
       self.SetCash(100000)

       # Ensure algorithm time is US/Eastern to match your earlier logs
       self.SetTimeZone("America/New_York")

       # Subscriptions
       self.alab = self.AddEquity("ALAB", Resolution.Minute).Symbol

       # Canonical option subscription so we get OptionChains in the Slice
       self.alab_opt = self.AddOption("ALAB", Resolution.Minute)
       self.alab_opt.SetFilter(-50, 50, timedelta(days=0), timedelta(days=60))
       self.alab_opt_symbol = self.alab_opt.Symbol

       # Specific contracts
       self.call_osi = "ALAB 250117C00150000"
       self.put_osi = "ALAB 250117P00115000"

       self.call_symbol = SymbolRepresentation.ParseOptionTickerOSI(
           self.call_osi, SecurityType.Option, OptionStyle.American, Market.USA
       )
       self.put_symbol = SymbolRepresentation.ParseOptionTickerOSI(
           self.put_osi, SecurityType.Option, OptionStyle.American, Market.USA
       )

       # Subscribe to the individual contracts too (so Securities cache has direct contract data)
       self.AddOptionContract(self.call_symbol, Resolution.Minute)
       self.AddOptionContract(self.put_symbol, Resolution.Minute)

       # Reduce noise / synthetic bars
       self.Securities[self.alab].SetDataNormalizationMode(DataNormalizationMode.Raw)
       self.Securities[self.call_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
       self.Securities[self.put_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)

       self.Settings.FillForward = False
       self._printed = set()
       # State for entry/exit sequence
       self._legs_bought = False
       self._legs_sold = False

   def OnData(self, slice: Slice) -> None:
       # 1) Always compute chain and cache quotes so we can reuse them for different dates
       chain = slice.OptionChains.get(self.alab_opt_symbol)
       call_chain_bid = None
       put_chain_bid = None
       if chain is not None:
           contracts_iter = getattr(chain, "Contracts", None)
           if contracts_iter is not None and hasattr(contracts_iter, "values"):
               contracts_iter = contracts_iter.values()
           else:
               contracts_iter = chain
           try:
               contracts_list = list(contracts_iter)
           except Exception:
               contracts_list = []

           call_contract = next((c for c in contracts_list if getattr(c, "Symbol", None) == self.call_symbol), None)
           put_contract = next((c for c in contracts_list if getattr(c, "Symbol", None) == self.put_symbol), None)
           call_chain_bid = getattr(call_contract, "BidPrice", None) if call_contract is not None else None
           put_chain_bid = getattr(put_contract, "BidPrice", None) if put_contract is not None else None

       # 2) Security cache bids/asks (what fill models typically reference)
       call_sec = self.Securities.get(self.call_symbol)
       put_sec = self.Securities.get(self.put_symbol)
       call_cache_bid = float(call_sec.BidPrice) if call_sec is not None and call_sec.BidPrice is not None else None
       call_cache_ask = float(call_sec.AskPrice) if call_sec is not None and call_sec.AskPrice is not None else None
       put_cache_bid = float(put_sec.BidPrice) if put_sec is not None and put_sec.BidPrice is not None else None
       put_cache_ask = float(put_sec.AskPrice) if put_sec is not None and put_sec.AskPrice is not None else None

       # 3) On Jan 6, 10:02–10:04, log the same window diagnostics as before
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 6
           and self.Time.hour == 10
           and self.Time.minute in (2, 3, 4)
       ):
           key = self.Time
           if key not in self._printed:
               self._printed.add(key)

               self.Debug(
                   "ALAB dbg {} chain_bid(call,put)=({}, {}) cache_bidask(call)=({},{}) cache_bidask(put)=({},{}) chain_in_slice={}".format(
                       self.Time,
                       (float(call_chain_bid) if call_chain_bid is not None else None),
                       (float(put_chain_bid) if put_chain_bid is not None else None),
                       call_cache_bid,
                       call_cache_ask,
                       put_cache_bid,
                       put_cache_ask,
                       chain is not None,
                   )
               )

               try:
                   h_call = self.History(QuoteBar, self.call_symbol, 3, Resolution.Minute)
                   h_put = self.History(QuoteBar, self.put_symbol, 3, Resolution.Minute)
                   # In python, History returns a pandas DataFrame in QC; we print a compact tail.
                   if h_call is not None and hasattr(h_call, "tail"):
                       self.Debug("ALAB dbg hist call tail:\n{}".format(h_call.tail(3)))
                   else:
                       self.Debug("ALAB dbg hist call: {}".format(h_call))
                   if h_put is not None and hasattr(h_put, "tail"):
                       self.Debug("ALAB dbg hist put tail:\n{}".format(h_put.tail(3)))
                   else:
                       self.Debug("ALAB dbg hist put: {}".format(h_put))
               except Exception as e:
                   self.Debug("ALAB dbg history error: {}".format(e))

       # 4) On Jan 2, 15:32, open the pair with a ComboLegLimitOrder BUY at the current asks
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 2
           and self.Time.hour == 15
           and self.Time.minute == 32
           and not self._legs_bought
       ):
           if (
               call_cache_ask is not None
               and put_cache_ask is not None
               and call_cache_ask > 0
               and put_cache_ask > 0
           ):
               try:
                   self.Debug(
                       "ALAB dbg submitting ENTRY combo at {} call_ask={} put_ask={}".format(
                           self.Time, call_cache_ask, put_cache_ask
                       )
                   )
                   # Buy 1 call and 1 put as a combo at the current asks
                   entry_legs = [
                       Leg.Create(self.call_symbol, 1, call_cache_ask),
                       Leg.Create(self.put_symbol, 1, put_cache_ask),
                   ]
                   self.ComboLegLimitOrder(entry_legs, 1, tag="ALAB dbg ENTRY combo")
                   self._legs_bought = True
               except Exception as e:
                   self.Debug("ALAB dbg ENTRY combo submit error: {}".format(e))

       # 5) On Jan 6, 10:03, close the pair with a ComboLegLimitOrder SELL at the current bids (Exit-style)
       if (
           self.Time.year == 2025
           and self.Time.month == 1
           and self.Time.day == 6
           and self.Time.hour == 10
           and self.Time.minute == 3
           and self._legs_bought
           and not self._legs_sold
       ):
           if (
               call_cache_bid is not None
               and put_cache_bid is not None
               and call_cache_bid > 0
               and put_cache_bid > 0
           ):
               try:
                   self.Debug(
                       "ALAB dbg submitting EXIT combo at {} call_bid={} put_bid={}".format(
                           self.Time, call_cache_bid, put_cache_bid
                       )
                   )
                   exit_legs = [
                       Leg.Create(self.call_symbol, -1, call_cache_bid),
                       Leg.Create(self.put_symbol, -1, put_cache_bid),
                   ]
                   self.ComboLegLimitOrder(exit_legs, 1, tag="ALAB dbg EXIT combo")
                   self._legs_sold = True
               except Exception as e:
                   self.Debug("ALAB dbg EXIT combo submit error: {}".format(e))


Orders log:
 

TimeSymbolPriceQuantityTypeStatusValueTag2025-01-02T20:32:00ZALAB 250117C001500002.051 Filled2.05ALAB dbg ENTRY combo2025-01-02T20:32:00ZALAB 250117P001150001.61 Filled1.6ALAB dbg ENTRY combo2025-01-06T15:03:00ZALAB 250117C001500000-1 Submitted0ALAB dbg EXIT combo2025-01-06T15:03:00ZALAB 250117P001150000-1 Submitted0ALAB dbg EXIT combo