diff --git a/qlib/backtest/executor.py b/qlib/backtest/executor.py index adea9dde0..c4807ebde 100644 --- a/qlib/backtest/executor.py +++ b/qlib/backtest/executor.py @@ -191,6 +191,7 @@ class NestedExecutor(BaseExecutor): generate_report: bool = False, verbose: bool = False, track_data: bool = False, + skip_empty_decision: bool = True, trade_exchange: Exchange = None, common_infra: CommonInfrastructure = None, **kwargs, @@ -206,6 +207,11 @@ class NestedExecutor(BaseExecutor): exchange that provides market info, used to generate report - If generate_report is None, trade_exchange will be ignored - Else If `trade_exchange` is None, self.trade_exchange will be set with common_infra + skip_empty_decision: bool + Will the executor skip the inner loop when the decision is empty. + It should be False in following cases + - The decisions may be updated by steps + - The inner executor may not follow the decisions from the outer strategy """ self.inner_executor = init_instance_by_config( inner_executor, common_infra=common_infra, accept_types=BaseExecutor @@ -214,6 +220,8 @@ class NestedExecutor(BaseExecutor): inner_strategy, common_infra=common_infra, accept_types=BaseStrategy ) + self._skip_empty_decision = skip_empty_decision + super(NestedExecutor, self).__init__( time_per_step=time_per_step, start_time=start_time, @@ -259,26 +267,32 @@ class NestedExecutor(BaseExecutor): def collect_data(self, trade_decision: BaseTradeDecision, return_value=None): if self.track_data: yield trade_decision - self._init_sub_trading(trade_decision) execute_result = [] inner_order_indicators = [] - _inner_execute_result = None - while not self.inner_executor.finished(): - # outter strategy have chance to update decision each iterator - updated_trade_decision = trade_decision.update(self.inner_executor.trade_calendar) - if updated_trade_decision is not None: - trade_decision = updated_trade_decision - # NEW UPDATE - # create a hook for inner strategy to update outter decision - self.inner_strategy.alter_outer_trade_decision(trade_decision) - _inner_trade_decision = self.inner_strategy.generate_trade_decision(_inner_execute_result) + if not (trade_decision.empty() and self._skip_empty_decision): + _inner_execute_result = None + self._init_sub_trading(trade_decision) + while not self.inner_executor.finished(): + # outter strategy have chance to update decision each iterator + updated_trade_decision = trade_decision.update(self.inner_executor.trade_calendar) + if updated_trade_decision is not None: + trade_decision = updated_trade_decision + # NEW UPDATE + # create a hook for inner strategy to update outter decision + self.inner_strategy.alter_outer_trade_decision(trade_decision) - # NOTE: Trade Calendar will step forward in the follow line - _inner_execute_result = yield from self.inner_executor.collect_data(trade_decision=_inner_trade_decision) + _inner_trade_decision = self.inner_strategy.generate_trade_decision(_inner_execute_result) - execute_result.extend(_inner_execute_result) - inner_order_indicators.append(self.inner_executor.trade_account.get_trade_indicator().get_order_indicator()) + # NOTE: Trade Calendar will step forward in the follow line + _inner_execute_result = yield from self.inner_executor.collect_data( + trade_decision=_inner_trade_decision + ) + + execute_result.extend(_inner_execute_result) + inner_order_indicators.append( + self.inner_executor.trade_account.get_trade_indicator().get_order_indicator() + ) trade_step = self.trade_calendar.get_trade_step() trade_start_time, trade_end_time = self.trade_calendar.get_step_time(trade_step) diff --git a/qlib/backtest/order.py b/qlib/backtest/order.py index 1767deb62..fb9b8edd7 100644 --- a/qlib/backtest/order.py +++ b/qlib/backtest/order.py @@ -196,7 +196,7 @@ class BaseTradeDecision: Example: []: Decision not available - concrete_decision: + [concrete_decision]: available """ raise NotImplementedError(f"This type of input is not supported") @@ -235,6 +235,9 @@ class BaseTradeDecision: """ raise NotImplementedError(f"Please implement the `func` method") + def empty(self) -> bool: + return len(self.get_decision()) == 0 + class TradeDecisionWO(BaseTradeDecision): """