{"record":{"id":"de2f0475a5ca5924","repo":"QuantConnect/Lean","slug":"expected-no-holdings-at-end-of-algorithm","errorCode":null,"errorMessage":"Expected no holdings at end of algorithm","messagePattern":"Expected no holdings at end of algorithm","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"Algorithm.Python/OptionStrategyFactoryMethodsBaseAlgorithm.py","lineNumber":58,"sourceCode":"            # Verify that the strategy was traded\n            position_group = list(self.portfolio.positions.groups)[0]\n\n            buying_power_model = position_group.buying_power_model\n            if not isinstance(buying_power_model, OptionStrategyPositionGroupBuyingPowerModel):\n                raise AssertionError(\"Expected position group buying power model type: OptionStrategyPositionGroupBuyingPowerModel. \"\n                                f\"Actual: {type(position_group.buying_power_model).__name__}\")\n\n            self.assert_strategy_position_group(position_group, self._option_symbol)\n\n            # Now we should be able to close the position\n            self.liquidate_strategy()\n\n            # We can quit now, no more testing required\n            self.quit()\n\n    def on_end_of_algorithm(self):\n        if self.portfolio.invested:\n            raise AssertionError(\"Expected no holdings at end of algorithm\")\n\n        orders_count = len(list(self.transactions.get_orders(lambda order: order.status == OrderStatus.FILLED)))\n        if orders_count != self.expected_orders_count():\n            raise AssertionError(f\"Expected {self.expected_orders_count()} orders to have been submitted and filled, \"\n                            f\"half for buying the strategy and the other half for the liquidation. Actual {orders_count}\")\n\n    def expected_orders_count(self) -> int:\n        raise NotImplementedError(\"ExpectedOrdersCount method is not implemented\")\n\n    def trade_strategy(self, chain: OptionChain, option_symbol: Symbol) -> None:\n        raise NotImplementedError(\"TradeStrategy method is not implemented\")\n\n    def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol) -> None:\n        raise NotImplementedError(\"AssertStrategyPositionGroup method is not implemented\")\n\n    def liquidate_strategy(self) -> None:\n        raise NotImplementedError(\"LiquidateStrategy method is not implemented\")\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/QuantConnect/Lean/blob/d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892/Algorithm.Python/OptionStrategyFactoryMethodsBaseAlgorithm.py#L40-L76","documentation":"On algorithm end, Lean's OptionStrategyFactoryMethodsBaseAlgorithm asserts the portfolio is flat (self.portfolio.invested is False) after liquidate_strategy() was called in on_data. A failure means the liquidation orders did not fully close every leg of the option strategy position group, leaving residual holdings. This guards the engine's group-liquidation and fill logic.","triggerScenarios":"on_end_of_algorithm fires while self.portfolio.invested is still True. Happens when liquidate_strategy() did not submit/close all legs, when fill events were pending at quit(), when quit() was called before fills settled, or when a leg order was rejected/partially filled.","commonSituations":"Calling self.quit() immediately after liquidate_strategy() so the algorithm terminates before fill events settle; a subclass liquidate_strategy that omits a leg; a brokerage/fill model rejecting a closing order; running with a data slice that had no quotes to fill the close.","solutions":["Do not call self.quit() until after all liquidation fills are confirmed; instead wait for portfolio.invested to be False in on_data before quitting.","Verify the subclass liquidate_strategy() closes every leg of the strategy (mirror the open trades).","Check order tickets from liquidation for rejections or partial fills and resubmit if needed.","Ensure the algorithm end date leaves enough time after liquidation for fills to process."],"exampleFix":"# before\nself.liquidate_strategy()\nself.quit()  # fills may not have settled\n# after\nself.liquidate_strategy()\n# defer quit until flat, checked in on_data:\nif not self.portfolio.invested:\n    self.quit()","handlingStrategy":"validation","validationCode":"# Confirm flat before ending; defer quit until settled\nself.liquidate_strategy()\n# in on_data, only quit when truly flat:\nif not self.portfolio.invested and self._liquidation_sent:\n    self.quit()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not call quit() immediately after liquidation; wait for fills to settle.","Mirror every opening leg in the close.","Check order tickets for rejections/partial fills after liquidation.","Leave enough backtest time after the close for fills."],"tags":["options","liquidation","portfolio","regression-test","quantconnect","option-strategy"],"backgroundTag":null,"analyzedSha":"d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892","analyzedAt":"2026-08-13T13:52:21.013Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}