{"record":{"id":"655243b770b439c2","repo":"microsoft/qlib","slug":"trade-calendar-is-necessary-for-getting-traderange","errorCode":null,"errorMessage":"trade_calendar is necessary for getting TradeRangeByTime.","messagePattern":"trade_calendar is necessary for getting TradeRangeByTime\\.","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"qlib/backtest/decision.py","lineNumber":288,"sourceCode":"\n        **NOTE**:\n        - It is designed for minute-bar for intra-day trading!!!!!\n        - Both start_time and end_time are **closed** in the range\n\n        Parameters\n        ----------\n        start_time : str | time\n            e.g. \"9:30\"\n        end_time : str | time\n            e.g. \"14:30\"\n        \"\"\"\n        self.start_time = pd.Timestamp(start_time).time() if isinstance(start_time, str) else start_time\n        self.end_time = pd.Timestamp(end_time).time() if isinstance(end_time, str) else end_time\n        assert self.start_time < self.end_time\n\n    def __call__(self, trade_calendar: TradeCalendarManager) -> Tuple[int, int]:\n        if trade_calendar is None:\n            raise NotImplementedError(\"trade_calendar is necessary for getting TradeRangeByTime.\")\n\n        start_date = trade_calendar.start_time.date()\n        val_start, val_end = concat_date_time(start_date, self.start_time), concat_date_time(start_date, self.end_time)\n        return trade_calendar.get_range_idx(val_start, val_end)\n\n    def clip_time_range(self, start_time: pd.Timestamp, end_time: pd.Timestamp) -> Tuple[pd.Timestamp, pd.Timestamp]:\n        start_date = start_time.date()\n        val_start, val_end = concat_date_time(start_date, self.start_time), concat_date_time(start_date, self.end_time)\n        # NOTE: `end_date` should not be used. Because the `end_date` is for slicing. It may be in the next day\n        # Assumption: start_time and end_time is for intra-day trading. So it is OK for only using start_date\n        return max(val_start, start_time), min(val_end, end_time)\n\n\nclass BaseTradeDecision(Generic[DecisionType]):\n    \"\"\"\n    Trade decisions are made by strategy and executed by executor\n\n    Motivation:","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/backtest/decision.py#L270-L306","documentation":"TradeRangeByTime.__call__ requires a TradeCalendarManager to convert an intraday time window (e.g. '9:30'-'14:30') into calendar index positions. If the trade_calendar argument is None, there is no way to map wall-clock times onto the trading calendar, so a NotImplementedError is raised. In practice the calendar normally arrives via the 'inner_calendar' kwarg of BaseTradeDecision._get_range_limit, so a None calendar usually means that kwarg was not forwarded.","triggerScenarios":"Calling trade_range(trade_calendar=None) directly, or calling decision.get_range_limit()/_get_range_limit() on a decision whose trade_range is a TradeRangeByTime while kwargs lacks 'inner_calendar' (e.g. a custom executor or strategy calling get_range_limit without passing the inner nested calendar).","commonSituations":"Custom NestedExecutor implementations that call trade_decision.get_range_limit() without inner_calendar; strategies constructing TradeRangeByTime manually and invoking it; refactors that renamed or dropped the inner_calendar kwarg.","solutions":["When calling decision.get_range_limit(), pass the inner (nested) calendar: decision.get_range_limit(inner_calendar=inner_executor.trade_calendar)","If invoking the trade range object directly, supply the calendar: trade_range(trade_calendar=trade_calendar_manager)","In custom executors, only request range limits from decisions when you actually have the nested trade calendar to resolve them"],"exampleFix":"// before\nstart, end = decision.get_range_limit()  # inner_calendar missing -> NotImplementedError\n// after\nstart, end = decision.get_range_limit(inner_calendar=inner_executor.trade_calendar)","handlingStrategy":"validation","validationCode":"from qlib.backtest.decision import TradeRangeByTime\n\ndef resolve_range(decision, inner_calendar=None):\n    if inner_calendar is None:\n        return None  # caller must supply calendar; do not call trade_range\n    return decision.get_range_limit(inner_calendar=inner_calendar)","typeGuard":"def has_trade_calendar(cal) -> bool:\n    return cal is not None and hasattr(cal, 'get_range_idx')","tryCatchPattern":"try:\n    start, end = trade_range(trade_calendar=cal)\nexcept NotImplementedError:\n    # no inner calendar available; fall back to full-step execution\n    start, end = 0, total_step - 1","preventionTips":["Always pass inner_calendar when calling get_range_limit inside nested executors","Guard calls with `if cal is not None` before invoking a TradeRangeByTime"],"tags":["qlib","backtest","trade-calendar","not-implemented"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}