{"record":{"id":"040c2feaae581804","repo":"QuantConnect/Lean","slug":"expected-no-holdings-at-end-of-algorithm-but-are","errorCode":null,"errorMessage":"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.id) for i in self.portfolio.keys()])}","messagePattern":"Expected no holdings at end of algorithm, but are invested in: (.+?)","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"Algorithm.Python/FutureOptionShortPutOTMExpiryRegressionAlgorithm.py","lineNumber":104,"sourceCode":"\n        else:\n            raise AssertionError(f\"Received order event for unknown Symbol: {order_event.symbol}\")\n\n        self.log(f\"{order_event}\")\n\n    def assert_future_option_contract_order(self, order_event: OrderEvent, option_contract: Security):\n        if order_event.direction == OrderDirection.SELL and option_contract.holdings.quantity != -1:\n            raise AssertionError(f\"No holdings were created for option contract {option_contract.symbol}\")\n\n        if order_event.direction == OrderDirection.BUY and option_contract.holdings.quantity != 0:\n            raise AssertionError(\"Expected no options holdings after closing position\")\n\n        if order_event.is_assignment:\n            raise AssertionError(f\"Assignment was not expected for {order_event.symbol}\")\n\n    def on_end_of_algorithm(self):\n        if self.portfolio.invested:\n            raise AssertionError(f\"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.id) for i in self.portfolio.keys()])}\")\n","sourceCodeStart":86,"sourceCodeEnd":105,"githubUrl":"https://github.com/QuantConnect/Lean/blob/d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892/Algorithm.Python/FutureOptionShortPutOTMExpiryRegressionAlgorithm.py#L86-L105","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the printed position IDs to see exactly which symbol is still invested.","Ensure every opened position has a matching close before algorithm end (the short put should be bought back or expire worthless flat).","Investigate assignment/liquidation events (see errors 133/137) that may have created residual holdings."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Before end-of-algorithm, flatten any residual positions\nfor symbol, holding in self.portfolio.items():\n    if holding.quantity != 0:\n        self.market_order(symbol, -holding.quantity)\n        algorithm.Debug(f'Flattened residual {symbol} qty={holding.quantity}')","typeGuard":"def portfolio_is_flat(portfolio) -> bool:\n    return all(h.quantity == 0 for h in portfolio.Values)","tryCatchPattern":null,"preventionTips":["Close every opened position explicitly; don't rely solely on expiry to flatten.","Run an end-of-algorithm flatness check during development to surface residual holdings early."],"tags":["regression-test","portfolio","holdings","future-options"],"backgroundTag":null,"analyzedSha":"d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892","analyzedAt":"2026-08-13T13:52:21.013Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}