QuantConnect/Lean · error · AssertionError

Expected no holdings at end of algorithm, but are invested i

Error message

Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.id) for i in self.portfolio.keys()])}

What it means

FutureOptionShortPutOTMExpiryRegressionAlgorithm.on_end_of_algorithm asserts the portfolio is fully flat at the end: a worthless OTM put that was opened and closed should leave no holdings. If self.portfolio.invested is true, it lists the offending position IDs.

Source

Thrown at Algorithm.Python/FutureOptionShortPutOTMExpiryRegressionAlgorithm.py:104

        else:
            raise AssertionError(f"Received order event for unknown Symbol: {order_event.symbol}")

        self.log(f"{order_event}")

    def assert_future_option_contract_order(self, order_event: OrderEvent, option_contract: Security):
        if order_event.direction == OrderDirection.SELL and option_contract.holdings.quantity != -1:
            raise AssertionError(f"No holdings were created for option contract {option_contract.symbol}")

        if order_event.direction == OrderDirection.BUY and option_contract.holdings.quantity != 0:
            raise AssertionError("Expected no options holdings after closing position")

        if order_event.is_assignment:
            raise AssertionError(f"Assignment was not expected for {order_event.symbol}")

    def on_end_of_algorithm(self):
        if self.portfolio.invested:
            raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.id) for i in self.portfolio.keys()])}")

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Read the printed position IDs to see exactly which symbol is still invested.
  2. Ensure every opened position has a matching close before algorithm end (the short put should be bought back or expire worthless flat).
  3. Investigate assignment/liquidation events (see errors 133/137) that may have created residual holdings.
Defensive patterns

Strategy: validation

Validate before calling

# Before end-of-algorithm, flatten any residual positions
for symbol, holding in self.portfolio.items():
    if holding.quantity != 0:
        self.market_order(symbol, -holding.quantity)
        algorithm.Debug(f'Flattened residual {symbol} qty={holding.quantity}')

Type guard

def portfolio_is_flat(portfolio) -> bool:
    return all(h.quantity == 0 for h in portfolio.Values)

Prevention

When it happens

Trigger: At algorithm termination, Portfolio.Invested is true — some symbol still has a non-zero holdings quantity. The error message enumerates which security IDs remain invested.

Common situations: A position was never closed (open short never bought back), an assignment/exercise created a residual future or option position, or a corporate-action/cash adjustment left holdings.

Related errors


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