QuantConnect/Lean · error · AssertionError

Expected position group buying power model type: OptionStrat

Error message

Expected position group buying power model type: OptionStrategyPositionGroupBuyingPowerModel. Actual: {type(position_group.buying_power_model).__name__}

What it means

In Lean, when legs of a recognized option strategy are traded together, the portfolio groups them into a PositionGroup whose buying power is governed by an OptionStrategyPositionGroupBuyingPowerModel (so margin is computed on the combo, not leg-by-leg). This assertion, run in on_data after the strategy is invested, verifies the first position group's buying_power_model is of that exact type. Failure means the combo was not recognized as a strategy group — the legs were booked as independent positions with a different (e.g. SecurityPositionGroupBuyingPowerModel) model.

Source

Thrown at Algorithm.Python/OptionStrategyFactoryMethodsBaseAlgorithm.py:45

        option = self.add_option("GOOG")
        self._option_symbol = option.symbol

        option.set_filter(lambda u: u.standards_only().strikes(-2, +2).expiration(0, 180))

        self.set_benchmark("GOOG")

    def on_data(self, slice):
        if not self.portfolio.invested:
            chain = slice.option_chains.get(self._option_symbol)
            if chain is not None:
                self.trade_strategy(chain, self._option_symbol)
        else:
            # Verify that the strategy was traded
            position_group = list(self.portfolio.positions.groups)[0]

            buying_power_model = position_group.buying_power_model
            if not isinstance(buying_power_model, OptionStrategyPositionGroupBuyingPowerModel):
                raise AssertionError("Expected position group buying power model type: OptionStrategyPositionGroupBuyingPowerModel. "
                                f"Actual: {type(position_group.buying_power_model).__name__}")

            self.assert_strategy_position_group(position_group, self._option_symbol)

            # Now we should be able to close the position
            self.liquidate_strategy()

            # We can quit now, no more testing required
            self.quit()

    def on_end_of_algorithm(self):
        if self.portfolio.invested:
            raise AssertionError("Expected no holdings at end of algorithm")

        orders_count = len(list(self.transactions.get_orders(lambda order: order.status == OrderStatus.FILLED)))
        if orders_count != self.expected_orders_count():
            raise AssertionError(f"Expected {self.expected_orders_count()} orders to have been submitted and filled, "
                            f"half for buying the strategy and the other half for the liquidation. Actual {orders_count}")

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Ensure all strategy legs are submitted as a single combo order via OptionStrategies (e.g. self.Buy(option_strategy, quantity)) so they form one position group.
  2. Check the subclass's trade_strategy implementation uses the correct OptionStrategies factory method matching a recognized canonical strategy.
  3. If you changed Lean's grouping logic, re-register the strategy so its group resolves to OptionStrategyPositionGroupBuyingPowerModel.
  4. Inspect self.portfolio.positions.groups count and contents to confirm legs were grouped rather than split.

Example fix

# before: legs booked separately -> SecurityPositionGroupBuyingPowerModel
self.buy(call.symbol, 1); self.sell(put.symbol, 1)
# after: submit as a recognized strategy combo so the group uses the strategy BPM
strategy = OptionStrategies.Straddle(self._option_symbol)
self.buy(strategy, 1)
Defensive patterns

Strategy: type-guard

Validate before calling

# Before asserting, confirm a strategy group exists and inspect its BPM
from QuantConnect.Securities.Positions import OptionStrategyPositionGroupBuyingPowerModel
groups = list(self.portfolio.positions.groups)
if not groups:
    return  # nothing invested yet
bpm = groups[0].buying_power_model
if not isinstance(bpm, OptionStrategyPositionGroupBuyingPowerModel):
    # legs were not grouped as a strategy; resubmit as a combo
    self.log(f'unexpected BPM: {type(bpm).__name__}')

Type guard

from QuantConnect.Securities.Positions import OptionStrategyPositionGroupBuyingPowerModel

def is_strategy_group(group) -> bool:
    return isinstance(group.buying_power_model,
                      OptionStrategyPositionGroupBuyingPowerModel)

Prevention

When it happens

Trigger: After the algorithm is invested, reading list(self.portfolio.positions.groups)[0].buying_power_model and finding it is not an OptionStrategyPositionGroupBuyingPowerModel. Occurs when the option strategy legs do not match a canonical strategy definition, when the strategy factory method changed, or when position-grouping/buying-power-model assignment logic in Lean changed.

Common situations: Subclassing this base algorithm and calling a trade method whose legs no longer satisfy a registered OptionStrategy pattern; a Lean engine refactor of position grouping or buying-power-model resolution; trading legs in separate orders so they never form one group.

Related errors


AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13). Data as JSON: /api/errors/bdf004d3fc573827. Report an issue: GitHub.