class_docstr: Trade decisions are made by strategy and executed by executor
  
  Motivation:
      Here are several typical scenarios for `BaseTradeDecision`
  
      Case 1:
      1. Outer strategy makes a decision. The decision is not available at the start of current interval
      2. After a period of time, the decision are updated and become available
      3. The inner strategy try to get the decision and start to execute the decision according to `get_range_limit`
      Case 2:
      1. The outer strategy's decision is available at the start of the interval
      2. Same as `case 1.3`
class_docstr_tok: ["Trade","decisions","are","made","by","strategy","and","executed","by","executor","Motivation",":","Here","are","several","typical","scenarios","for","`","BaseTradeDecision","`","Case","1",":","1",".","Outer","strategy","makes","a","decision",".","The","decision","is","not","available","at","the","start","of","current","interval","2",".","After","a","period","of","time",",","the","decision","are","updated","and","become","available","3",".","The","inner","strategy","try","to","get","the","decision","and","start","to","execute","the","decision","according","to","`","get_range_limit","`","Case","2",":","1",".","The","outer","strategy","'s","decision","is","available","at","the","start","of","the","interval","2",".","Same","as","`","case","1.3","`"]
docstr: return the expected step range for limiting the decision execution time
  Both left and right are **closed**
  
  if no available trade_range, `default_value` will be returned
  
  It is only used in `NestedExecutor`
  - The outmost strategy will not follow any range limit (but it may give range_limit)
  - The inner most strategy's range_limit will be useless due to atomic executors don't have such
    features.
  
  **NOTE**:
  1) This function must be called after `self.update` in following cases(ensured by NestedExecutor):
  - user relies on the auto-clip feature of `self.update`
  
  2) This function will be called after _init_sub_trading in NestedExecutor.
  
  Parameters
  ----------
  **kwargs:
      {
          "default_value": <default_value>, # using dict is for distinguish no value provided or None provided
          "inner_calendar": <trade calendar of inner strategy>
          # because the range limit  will control the step range of inner strategy, inner calendar will be a
          # important parameter when trade_range is callable
      }
  
  Returns
  -------
  Tuple[int, int]:
  
  Raises
  ------
  NotImplementedError:
      If the following criteria meet
      1) the decision can't provide a unified start and end
      2) default_value is not provided
code: def get_range_limit(self, **kwargs: Any) -> Tuple[int, int]:
      """
      return the expected step range for limiting the decision execution time
      Both left and right are **closed**
  
      if no available trade_range, `default_value` will be returned
  
      It is only used in `NestedExecutor`
      - The outmost strategy will not follow any range limit (but it may give range_limit)
      - The inner most strategy's range_limit will be useless due to atomic executors don't have such
        features.
  
      **NOTE**:
      1) This function must be called after `self.update` in following cases(ensured by NestedExecutor):
      - user relies on the auto-clip feature of `self.update`
  
      2) This function will be called after _init_sub_trading in NestedExecutor.
  
      Parameters
      ----------
      **kwargs:
          {
              "default_value": <default_value>, # using dict is for distinguish no value provided or None provided
              "inner_calendar": <trade calendar of inner strategy>
              # because the range limit  will control the step range of inner strategy, inner calendar will be a
              # important parameter when trade_range is callable
          }
  
      Returns
      -------
      Tuple[int, int]:
  
      Raises
      ------
      NotImplementedError:
          If the following criteria meet
          1) the decision can't provide a unified start and end
          2) default_value is not provided
      """
      try:
          _start_idx, _end_idx = self._get_range_limit(**kwargs)
      except NotImplementedError as e:
          if "default_value" in kwargs:
              return kwargs["default_value"]
          else:
              # Default to get full index
              raise NotImplementedError(
                  f"The decision didn't provide an index range"
              ) from e
  
      # clip index
      if getattr(self, "total_step", None) is not None:
          # if `self.update` is called.
          # Then the _start_idx, _end_idx should be clipped
          assert self.total_step is not None
          if _start_idx < 0 or _end_idx >= self.total_step:
              logger = get_module_logger("decision")
              logger.warning(
                  f"[{_start_idx},{_end_idx}] go beyond the total_step({self.total_step}), it will be clipped.",
              )
              _start_idx, _end_idx = max(0, _start_idx), min(
                  self.total_step - 1, _end_idx
              )
      return _start_idx, _end_idx
  
imports: from __future__ import annotations
  from abc import abstractmethod
  from datetime import time
  from enum import IntEnum
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, List, Optional, Tuple, TypeVar, Union, cast
  from qlib.backtest.utils import TradeCalendarManager
  from qlib.data.data import Cal
  from qlib.log import get_module_logger
  from qlib.utils.time import concat_date_time, epsilon_change
  from dataclasses import dataclass
  import numpy
  import pandas
label_desc: Use image node_id 5 for calling the BaseTradeDecision obj's underlying member method code with example usage: obj.get_range_limit() and returns: _start_idx,  _end_idx, kwargs