mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-12 15:26:54 +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):
|
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
|
"""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
|
Returns
|
||||||
-------
|
-------
|
||||||
report: Report
|
report: Report
|
||||||
@@ -28,8 +30,11 @@ def collect_data_loop(start_time, end_time, trade_strategy: BaseStrategy, trade_
|
|||||||
----------
|
----------
|
||||||
start_time : pd.Timestamp|str
|
start_time : pd.Timestamp|str
|
||||||
closed start time for backtest
|
closed start time for backtest
|
||||||
|
**NOTE**: This will be applied to the outmost executor's calendar.
|
||||||
end_time : pd.Timestamp|str
|
end_time : pd.Timestamp|str
|
||||||
closed end time for backtest
|
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
|
trade_strategy : BaseStrategy
|
||||||
the outermost portfolio strategy
|
the outermost portfolio strategy
|
||||||
trade_executor : BaseExecutor
|
trade_executor : BaseExecutor
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import warnings
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
from qlib.backtest.report import Indicator
|
||||||
|
|
||||||
from .order import Order, BaseTradeDecision
|
from .order import Order, BaseTradeDecision
|
||||||
from .exchange import Exchange
|
from .exchange import Exchange
|
||||||
from .utils import TradeCalendarManager, CommonInfrastructure, LevelInfrastructure
|
from .utils import TradeCalendarManager, CommonInfrastructure, LevelInfrastructure
|
||||||
@@ -174,7 +176,7 @@ class BaseExecutor:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("generate_report should be True if you want to generate report")
|
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."""
|
"""get the trade indicator instance, which has pa/pos/ffr info."""
|
||||||
return self.trade_account.indicator
|
return self.trade_account.indicator
|
||||||
|
|
||||||
@@ -279,7 +281,7 @@ class NestedExecutor(BaseExecutor):
|
|||||||
trade_decision = updated_trade_decision
|
trade_decision = updated_trade_decision
|
||||||
# NEW UPDATE
|
# NEW UPDATE
|
||||||
# create a hook for inner strategy to update outter decision
|
# 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)
|
_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)
|
_inner_execute_result = yield from self.inner_executor.collect_data(trade_decision=_inner_trade_decision)
|
||||||
|
|
||||||
execute_result.extend(_inner_execute_result)
|
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"):
|
if hasattr(self, "trade_account"):
|
||||||
trade_step = self.trade_calendar.get_trade_step()
|
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
|
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`
|
3. The inner strategy try to get the decision and start to execute the decision according to `get_range_limit`
|
||||||
Case 2:
|
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`
|
2. Same as `case 1.3`
|
||||||
"""
|
"""
|
||||||
def __init__(self, strategy: BaseStrategy):
|
def __init__(self, strategy: BaseStrategy):
|
||||||
@@ -133,14 +133,19 @@ class TradeDecisionWO(BaseTradeDecision):
|
|||||||
def get_range_limit(self) -> Tuple[int, int]:
|
def get_range_limit(self) -> Tuple[int, int]:
|
||||||
if self.idx_range is None:
|
if self.idx_range is None:
|
||||||
# Default to get full index
|
# 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
|
return self.idx_range
|
||||||
|
|
||||||
def get_decision(self) -> List[object]:
|
def get_decision(self) -> List[object]:
|
||||||
return self.order_list
|
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 ------------------------------------
|
# 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:
|
class TradeDecisionWithOrderPool:
|
||||||
"""trade decision that made by strategy"""
|
"""trade decision that made by strategy"""
|
||||||
|
|
||||||
|
|||||||
@@ -395,11 +395,9 @@ class Indicator:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def get_order_indicator(self):
|
def get_order_indicator(self):
|
||||||
return self.order_indicator
|
return self.order_indicator
|
||||||
|
|
||||||
@property
|
|
||||||
def get_trade_indicator(self):
|
def get_trade_indicator(self):
|
||||||
return self.trade_indicator
|
return self.trade_indicator
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ class TradeCalendarManager:
|
|||||||
"""Get the start_time and end_time for trading"""
|
"""Get the start_time and end_time for trading"""
|
||||||
return self.start_time, self.end_time
|
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:
|
class BaseInfrastructure:
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import pandas as pd
|
|||||||
|
|
||||||
from ...utils.resam import resam_ts_data
|
from ...utils.resam import resam_ts_data
|
||||||
from ...strategy.base import ModelStrategy
|
from ...strategy.base import ModelStrategy
|
||||||
from ...backtest.order import Order, BaseTradeDecision
|
from ...backtest.order import Order, BaseTradeDecision, TradeDecisionWO
|
||||||
|
|
||||||
from .order_generator import OrderGenWInteract
|
from .order_generator import OrderGenWInteract
|
||||||
|
|
||||||
|
|
||||||
class TopkDropoutStrategy(ModelStrategy):
|
class TopkDropoutStrategy(ModelStrategy):
|
||||||
|
# TODO:
|
||||||
|
# 1. Supporting leverage the get_range_limit result from the decision
|
||||||
|
# 2. Supporting alter_outer_trade_decision
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
model,
|
model,
|
||||||
@@ -246,10 +249,13 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
factor=factor,
|
factor=factor,
|
||||||
)
|
)
|
||||||
buy_order_list.append(buy_order)
|
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):
|
class WeightStrategyBase(ModelStrategy):
|
||||||
|
# TODO:
|
||||||
|
# 1. Supporting leverage the get_range_limit result from the decision
|
||||||
|
# 2. Supporting alter_outer_trade_decision
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
model,
|
model,
|
||||||
@@ -343,4 +349,4 @@ class WeightStrategyBase(ModelStrategy):
|
|||||||
trade_start_time=trade_start_time,
|
trade_start_time=trade_start_time,
|
||||||
trade_end_time=trade_end_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.position import Position
|
||||||
from ...backtest.exchange import Exchange
|
from ...backtest.exchange import Exchange
|
||||||
from ...backtest.order import BaseTradeDecision
|
from ...backtest.order import BaseTradeDecision, TradeDecisionWO
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import copy
|
import copy
|
||||||
@@ -127,7 +127,7 @@ class OrderGenWInteract(OrderGenerator):
|
|||||||
trade_start_time=trade_start_time,
|
trade_start_time=trade_start_time,
|
||||||
trade_end_time=trade_end_time,
|
trade_end_time=trade_end_time,
|
||||||
)
|
)
|
||||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
return TradeDecisionWO(order_list, self)
|
||||||
|
|
||||||
|
|
||||||
class OrderGenWOInteract(OrderGenerator):
|
class OrderGenWOInteract(OrderGenerator):
|
||||||
@@ -191,4 +191,4 @@ class OrderGenWOInteract(OrderGenerator):
|
|||||||
trade_start_time=trade_start_time,
|
trade_start_time=trade_start_time,
|
||||||
trade_end_time=trade_end_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
|
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):
|
class TWAPStrategy(BaseStrategy):
|
||||||
"""TWAP Strategy for trading"""
|
"""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]
|
# 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()
|
trade_step = self.trade_calendar.get_trade_step()
|
||||||
# get the total count of trading 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
|
trade_len = end_idx - start_idx + 1
|
||||||
|
|
||||||
if trade_step < start_idx:
|
if trade_step < start_idx:
|
||||||
@@ -147,6 +170,10 @@ class SBBStrategyBase(BaseStrategy):
|
|||||||
TREND_SHORT = 1
|
TREND_SHORT = 1
|
||||||
TREND_LONG = 2
|
TREND_LONG = 2
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# 1. Supporting leverage the get_range_limit result from the decision
|
||||||
|
# 2. Supporting alter_outer_trade_decision
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
outer_trade_decision: BaseTradeDecision = None,
|
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
|
# 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
|
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):
|
class SBBStrategyEMA(SBBStrategyBase):
|
||||||
"""
|
"""
|
||||||
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy with (EMA) signal.
|
(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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -430,6 +460,9 @@ class SBBStrategyEMA(SBBStrategyBase):
|
|||||||
|
|
||||||
|
|
||||||
class ACStrategy(BaseStrategy):
|
class ACStrategy(BaseStrategy):
|
||||||
|
# TODO:
|
||||||
|
# 1. Supporting leverage the get_range_limit result from the decision
|
||||||
|
# 2. Supporting alter_outer_trade_decision
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
lamb: float = 1e-6,
|
lamb: float = 1e-6,
|
||||||
@@ -601,7 +634,7 @@ class ACStrategy(BaseStrategy):
|
|||||||
factor=order.factor,
|
factor=order.factor,
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
return TradeDecision(order_list=order_list, ori_strategy=self)
|
return TradeDecisionWO(order_list, self)
|
||||||
|
|
||||||
|
|
||||||
class RandomOrderStrategy(BaseStrategy):
|
class RandomOrderStrategy(BaseStrategy):
|
||||||
@@ -655,4 +688,4 @@ class RandomOrderStrategy(BaseStrategy):
|
|||||||
end_time=step_time_end,
|
end_time=step_time_end,
|
||||||
direction=direction, # 1 for buy
|
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
|
outer_trade_decision : BaseTradeDecision
|
||||||
the decision updated by the outer strategy
|
the decision updated by the outer strategy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# default to reset the decision directly
|
# default to reset the decision directly
|
||||||
# NOTE: normally, user should do something to the strategy due to the change of outer decision
|
# 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):
|
class ModelStrategy(BaseStrategy):
|
||||||
|
|||||||
Reference in New Issue
Block a user