mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-15 00:36:55 +08:00
API enhancement
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
from qlib.backtest.utils import TradeDecison
|
||||||
|
from qlib.strategy.base import BaseStrategy
|
||||||
|
from qlib.backtest.executor import BaseExecutor
|
||||||
from ..utils.resam import parse_freq
|
from ..utils.resam import parse_freq
|
||||||
|
|
||||||
|
|
||||||
def backtest_loop(start_time, end_time, trade_strategy, trade_executor):
|
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 decison execution
|
"""backtest funciton for the interaction of the outermost strategy and executor in the nested decison execution
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -17,7 +20,7 @@ def backtest_loop(start_time, end_time, trade_strategy, trade_executor):
|
|||||||
return return_value.get("report"), return_value.get("indicator")
|
return return_value.get("report"), return_value.get("indicator")
|
||||||
|
|
||||||
|
|
||||||
def collect_data_loop(start_time, end_time, trade_strategy, trade_executor, return_value: dict = None):
|
def collect_data_loop(start_time, end_time, trade_strategy: BaseStrategy, trade_executor: BaseExecutor, return_value: dict = None):
|
||||||
"""Generator for collecting the trade decision data for rl training
|
"""Generator for collecting the trade decision data for rl training
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -44,7 +47,7 @@ def collect_data_loop(start_time, end_time, trade_strategy, trade_executor, retu
|
|||||||
|
|
||||||
_execute_result = None
|
_execute_result = None
|
||||||
while not trade_executor.finished():
|
while not trade_executor.finished():
|
||||||
_trade_decision = trade_strategy.generate_trade_decision(_execute_result)
|
_trade_decision: TradeDecison = trade_strategy.generate_trade_decision(_execute_result)
|
||||||
_execute_result = yield from trade_executor.collect_data(_trade_decision)
|
_execute_result = yield from trade_executor.collect_data(_trade_decision)
|
||||||
|
|
||||||
if return_value is not None:
|
if return_value is not None:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Union
|
|||||||
|
|
||||||
from .order import Order
|
from .order import Order
|
||||||
from .exchange import Exchange
|
from .exchange import Exchange
|
||||||
from .utils import TradeCalendarManager, CommonInfrastructure, LevelInfrastructure, TradeDecison
|
from .utils import BaseTradeDecision, TradeCalendarManager, CommonInfrastructure, LevelInfrastructure, TradeDecison
|
||||||
|
|
||||||
from ..utils import init_instance_by_config
|
from ..utils import init_instance_by_config
|
||||||
from ..utils.resam import parse_freq
|
from ..utils.resam import parse_freq
|
||||||
@@ -265,7 +265,7 @@ class NestedExecutor(BaseExecutor):
|
|||||||
pass
|
pass
|
||||||
return return_value.get("execute_result")
|
return return_value.get("execute_result")
|
||||||
|
|
||||||
def collect_data(self, trade_decision, return_value=None):
|
def collect_data(self, trade_decision: BaseTradeDecision, return_value=None):
|
||||||
if self.track_data:
|
if self.track_data:
|
||||||
yield trade_decision
|
yield trade_decision
|
||||||
self._init_sub_trading(trade_decision)
|
self._init_sub_trading(trade_decision)
|
||||||
@@ -273,6 +273,14 @@ class NestedExecutor(BaseExecutor):
|
|||||||
inner_order_indicators = []
|
inner_order_indicators = []
|
||||||
_inner_execute_result = None
|
_inner_execute_result = None
|
||||||
while not self.inner_executor.finished():
|
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_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)
|
||||||
_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)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
from re import L
|
from qlib.strategy.base import BaseStrategy
|
||||||
|
from qlib.backtest.exchange import Exchange
|
||||||
|
from qlib.backtest.account import Account
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Union, List, Set
|
from typing import Tuple, Union, List, Set
|
||||||
|
|
||||||
from ..utils.resam import get_resam_calendar
|
from ..utils.resam import get_resam_calendar
|
||||||
from ..data.data import Cal
|
from ..data.data import Cal
|
||||||
@@ -138,6 +140,7 @@ class BaseInfrastructure:
|
|||||||
self.reset_infra(**infra_dict)
|
self.reset_infra(**infra_dict)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CommonInfrastructure(BaseInfrastructure):
|
class CommonInfrastructure(BaseInfrastructure):
|
||||||
def get_support_infra(self):
|
def get_support_infra(self):
|
||||||
return ["trade_account", "trade_exchange"]
|
return ["trade_account", "trade_exchange"]
|
||||||
@@ -148,8 +151,63 @@ class LevelInfrastructure(BaseInfrastructure):
|
|||||||
return ["trade_calendar"]
|
return ["trade_calendar"]
|
||||||
|
|
||||||
|
|
||||||
class TradeDecison:
|
class BaseTradeDecision:
|
||||||
"""trade decison that made by strategy"""
|
# TODO: put it into order.py; and replace it with decision.py
|
||||||
|
def __init__(self, strategy: BaseStrategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def get_decision(self) -> List[object]:
|
||||||
|
"""
|
||||||
|
get the concrete decision of the order
|
||||||
|
This will be called by the inner strategy
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[object]:
|
||||||
|
The decision result. Typically it is some orders
|
||||||
|
Example:
|
||||||
|
[]:
|
||||||
|
Decision not available
|
||||||
|
concrete_decision:
|
||||||
|
available
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(f"This type of input is not supported")
|
||||||
|
|
||||||
|
NOT_AVAIL = 0
|
||||||
|
NO_UPDATE = 1
|
||||||
|
NEW_UPDATE = 2
|
||||||
|
def update(self, trade_step: int, trade_len: int) -> "BaseTradeDecison":
|
||||||
|
"""
|
||||||
|
Be called at the **start** of each step
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None:
|
||||||
|
No update, use previous decision(or unavailable)
|
||||||
|
BaseTradeDecison:
|
||||||
|
New update, use new decision
|
||||||
|
"""
|
||||||
|
return self.strategy.update_trade_decision(self, trade_step, trade_len)
|
||||||
|
|
||||||
|
def get_range_limit(self) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
return the expected step range for limiting the dealing time of the order
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[int, int]:
|
||||||
|
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
NotImplementedError:
|
||||||
|
If the decision can't provide a unified start and end
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(f"This type of input is not supported")
|
||||||
|
|
||||||
|
|
||||||
|
class TradeDecison(BaseTradeDecision):
|
||||||
|
"""trade decision that made by strategy"""
|
||||||
|
|
||||||
def __init__(self, order_list, ori_strategy, init_enable=False):
|
def __init__(self, order_list, ori_strategy, init_enable=False):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from typing import List, Union
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
from ...utils.resam import resam_ts_data
|
from ...utils.resam import resam_ts_data
|
||||||
from ...data.data import D
|
from ...data.data import D
|
||||||
@@ -597,3 +597,41 @@ class ACStrategy(BaseStrategy):
|
|||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
return TradeDecison(order_list=order_list, ori_strategy=self)
|
return TradeDecison(order_list=order_list, ori_strategy=self)
|
||||||
|
|
||||||
|
|
||||||
|
class RandomOrderStrategy(BaseStrategy):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
time_range: Tuple = ("9:30", "15:00"), # left closed and right closed.
|
||||||
|
sample_ratio: float = 1.,
|
||||||
|
volume_ratio: float = 0.01,
|
||||||
|
market: str = "all",
|
||||||
|
*args,
|
||||||
|
**kwargs):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
time_range : Tuple
|
||||||
|
the intra day time range of the orders
|
||||||
|
the left and right is closed.
|
||||||
|
sample_ratio : float
|
||||||
|
the ratio of all orders are sampled
|
||||||
|
volume_ratio : float
|
||||||
|
the volume of the total day
|
||||||
|
raito of the total volume of a specific day
|
||||||
|
market : str
|
||||||
|
stock pool for sampling
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.time_range = time_range
|
||||||
|
self.sample_ratio = sample_ratio
|
||||||
|
self.volume_ratio = volume_ratio
|
||||||
|
self.market = market
|
||||||
|
exch: Exchange = self.common_infra.get("exchange")
|
||||||
|
self.volume = D.features(D.instruments("market"), ["Mean($volume, 10)"], start_time=exch.start_time, end_time=exch.end_time)
|
||||||
|
|
||||||
|
def generate_trade_decision(self, execute_result=None):
|
||||||
|
|
||||||
|
|
||||||
|
return super().generate_trade_decision(execute_result=execute_result)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
from typing import Union
|
from typing import List, Union
|
||||||
|
|
||||||
from ..model.base import BaseModel
|
from ..model.base import BaseModel
|
||||||
from ..data.dataset import DatasetH
|
from ..data.dataset import DatasetH
|
||||||
from ..data.dataset.utils import convert_index_format
|
from ..data.dataset.utils import convert_index_format
|
||||||
from ..rl.interpreter import ActionInterpreter, StateInterpreter
|
from ..rl.interpreter import ActionInterpreter, StateInterpreter
|
||||||
from ..utils import init_instance_by_config
|
from ..utils import init_instance_by_config
|
||||||
from ..backtest.utils import CommonInfrastructure, LevelInfrastructure, TradeDecison
|
from ..backtest.utils import BaseTradeDecision, CommonInfrastructure, LevelInfrastructure, TradeDecison
|
||||||
|
|
||||||
|
|
||||||
class BaseStrategy:
|
class BaseStrategy:
|
||||||
@@ -43,9 +43,9 @@ class BaseStrategy:
|
|||||||
if level_infra.has("trade_calendar"):
|
if level_infra.has("trade_calendar"):
|
||||||
self.trade_calendar = level_infra.get("trade_calendar")
|
self.trade_calendar = level_infra.get("trade_calendar")
|
||||||
|
|
||||||
def reset_common_infra(self, common_infra):
|
def reset_common_infra(self, common_infra: CommonInfrastructure):
|
||||||
if not hasattr(self, "common_infra"):
|
if not hasattr(self, "common_infra"):
|
||||||
self.common_infra = common_infra
|
self.common_infra: CommonInfrastructure = common_infra
|
||||||
else:
|
else:
|
||||||
self.common_infra.update(common_infra)
|
self.common_infra.update(common_infra)
|
||||||
|
|
||||||
@@ -84,17 +84,32 @@ class BaseStrategy:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError("generate_trade_decision is not implemented!")
|
raise NotImplementedError("generate_trade_decision is not implemented!")
|
||||||
|
|
||||||
def update_trade_decision(self, trade_decison: TradeDecison, trade_step, trade_len):
|
def update_trade_decision(self, trade_decison: BaseTradeDecision, trade_step: int, trade_len: int) -> BaseTradeDecision:
|
||||||
"""update trade decision in each step of inner execution, this method enable all order
|
"""update trade decision in each step of inner execution, this method enable all order
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
trade_decison : TradeDecison
|
trade_decison : TradeDecison
|
||||||
the trade decison that will be updated
|
the trade decison that will be updated
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
BaseTradeDecision:
|
||||||
"""
|
"""
|
||||||
if trade_step == 0:
|
if trade_step == 0:
|
||||||
trade_decison.enable(all_enable=True)
|
trade_decison.enable(all_enable=True)
|
||||||
|
|
||||||
|
def alter_outer_trade_decision(self, outer_trade_decision: BaseTradeDecision):
|
||||||
|
"""
|
||||||
|
A method for updating the outer_trade_decision.
|
||||||
|
The outer strategy may change its decision during updating.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
outer_trade_decision : BaseTradeDecision
|
||||||
|
the decision updated by the outer strategy
|
||||||
|
"""
|
||||||
|
self.outer_trade_decision = outer_trade_decision
|
||||||
|
|
||||||
|
|
||||||
class ModelStrategy(BaseStrategy):
|
class ModelStrategy(BaseStrategy):
|
||||||
"""Model-based trading strategy, use model to make predictions for trading"""
|
"""Model-based trading strategy, use model to make predictions for trading"""
|
||||||
|
|||||||
Reference in New Issue
Block a user