1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-13 15:56:57 +08:00

docs and bug fixed

This commit is contained in:
lzh222333
2021-05-06 04:18:55 +00:00
parent 1c99fb35da
commit 84c56f13bd
17 changed files with 312 additions and 145 deletions

View File

@@ -2,8 +2,7 @@
# Licensed under the MIT License.
"""
OnlineStrategy is a set of strategy of online serving.
It is working with OnlineManager, responsing how the tasks are generated, the models are updated and signals are perpared.
OnlineStrategy is a set of strategy for online serving.
"""
from copy import deepcopy
@@ -12,6 +11,7 @@ from typing import List, Tuple, Union
import pandas as pd
from qlib.data.data import D
from qlib.log import get_module_logger
from qlib.model.ens.ensemble import AverageEnsemble, SingleKeyEnsemble
from qlib.model.ens.group import RollingGroup
from qlib.model.trainer import Trainer, TrainerR
from qlib.workflow import R
@@ -23,9 +23,14 @@ from qlib.workflow.task.utils import TimeAdjuster, list_recorders
class OnlineStrategy:
"""
OnlineStrategy is working with `Online Manager <#Online Manager>`_, responsing how the tasks are generated, the models are updated and signals are perpared.
"""
def __init__(self, name_id: str, trainer: Trainer = None, need_log=True):
"""
Init OnlineStrategy.
This module **MUST** use `Trainer <../reference/api.html#Trainer>`_ to finishing model training.
Args:
name_id (str): a unique name or id
@@ -43,6 +48,7 @@ class OnlineStrategy:
After perparing the data of last routine (a box in box-plot) which means the end of the routine, we can prepare trading signals for next routine.
NOTE: Given a set prediction, all signals before these prediction end time will be prepared well.
Args:
delay: bool
If this method was called by `delay_prepare`
@@ -52,7 +58,7 @@ class OnlineStrategy:
def prepare_tasks(self, *args, **kwargs):
"""
After the end of a routine, check whether we need to prepare and train some new tasks.
return the new tasks waiting for training.
Return the new tasks waiting for training.
You can find last online models by OnlineTool.online_models.
"""
@@ -66,10 +72,6 @@ class OnlineStrategy:
Args:
tasks (list): a list of tasks.
tag (str):
`ONLINE_TAG` for first train or additional train
`NEXT_ONLINE_TAG` for reset online model when calling `reset_online_tag`
`OFFLINE_TAG` for train but offline those models
check_func: the method to judge if a model can be online.
The parameter is the model record and return True for online.
None for online every models.
@@ -95,7 +97,8 @@ class OnlineStrategy:
def get_collector(self) -> Collector:
"""
Get the instance of collector to collect results of online serving.
Get the instance of `Collector <../advanced/task_management.html#Task Collecting>`_ to collect results of online serving.
For example:
1) collect predictions in Recorder
@@ -109,7 +112,8 @@ class OnlineStrategy:
def delay_prepare(self, history: list, **kwargs):
"""
Prepare all models and signals if there are something waiting for prepare.
NOTE: Assumption: the predictions of online models need less than next begin_time, or this method will work in a wrong way.
Assumption: the predictions of online models need less than next begin_time, or this method will work in a wrong way.
Args:
history (list): an online models list likes [begin_time:[online models]].
@@ -120,6 +124,12 @@ class OnlineStrategy:
self.tool.reset_online_tag(recs_list)
self.prepare_signals(delay=True)
def get_signals(self):
"""
Get prepared signals.
"""
raise NotImplementedError(f"Please implement the `get_signals` method.")
def reset(self):
"""
Delete all things and set them to default status. This method is convenient to explore the strategy for online simulation.
@@ -164,17 +174,20 @@ class RollingAverageStrategy(OnlineStrategy):
self.rg = rolling_gen
self.tool = OnlineToolR(self.exp_name)
self.ta = TimeAdjuster()
self.signal_rec = None # the recorder to record signals
with R.start(experiment_name=self.signal_exp_name, recorder_name=self.exp_name, resume=True):
self.signal_rec = R.get_recorder() # the recorder to record signals
self.signal_rec.save_objects(**{"signals": None})
def get_collector(self, rec_key_func=None, rec_filter_func=None):
def get_collector(self, process_list=[RollingGroup()], rec_key_func=None, rec_filter_func=None, artifacts_key=None):
"""
Get the instance of collector to collect results. The returned collector must can distinguish results in different models.
Get the instance of `Collector <../advanced/task_management.html#Task Collecting>`_ to collect results. The returned collector must can distinguish results in different models.
Assumption: the models can be distinguished based on model name and rolling test segments.
If you do not want this assumption, please implement your own method or use another rec_key_func.
Args:
rec_key_func (Callable): a function to get the key of a recorder. If None, use recorder id.
rec_filter_func (Callable, optional): filter the recorder by return True or False. Defaults to None.
artifacts_key (List[str], optional): the artifacts key you want to get. If None, get all artifacts.
"""
def rec_key(recorder):
@@ -188,18 +201,13 @@ class RollingAverageStrategy(OnlineStrategy):
artifacts_collector = RecorderCollector(
experiment=self.exp_name,
process_list=RollingGroup(),
process_list=process_list,
rec_key_func=rec_key_func,
rec_filter_func=rec_filter_func,
artifacts_key=artifacts_key,
)
signals_collector = RecorderCollector(
experiment=self.signal_exp_name,
rec_key_func=lambda rec: rec.info["name"],
rec_filter_func=lambda rec: rec.info["name"] == self.exp_name,
artifacts_path={"signals": "signals"},
)
return HyperCollector({"artifacts": artifacts_collector, "signals": signals_collector})
return artifacts_collector
def first_train(self) -> List[Recorder]:
"""
@@ -252,7 +260,11 @@ class RollingAverageStrategy(OnlineStrategy):
Average the predictions of online models and offer a trading signals every routine.
The signals will be saved to `signal` file of a recorder named self.exp_name of a experiment using the name of `SIGNAL_EXP`
Even if the latest signal already exists, the latest calculation result will be overwritten.
NOTE: Given a prediction of a certain time, all signals before this time will be prepared well.
.. note::
Given a prediction of a certain time, all signals before this time will be prepared well.
Args:
over_write (bool, optional): If True, the new signals will overwrite the file. If False, the new signals will append to the end of signals. Defaults to False.
Returns:
@@ -260,21 +272,17 @@ class RollingAverageStrategy(OnlineStrategy):
"""
if not delay:
self.tool.update_online_pred()
if self.signal_rec is None:
with R.start(experiment_name=self.signal_exp_name, recorder_name=self.exp_name, resume=True):
self.signal_rec = R.get_recorder()
pred = []
try:
old_signals = self.signal_rec.load_object("signals")
except OSError:
old_signals = None
# Get a collector to average online models predictions
online_collector = self.get_collector(
process_list=[AverageEnsemble()],
rec_filter_func=lambda x: True if self.tool.get_online_tag(x) == self.tool.ONLINE_TAG else False,
artifacts_key="pred",
)
online_results = online_collector()
signals = online_results["pred"]
for rec in self.tool.online_models():
pred.append(rec.load_object("pred.pkl"))
signals: pd.DataFrame = pd.concat(pred, axis=1).mean(axis=1).to_frame("score")
signals = signals.sort_index()
old_signals = self.get_signals()
if old_signals is not None and not over_write:
old_max = old_signals.index.get_level_values("datetime").max()
new_signals = signals.loc[old_max:]
@@ -288,18 +296,15 @@ class RollingAverageStrategy(OnlineStrategy):
self.signal_rec.save_objects(**{"signals": signals})
return signals
# def get_signals(self):
# """
# get signals from the recorder(named self.exp_name) of the experiment(named self.SIGNAL_EXP)
def get_signals(self) -> object:
"""
Get signals from the recorder(named self.exp_name) of the experiment(named self.SIGNAL_EXP)
# Returns:
# signals
# """
# if self.signal_rec is None:
# with R.start(experiment_name=self.signal_exp_name, recorder_name=self.exp_name, resume=True):
# self.signal_rec = R.get_recorder()
# signals = self.signal_rec.load_object("signals")
# return signals
Returns:
object: signals
"""
signals = self.signal_rec.load_object("signals")
return signals
def _list_latest(self, rec_list: List[Recorder]):
"""