mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-01 18:11:18 +08:00
adapting strategies to latest interfaces.
This commit is contained in:
@@ -10,6 +10,8 @@ from tqdm.auto import tqdm
|
||||
def backtest_loop(start_time, end_time, trade_strategy: BaseStrategy, trade_executor: BaseExecutor):
|
||||
"""backtest funciton for the interaction of the outermost strategy and executor in the nested decision execution
|
||||
|
||||
please refer to the docs of `collect_data_loop`
|
||||
|
||||
Returns
|
||||
-------
|
||||
report: Report
|
||||
@@ -28,8 +30,11 @@ def collect_data_loop(start_time, end_time, trade_strategy: BaseStrategy, trade_
|
||||
----------
|
||||
start_time : pd.Timestamp|str
|
||||
closed start time for backtest
|
||||
**NOTE**: This will be applied to the outmost executor's calendar.
|
||||
end_time : pd.Timestamp|str
|
||||
closed end time for backtest
|
||||
**NOTE**: This will be applied to the outmost executor's calendar.
|
||||
E.g. Executor[day](Executor[1min]), setting `end_time == 20XX0301` will include all the minutes on 20XX0301
|
||||
trade_strategy : BaseStrategy
|
||||
the outermost portfolio strategy
|
||||
trade_executor : BaseExecutor
|
||||
|
||||
@@ -3,6 +3,8 @@ import warnings
|
||||
import pandas as pd
|
||||
from typing import Union
|
||||
|
||||
from qlib.backtest.report import Indicator
|
||||
|
||||
from .order import Order, BaseTradeDecision
|
||||
from .exchange import Exchange
|
||||
from .utils import TradeCalendarManager, CommonInfrastructure, LevelInfrastructure
|
||||
@@ -174,7 +176,7 @@ class BaseExecutor:
|
||||
else:
|
||||
raise ValueError("generate_report should be True if you want to generate report")
|
||||
|
||||
def get_trade_indicator(self):
|
||||
def get_trade_indicator(self) -> Indicator:
|
||||
"""get the trade indicator instance, which has pa/pos/ffr info."""
|
||||
return self.trade_account.indicator
|
||||
|
||||
@@ -279,7 +281,7 @@ class NestedExecutor(BaseExecutor):
|
||||
trade_decision = updated_trade_decision
|
||||
# NEW UPDATE
|
||||
# create a hook for inner strategy to update outter decision
|
||||
self.inner_strategy.alter_decision(trade_decision)
|
||||
self.inner_strategy.alter_outer_trade_decision(trade_decision)
|
||||
|
||||
_inner_trade_decision = self.inner_strategy.generate_trade_decision(_inner_execute_result)
|
||||
|
||||
@@ -287,7 +289,7 @@ class NestedExecutor(BaseExecutor):
|
||||
_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.get_trade_indicator().get_order_indicator)
|
||||
inner_order_indicators.append(self.inner_executor.get_trade_indicator().get_order_indicator())
|
||||
|
||||
if hasattr(self, "trade_account"):
|
||||
trade_step = self.trade_calendar.get_trade_step()
|
||||
|
||||
@@ -56,7 +56,7 @@ class BaseTradeDecision:
|
||||
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 strategy is available at the start of the interval
|
||||
1. The outer strategy's decision is available at the start of the interval
|
||||
2. Same as `case 1.3`
|
||||
"""
|
||||
def __init__(self, strategy: BaseStrategy):
|
||||
@@ -133,14 +133,19 @@ class TradeDecisionWO(BaseTradeDecision):
|
||||
def get_range_limit(self) -> Tuple[int, int]:
|
||||
if self.idx_range is None:
|
||||
# Default to get full index
|
||||
return 0, self.strategy.trade_calendar.get_trade_len() - 1
|
||||
raise NotImplementedError(f"The decision didn't provide an index range")
|
||||
return self.idx_range
|
||||
|
||||
def get_decision(self) -> List[object]:
|
||||
return self.order_list
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"strategy: {self.strategy}; idx_range: {self.idx_range}; order_list[{len(self.order_list)}]"
|
||||
|
||||
|
||||
# TODO: the orders below need to be discussed ------------------------------------
|
||||
# - The classes below are designed for Case 1
|
||||
# - However, Case 1 can't take `order_pool` as the an argument as the constructor function
|
||||
class TradeDecisionWithOrderPool:
|
||||
"""trade decision that made by strategy"""
|
||||
|
||||
|
||||
@@ -395,11 +395,9 @@ class Indicator:
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def get_order_indicator(self):
|
||||
return self.order_indicator
|
||||
|
||||
@property
|
||||
def get_trade_indicator(self):
|
||||
return self.trade_indicator
|
||||
|
||||
|
||||
@@ -103,6 +103,9 @@ class TradeCalendarManager:
|
||||
"""Get the start_time and end_time for trading"""
|
||||
return self.start_time, self.end_time
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.start_time}[{self.start_index}]~{self.end_time}[{self.end_index}]: [{self.trade_step}/{self.trade_len}]"
|
||||
|
||||
|
||||
class BaseInfrastructure:
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
@@ -6,12 +6,15 @@ import pandas as pd
|
||||
|
||||
from ...utils.resam import resam_ts_data
|
||||
from ...strategy.base import ModelStrategy
|
||||
from ...backtest.order import Order, BaseTradeDecision
|
||||
from ...backtest.order import Order, BaseTradeDecision, TradeDecisionWO
|
||||
|
||||
from .order_generator import OrderGenWInteract
|
||||
|
||||
|
||||
class TopkDropoutStrategy(ModelStrategy):
|
||||
# TODO:
|
||||
# 1. Supporting leverage the get_range_limit result from the decision
|
||||
# 2. Supporting alter_outer_trade_decision
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
@@ -246,10 +249,13 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
factor=factor,
|
||||
)
|
||||
buy_order_list.append(buy_order)
|
||||
return TradeDecision(order_list=sell_order_list + buy_order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(sell_order_list + buy_order_list, self)
|
||||
|
||||
|
||||
class WeightStrategyBase(ModelStrategy):
|
||||
# TODO:
|
||||
# 1. Supporting leverage the get_range_limit result from the decision
|
||||
# 2. Supporting alter_outer_trade_decision
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
@@ -343,4 +349,4 @@ class WeightStrategyBase(ModelStrategy):
|
||||
trade_start_time=trade_start_time,
|
||||
trade_end_time=trade_end_time,
|
||||
)
|
||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(order_list, self)
|
||||
|
||||
@@ -6,7 +6,7 @@ This order generator is for strategies based on WeightStrategyBase
|
||||
"""
|
||||
from ...backtest.position import Position
|
||||
from ...backtest.exchange import Exchange
|
||||
from ...backtest.order import BaseTradeDecision
|
||||
from ...backtest.order import BaseTradeDecision, TradeDecisionWO
|
||||
|
||||
import pandas as pd
|
||||
import copy
|
||||
@@ -127,7 +127,7 @@ class OrderGenWInteract(OrderGenerator):
|
||||
trade_start_time=trade_start_time,
|
||||
trade_end_time=trade_end_time,
|
||||
)
|
||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(order_list, self)
|
||||
|
||||
|
||||
class OrderGenWOInteract(OrderGenerator):
|
||||
@@ -191,4 +191,4 @@ class OrderGenWOInteract(OrderGenerator):
|
||||
trade_start_time=trade_start_time,
|
||||
trade_end_time=trade_end_time,
|
||||
)
|
||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(order_list, self)
|
||||
|
||||
@@ -12,6 +12,29 @@ from ...backtest.exchange import Exchange
|
||||
from ...backtest.utils import CommonInfrastructure, LevelInfrastructure
|
||||
|
||||
|
||||
def get_start_end_idx(strategy: BaseStrategy, outer_trade_decision: BaseTradeDecision) -> Union[int, int]:
|
||||
"""
|
||||
A helper function for getting the decision-level index range limitation for inner strategy
|
||||
- NOTE: this function is not applicable to order-level
|
||||
|
||||
Parameters
|
||||
----------
|
||||
strategy : BaseStrategy
|
||||
the inner strawtegy
|
||||
outer_trade_decision : BaseTradeDecision
|
||||
the trade decision made by outer strategy
|
||||
|
||||
Returns
|
||||
-------
|
||||
Union[int, int]:
|
||||
start index and end index
|
||||
"""
|
||||
try:
|
||||
return outer_trade_decision.get_range_limit()
|
||||
except NotImplementedError:
|
||||
return 0, strategy.trade_calendar.get_trade_len() - 1
|
||||
|
||||
|
||||
class TWAPStrategy(BaseStrategy):
|
||||
"""TWAP Strategy for trading"""
|
||||
|
||||
@@ -78,7 +101,7 @@ class TWAPStrategy(BaseStrategy):
|
||||
# get the number of trading step finished, trade_step can be [0, 1, 2, ..., trade_len - 1]
|
||||
trade_step = self.trade_calendar.get_trade_step()
|
||||
# get the total count of trading step
|
||||
start_idx, end_idx = self.outer_trade_decision.get_range_limit()
|
||||
start_idx, end_idx = get_start_end_idx(self, self.outer_trade_decision)
|
||||
trade_len = end_idx - start_idx + 1
|
||||
|
||||
if trade_step < start_idx:
|
||||
@@ -147,6 +170,10 @@ class SBBStrategyBase(BaseStrategy):
|
||||
TREND_SHORT = 1
|
||||
TREND_LONG = 2
|
||||
|
||||
# TODO:
|
||||
# 1. Supporting leverage the get_range_limit result from the decision
|
||||
# 2. Supporting alter_outer_trade_decision
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
outer_trade_decision: BaseTradeDecision = None,
|
||||
@@ -345,13 +372,16 @@ class SBBStrategyBase(BaseStrategy):
|
||||
# in the first one of two adjacent bars, store the trend for the second one to use
|
||||
self.trade_trend[order.stock_id] = _pred_trend
|
||||
|
||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(order_list, self)
|
||||
|
||||
|
||||
class SBBStrategyEMA(SBBStrategyBase):
|
||||
"""
|
||||
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy with (EMA) signal.
|
||||
"""
|
||||
# TODO:
|
||||
# 1. Supporting leverage the get_range_limit result from the decision
|
||||
# 2. Supporting alter_outer_trade_decision
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -430,6 +460,9 @@ class SBBStrategyEMA(SBBStrategyBase):
|
||||
|
||||
|
||||
class ACStrategy(BaseStrategy):
|
||||
# TODO:
|
||||
# 1. Supporting leverage the get_range_limit result from the decision
|
||||
# 2. Supporting alter_outer_trade_decision
|
||||
def __init__(
|
||||
self,
|
||||
lamb: float = 1e-6,
|
||||
@@ -601,7 +634,7 @@ class ACStrategy(BaseStrategy):
|
||||
factor=order.factor,
|
||||
)
|
||||
order_list.append(_order)
|
||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
||||
return TradeDecisionWO(order_list, self)
|
||||
|
||||
|
||||
class RandomOrderStrategy(BaseStrategy):
|
||||
@@ -655,4 +688,4 @@ class RandomOrderStrategy(BaseStrategy):
|
||||
end_time=step_time_end,
|
||||
direction=direction, # 1 for buy
|
||||
))
|
||||
return TradeDecisionWO(order_list, self)
|
||||
return TradeDecisionWO(order_list, self, self.index_range)
|
||||
|
||||
@@ -113,10 +113,9 @@ class BaseStrategy:
|
||||
outer_trade_decision : BaseTradeDecision
|
||||
the decision updated by the outer strategy
|
||||
"""
|
||||
|
||||
# default to reset the decision directly
|
||||
# NOTE: normally, user should do something to the strategy due to the change of outer decision
|
||||
self.outer_trade_decision = outer_trade_decision
|
||||
raise NotImplementedError(f"Please implement the `alter_outer_trade_decision` method")
|
||||
|
||||
|
||||
class ModelStrategy(BaseStrategy):
|
||||
|
||||
Reference in New Issue
Block a user