{"record":{"id":"f55ec61b293060c1","repo":"QuantConnect/Lean","slug":"algorithm-should-have-not-run-on-extended-hours-fo","errorCode":null,"errorMessage":"Algorithm should have not run on extended hours for {self._gc.symbol} future, which did not enable extended market hours","messagePattern":"Algorithm should have not run on extended hours for (.+?) future, which did not enable extended market hours","errorType":"exception","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"Algorithm.Python/FuturesExtendedMarketHoursRegressionAlgorithm.py","lineNumber":72,"sourceCode":"\n        time_of_day = self.time.time()\n        current_time_is_regular_hours = (time_of_day >= time(9, 30, 0) and time_of_day < time(16, 15, 0)) or (time_of_day >= time(16, 30, 0) and time_of_day < time(17, 0, 0))\n        current_time_is_extended_hours = not current_time_is_regular_hours and (time_of_day < time(9, 30, 0) or time_of_day >= time(18, 0, 0))\n        if es_is_in_regular_hours != current_time_is_regular_hours or es_is_in_extended_hours != current_time_is_extended_hours:\n            raise AssertionError(\"At {Time}, {_es.symbol} is either in regular hours but current time is in extended hours, or viceversa\")\n\n    def on_end_of_algorithm(self):\n        if not self._es_ran_on_regular_hours:\n            raise AssertionError(f\"Algorithm should have run on regular hours for {self._es.symbol} future, which enabled extended market hours\")\n\n        if not self._es_ran_on_extended_hours:\n            raise AssertionError(f\"Algorithm should have run on extended hours for {self._es.symbol} future, which enabled extended market hours\")\n\n        if not self._gc_ran_on_regular_hours:\n            raise AssertionError(f\"Algorithm should have run on regular hours for {self._gc.symbol} future, which did not enable extended market hours\")\n\n        if self._gc_ran_on_extended_hours:\n            raise AssertionError(f\"Algorithm should have not run on extended hours for {self._gc.symbol} future, which did not enable extended market hours\")\n","sourceCodeStart":54,"sourceCodeEnd":73,"githubUrl":"https://github.com/QuantConnect/Lean/blob/d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892/Algorithm.Python/FuturesExtendedMarketHoursRegressionAlgorithm.py#L54-L73","documentation":"Asserted at the end of a futures regression algorithm (on_end_of_algorithm). It proves the Lean data-feed isolation contract: a future subscription created with extended_market_hours=False must NEVER receive OnData slices during that contract's extended (non-regular) exchange hours. The flag _gc_ran_on_extended_hours is set only when on_data fires while self._gc.exchange.hours.is_open(self.time, True) is True and is_open(self.time, False) is False for the Gold (GC) contract that was added with extended hours disabled.","triggerScenarios":"on_data runs and slice contains data for the GC canonical/mapped symbol while self._gc.exchange.hours.is_open(self.time, extended=True) is True but is_open(..., extended=False) is False, even though add_future(..., extended_market_hours=False) was used. This trips line 53 (_gc_ran_on_extended_hours |= ...) so on_end_of_algorithm line 72 raises.","commonSituations":"Lean engine regression after a change to the SecurityExchangeHours database, the extended-hours subscription routing in DataManager, fill-forward logic, or the futures canonical-symbol resolution. Also triggered when someone edits the algorithm to enable extended hours on GC, or when the exchange-hours JSON for CME metals is edited to fold extended ranges into regular hours.","solutions":["Confirm GC was added with extended_market_hours=False (line 28) and not mutated later; re-run the unmodified algorithm to rule out an edit.","Inspect Common/Data/market-hours for the CME/Gold entry: regular hours and extended hours ranges must be disjoint; if they overlap, is_open(extended=True) can be True while data still streams.","Check DataManager / SubscriptionManager routing: a subscription with extendedMarketHours=False must not receive slices outside regular hours; trace the fillForward / extendedHoursDataSubscription flags in Engine/DataFeeds.","Verify the test data folder (CME futures hour data) matches the exchange-hours definition; mismatched data coverage for extended periods can make slices appear outside regular hours."],"exampleFix":"// before: GC accidentally subscribed to extended hours\nself._gc = self.add_future(Futures.Metals.GOLD, Resolution.HOUR, fill_forward=True, extended_market_hours=True)\n// after: extended hours disabled, as the test expects\nself._gc = self.add_future(Futures.Metals.GOLD, Resolution.HOUR, fill_forward=True, extended_market_hours=False)","handlingStrategy":"validation","validationCode":"# before subscribing, decide and keep the extended-hours flag explicit\nuse_extended = False  # GC contract\ngc = self.add_future(Futures.Metals.GOLD, Resolution.HOUR, fill_forward=True, extended_market_hours=use_extended)\n# guard: never assert extended-hours behavior you did not opt into\nif not use_extended and self._gc_ran_on_extended_hours:\n    self.debug(f\"unexpected extended-hours data for {gc.symbol}\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass extended_market_hours explicitly (True/False) when adding futures; never rely on the default.","Keep a single source of truth for which subscriptions enable extended hours and assert against it.","When editing exchange-hours data files, verify regular and extended ranges stay disjoint."],"tags":["quantconnect","lean","regression","futures","extended-hours","data-feed","market-hours"],"backgroundTag":null,"analyzedSha":"d2c3659f877bfc2b5d9dc0fc89a9c7566f45e892","analyzedAt":"2026-08-13T13:52:21.013Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}