1
0
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:
Young
2021-06-28 07:30:34 +00:00
committed by you-n-g
parent c907d8deb4
commit 72c9593aa7
9 changed files with 70 additions and 19 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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"""

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):