Skip to content

Commit c5aa471

Browse files
committed
Add hook for supporting RL strategy
1 parent d087054 commit c5aa471

File tree

1 file changed

+31
-1
lines changed

1 file changed

+31
-1
lines changed

qlib/backtest/executor.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,27 @@ def _collect_data(self, trade_decision: BaseTradeDecision, level: int = 0):
395395
if not self._align_range_limit or start_idx <= sub_cal.get_trade_step() <= end_idx:
396396
# if force align the range limit, skip the steps outside the decision range limit
397397

398-
_inner_trade_decision: BaseTradeDecision = self.inner_strategy.generate_trade_decision(
398+
res = self.inner_strategy.generate_trade_decision(
399399
_inner_execute_result
400400
)
401+
402+
# NOTE: !!!!!
403+
# the two lines below is for a special case in RL
404+
# To solve the confliction below
405+
# - Normally, user will create a strategy and embed it into Qlib's executor and simulator interaction loop
406+
# For a _nested qlib example_, (Qlib Strategy) <=> (Qlib Executor[(inner Qlib Strategy) <=> (inner Qlib Executor)])
407+
# - However, RL-based framework has it's own script to run the loop
408+
# For an _RL learning example_, (RL Policy) <=> (RL Env[(inner Qlib Executor)])
409+
# To make it possible to run _nested qlib example_ and _RL learning example_ together, the solution below is proposed
410+
# - The entry script follow the example of _RL learning example_ to be compatible with all kinds of RL Framework
411+
# - Each step of (RL Env) will make (inner Qlib Executor) one step forward
412+
# - (inner Qlib Strategy) is a proxy strategy, it will give the program control right to (RL Env) by `yield from` and wait for the action from the policy
413+
# So the two lines below is the implementation of yielding control rights
414+
if isinstance(res, GeneratorType):
415+
res = yield from res
416+
417+
_inner_trade_decision: BaseTradeDecision = res
418+
401419
trade_decision.mod_inner_decision(_inner_trade_decision) # propagate part of decision information
402420

403421
# NOTE sub_cal.get_step_time() must be called before collect_data in case of step shifting
@@ -407,6 +425,7 @@ def _collect_data(self, trade_decision: BaseTradeDecision, level: int = 0):
407425
_inner_execute_result = yield from self.inner_executor.collect_data(
408426
trade_decision=_inner_trade_decision, level=level + 1
409427
)
428+
self.post_inner_exe_step(_inner_execute_result)
410429
execute_result.extend(_inner_execute_result)
411430

412431
inner_order_indicators.append(
@@ -418,6 +437,17 @@ def _collect_data(self, trade_decision: BaseTradeDecision, level: int = 0):
418437

419438
return execute_result, {"inner_order_indicators": inner_order_indicators, "decision_list": decision_list}
420439

440+
def post_inner_exe_step(self, inner_exe_res):
441+
"""
442+
A hook for doing sth after each step of inner strategy
443+
444+
Parameters
445+
----------
446+
inner_exe_res :
447+
the execution result of inner task
448+
"""
449+
pass
450+
421451
def get_all_executors(self):
422452
"""get all executors, including self and inner_executor.get_all_executors()"""
423453
return [self, *self.inner_executor.get_all_executors()]

0 commit comments

Comments
 (0)