mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-10 22:36:55 +08:00
online serving v10
This commit is contained in:
@@ -7,19 +7,14 @@ OnlineStrategy is a set of strategy for online serving.
|
||||
|
||||
from copy import deepcopy
|
||||
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
|
||||
from qlib.workflow.online.utils import OnlineTool, OnlineToolR
|
||||
from qlib.workflow.recorder import Recorder
|
||||
from qlib.workflow.task.collect import Collector, HyperCollector, RecorderCollector
|
||||
from qlib.workflow.task.collect import Collector, RecorderCollector
|
||||
from qlib.workflow.task.gen import RollingGen, task_generator
|
||||
from qlib.workflow.task.utils import TimeAdjuster, list_recorders
|
||||
from qlib.workflow.task.utils import TimeAdjuster
|
||||
|
||||
|
||||
class OnlineStrategy:
|
||||
@@ -27,7 +22,7 @@ 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):
|
||||
def __init__(self, name_id: str, need_log=True):
|
||||
"""
|
||||
Init OnlineStrategy.
|
||||
This module **MUST** use `Trainer <../reference/api.html#Trainer>`_ to finishing model training.
|
||||
@@ -38,34 +33,22 @@ class OnlineStrategy:
|
||||
need_log (bool, optional): print log or not. Defaults to True.
|
||||
"""
|
||||
self.name_id = name_id
|
||||
self.trainer = trainer
|
||||
self.logger = get_module_logger(self.__class__.__name__)
|
||||
self.need_log = need_log
|
||||
self.tool = OnlineTool()
|
||||
self.tool = OnlineTool(need_log)
|
||||
|
||||
def prepare_signals(self, delay: bool = False):
|
||||
def prepare_tasks(self, cur_time, **kwargs) -> List[dict]:
|
||||
"""
|
||||
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`
|
||||
"""
|
||||
raise NotImplementedError(f"Please implement the `prepare_signals` method.")
|
||||
|
||||
def prepare_tasks(self, *args, **kwargs):
|
||||
"""
|
||||
After the end of a routine, check whether we need to prepare and train some new tasks.
|
||||
After the end of a routine, check whether we need to prepare and train some new tasks based on cur_time (None for latest)..
|
||||
Return the new tasks waiting for training.
|
||||
|
||||
You can find last online models by OnlineTool.online_models.
|
||||
"""
|
||||
raise NotImplementedError(f"Please implement the `prepare_tasks` method.")
|
||||
|
||||
def prepare_online_models(self, tasks, check_func=None, **kwargs):
|
||||
def prepare_online_models(self, models, cur_time=None, check_func=None, **kwargs):
|
||||
"""
|
||||
A typically implementation, but maybe you will need old models by online_tool.
|
||||
Use trainer to train a list of tasks and set the trained model to `online`.
|
||||
|
||||
NOTE: This method will first offline all models and online the online models prepared by this method. So you can find last online models by OnlineTool.online_models if you still need them.
|
||||
@@ -78,64 +61,34 @@ class OnlineStrategy:
|
||||
**kwargs: will be passed to end_train which means will be passed to customized train method.
|
||||
|
||||
"""
|
||||
if check_func is None:
|
||||
check_func = lambda x: True
|
||||
online_models = []
|
||||
if len(tasks) > 0:
|
||||
new_models = self.trainer.train(tasks, **kwargs)
|
||||
for model in new_models:
|
||||
if check_func(model):
|
||||
if check_func is not None:
|
||||
online_models = []
|
||||
for model in models:
|
||||
if check_func(model, cur_time):
|
||||
online_models.append(model)
|
||||
self.tool.reset_online_tag(online_models)
|
||||
return online_models
|
||||
models = online_models
|
||||
self.tool.reset_online_tag(models)
|
||||
return models
|
||||
|
||||
def first_train(self):
|
||||
def first_tasks(self) -> List[dict]:
|
||||
"""
|
||||
Train a series of models firstly and set some of them as online models.
|
||||
Generate a series of tasks firstly and return them.
|
||||
"""
|
||||
raise NotImplementedError(f"Please implement the `first_train` method.")
|
||||
raise NotImplementedError(f"Please implement the `first_tasks` method.")
|
||||
|
||||
def get_collector(self) -> Collector:
|
||||
"""
|
||||
Get the instance of `Collector <../advanced/task_management.html#Task Collecting>`_ to collect results of online serving.
|
||||
|
||||
Get the instance of `Collector <../advanced/task_management.html#Task Collecting>`_ to collect different results of this strategy.
|
||||
|
||||
For example:
|
||||
1) collect predictions in Recorder
|
||||
2) collect signals in .txt file
|
||||
2) collect signals in a txt file
|
||||
|
||||
Returns:
|
||||
Collector
|
||||
"""
|
||||
raise NotImplementedError(f"Please implement the `get_collector` method.")
|
||||
|
||||
def delay_prepare(self, history: list, **kwargs):
|
||||
"""
|
||||
Prepare all models and signals if there are something waiting for prepare.
|
||||
|
||||
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]].
|
||||
**kwargs: will be passed to end_train which means will be passed to customized train method.
|
||||
"""
|
||||
for begin_time, recs_list in history:
|
||||
self.trainer.end_train(recs_list, **kwargs)
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RollingAverageStrategy(OnlineStrategy):
|
||||
|
||||
@@ -148,9 +101,7 @@ class RollingAverageStrategy(OnlineStrategy):
|
||||
name_id: str,
|
||||
task_template: Union[dict, List[dict]],
|
||||
rolling_gen: RollingGen,
|
||||
trainer: Trainer = None,
|
||||
need_log=True,
|
||||
signal_exp_name="OnlineManagerSignals",
|
||||
):
|
||||
"""
|
||||
Init RollingAverageStrategy.
|
||||
@@ -161,22 +112,16 @@ class RollingAverageStrategy(OnlineStrategy):
|
||||
name_id (str): a unique name or id. Will be also the name of Experiment.
|
||||
task_template (Union[dict,List[dict]]): a list of task_template or a single template, which will be used to generate many tasks using rolling_gen.
|
||||
rolling_gen (RollingGen): an instance of RollingGen
|
||||
trainer (Trainer, optional): a instance of Trainer. Defaults to None.
|
||||
need_log (bool, optional): print log or not. Defaults to True.
|
||||
signal_exp_path (str): a specific experiment to save signals of different experiment.
|
||||
"""
|
||||
super().__init__(name_id=name_id, trainer=trainer, need_log=need_log)
|
||||
super().__init__(name_id=name_id, need_log=need_log)
|
||||
self.exp_name = self.name_id
|
||||
if not isinstance(task_template, list):
|
||||
task_template = [task_template]
|
||||
self.task_template = task_template
|
||||
self.signal_exp_name = signal_exp_name
|
||||
self.rg = rolling_gen
|
||||
self.tool = OnlineToolR(self.exp_name)
|
||||
self.tool = OnlineToolR(self.exp_name, need_log)
|
||||
self.ta = TimeAdjuster()
|
||||
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, process_list=[RollingGroup()], rec_key_func=None, rec_filter_func=None, artifacts_key=None):
|
||||
"""
|
||||
@@ -209,18 +154,17 @@ class RollingAverageStrategy(OnlineStrategy):
|
||||
|
||||
return artifacts_collector
|
||||
|
||||
def first_train(self) -> List[Recorder]:
|
||||
def first_tasks(self) -> List[dict]:
|
||||
"""
|
||||
Use rolling_gen to generate different tasks based on task_template and trained them.
|
||||
Use rolling_gen to generate different tasks based on task_template.
|
||||
|
||||
Returns:
|
||||
List[Recorder]: a list of Recorder.
|
||||
List[dict]: a list of tasks
|
||||
"""
|
||||
tasks = task_generator(
|
||||
return task_generator(
|
||||
tasks=self.task_template,
|
||||
generators=self.rg, # generate different date segment
|
||||
)
|
||||
return self.prepare_online_models(tasks)
|
||||
|
||||
def prepare_tasks(self, cur_time) -> List[dict]:
|
||||
"""
|
||||
@@ -255,57 +199,6 @@ class RollingAverageStrategy(OnlineStrategy):
|
||||
return new_tasks
|
||||
return []
|
||||
|
||||
def prepare_signals(self, delay=False, over_write=False) -> pd.DataFrame:
|
||||
"""
|
||||
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.
|
||||
|
||||
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:
|
||||
pd.DataFrame: the signals.
|
||||
"""
|
||||
if not delay:
|
||||
self.tool.update_online_pred()
|
||||
|
||||
# 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"]
|
||||
|
||||
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:]
|
||||
signals = pd.concat([old_signals, new_signals], axis=0)
|
||||
else:
|
||||
new_signals = signals
|
||||
if self.need_log:
|
||||
self.logger.info(
|
||||
f"Finished preparing new {len(new_signals)} signals to {self.signal_exp_name}/{self.exp_name}."
|
||||
)
|
||||
self.signal_rec.save_objects(**{"signals": signals})
|
||||
return signals
|
||||
|
||||
def get_signals(self) -> object:
|
||||
"""
|
||||
Get signals from the recorder(named self.exp_name) of the experiment(named self.SIGNAL_EXP)
|
||||
|
||||
Returns:
|
||||
object: signals
|
||||
"""
|
||||
signals = self.signal_rec.load_object("signals")
|
||||
return signals
|
||||
|
||||
def _list_latest(self, rec_list: List[Recorder]):
|
||||
"""
|
||||
List latest recorder form rec_list
|
||||
@@ -324,16 +217,3 @@ class RollingAverageStrategy(OnlineStrategy):
|
||||
if rec.load_object("task")["dataset"]["kwargs"]["segments"]["test"] == max_test:
|
||||
latest_rec.append(rec)
|
||||
return latest_rec, max_test
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
NOTE: This method will delete all recorder in Experiment and reset the Trainer!
|
||||
"""
|
||||
self.trainer.reset()
|
||||
# delete models
|
||||
exp = R.get_exp(experiment_name=self.exp_name)
|
||||
for rid in exp.list_recorders():
|
||||
exp.delete_recorder(rid)
|
||||
# delete signals
|
||||
for rid in list_recorders(self.signal_exp_name, lambda x: True if x.info["name"] == self.exp_name else False):
|
||||
exp.delete_recorder(rid)
|
||||
|
||||
Reference in New Issue
Block a user