mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-17 17:34:35 +08:00
support adding from date when updating pred (#703)
* support adding from date when updating pred * fix updating data error
This commit is contained in:
@@ -578,7 +578,7 @@ def get_date_range(trading_date, left_shift=0, right_shift=0, future=False):
|
|||||||
return calendar
|
return calendar
|
||||||
|
|
||||||
|
|
||||||
def get_date_by_shift(trading_date, shift, future=False, clip_shift=True, freq="day"):
|
def get_date_by_shift(trading_date, shift, future=False, clip_shift=True, freq="day", align: Optional[str] = None):
|
||||||
"""get trading date with shift bias wil cur_date
|
"""get trading date with shift bias wil cur_date
|
||||||
e.g. : shift == 1, return next trading date
|
e.g. : shift == 1, return next trading date
|
||||||
shift == -1, return previous trading date
|
shift == -1, return previous trading date
|
||||||
@@ -587,14 +587,25 @@ def get_date_by_shift(trading_date, shift, future=False, clip_shift=True, freq="
|
|||||||
current date
|
current date
|
||||||
shift : int
|
shift : int
|
||||||
clip_shift: bool
|
clip_shift: bool
|
||||||
|
align : Optional[str]
|
||||||
|
When align is None, this function will raise ValueError if `trading_date` is not a trading date
|
||||||
|
when align is "left"/"right", it will try to align to left/right nearest trading date before shifting when `trading_date` is not a trading date
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from qlib.data import D
|
from qlib.data import D
|
||||||
|
|
||||||
cal = D.calendar(future=future, freq=freq)
|
cal = D.calendar(future=future, freq=freq)
|
||||||
if pd.to_datetime(trading_date) not in list(cal):
|
trading_date = pd.to_datetime(trading_date)
|
||||||
raise ValueError("{} is not trading day!".format(str(trading_date)))
|
if align is None:
|
||||||
_index = bisect.bisect_left(cal, trading_date)
|
if trading_date not in list(cal):
|
||||||
|
raise ValueError("{} is not trading day!".format(str(trading_date)))
|
||||||
|
_index = bisect.bisect_left(cal, trading_date)
|
||||||
|
elif align == "left":
|
||||||
|
_index = bisect.bisect_right(cal, trading_date) - 1
|
||||||
|
elif align == "right":
|
||||||
|
_index = bisect.bisect_left(cal, trading_date)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"align with value `{align}` is not supported")
|
||||||
shift_index = _index + shift
|
shift_index = _index + shift
|
||||||
if shift_index < 0 or shift_index >= len(cal):
|
if shift_index < 0 or shift_index >= len(cal):
|
||||||
if clip_shift:
|
if clip_shift:
|
||||||
|
|||||||
@@ -90,14 +90,24 @@ class DSBasedUpdater(RecordUpdater, metaclass=ABCMeta):
|
|||||||
SZ300676 -0.001321
|
SZ300676 -0.001321
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, record: Recorder, to_date=None, hist_ref: int = 0, freq="day", fname="pred.pkl"):
|
def __init__(self, record: Recorder, to_date=None, from_date=None, hist_ref: int = 0, freq="day", fname="pred.pkl"):
|
||||||
"""
|
"""
|
||||||
Init PredUpdater.
|
Init PredUpdater.
|
||||||
|
|
||||||
|
Expected behavior in following cases:
|
||||||
|
- if `to_date` is greater than the max date in the calendar, the data will be updated to the latest date
|
||||||
|
- if there are data before `from_date` or after `to_date`, only the data between `from_date` and `to_date` are affected.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
record : Recorder
|
record : Recorder
|
||||||
to_date :
|
to_date :
|
||||||
update to prediction to the `to_date`
|
update to prediction to the `to_date`
|
||||||
|
if to_date is None:
|
||||||
|
data will updated to the latest date.
|
||||||
|
from_date :
|
||||||
|
the update will start from `from_date`
|
||||||
|
if from_date is None:
|
||||||
|
the updating will occur on the next tick after the latest data in historical data
|
||||||
hist_ref : int
|
hist_ref : int
|
||||||
Sometimes, the dataset will have historical depends.
|
Sometimes, the dataset will have historical depends.
|
||||||
Leave the problem to users to set the length of historical dependency
|
Leave the problem to users to set the length of historical dependency
|
||||||
@@ -127,13 +137,16 @@ class DSBasedUpdater(RecordUpdater, metaclass=ABCMeta):
|
|||||||
)
|
)
|
||||||
to_date = latest_date
|
to_date = latest_date
|
||||||
self.to_date = to_date
|
self.to_date = to_date
|
||||||
|
|
||||||
# FIXME: it will raise error when running routine with delay trainer
|
# FIXME: it will raise error when running routine with delay trainer
|
||||||
# should we use another prediction updater for delay trainer?
|
# should we use another prediction updater for delay trainer?
|
||||||
self.old_data: pd.DataFrame = record.load_object(fname)
|
self.old_data: pd.DataFrame = record.load_object(fname)
|
||||||
|
if from_date is None:
|
||||||
# dropna is for being compatible to some data with future information(e.g. label)
|
# dropna is for being compatible to some data with future information(e.g. label)
|
||||||
# The recent label data should be updated together
|
# The recent label data should be updated together
|
||||||
self.last_end = self.old_data.dropna().index.get_level_values("datetime").max()
|
self.last_end = self.old_data.dropna().index.get_level_values("datetime").max()
|
||||||
|
else:
|
||||||
|
self.last_end = get_date_by_shift(from_date, -1, align="left")
|
||||||
|
|
||||||
def prepare_data(self) -> DatasetH:
|
def prepare_data(self) -> DatasetH:
|
||||||
"""
|
"""
|
||||||
@@ -187,6 +200,15 @@ class DSBasedUpdater(RecordUpdater, metaclass=ABCMeta):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_range(data, new_data):
|
||||||
|
dates = new_data.index.get_level_values("datetime")
|
||||||
|
data = data.sort_index()
|
||||||
|
data = data.drop(data.loc[dates.min() : dates.max()].index)
|
||||||
|
cb_data = pd.concat([data, new_data], axis=0)
|
||||||
|
cb_data = cb_data[~cb_data.index.duplicated(keep="last")].sort_index()
|
||||||
|
return cb_data
|
||||||
|
|
||||||
|
|
||||||
class PredUpdater(DSBasedUpdater):
|
class PredUpdater(DSBasedUpdater):
|
||||||
"""
|
"""
|
||||||
Update the prediction in the Recorder
|
Update the prediction in the Recorder
|
||||||
@@ -196,11 +218,9 @@ class PredUpdater(DSBasedUpdater):
|
|||||||
# Load model
|
# Load model
|
||||||
model = self.rmdl.get_model()
|
model = self.rmdl.get_model()
|
||||||
new_pred: pd.Series = model.predict(dataset)
|
new_pred: pd.Series = model.predict(dataset)
|
||||||
|
data = _replace_range(self.old_data, new_pred.to_frame("score"))
|
||||||
cb_pred = pd.concat([self.old_data, new_pred.to_frame("score")], axis=0)
|
|
||||||
cb_pred = cb_pred.sort_index()
|
|
||||||
self.logger.info(f"Finish updating new {new_pred.shape[0]} predictions in {self.record.info['id']}.")
|
self.logger.info(f"Finish updating new {new_pred.shape[0]} predictions in {self.record.info['id']}.")
|
||||||
return cb_pred
|
return data
|
||||||
|
|
||||||
|
|
||||||
class LabelUpdater(DSBasedUpdater):
|
class LabelUpdater(DSBasedUpdater):
|
||||||
@@ -216,6 +236,5 @@ class LabelUpdater(DSBasedUpdater):
|
|||||||
|
|
||||||
def get_update_data(self, dataset: Dataset) -> pd.DataFrame:
|
def get_update_data(self, dataset: Dataset) -> pd.DataFrame:
|
||||||
new_label = SignalRecord.generate_label(dataset)
|
new_label = SignalRecord.generate_label(dataset)
|
||||||
cb_data = pd.concat([self.old_data, new_label], axis=0)
|
cb_data = _replace_range(self.old_data.sort_index(), new_label)
|
||||||
cb_data = cb_data[~cb_data.index.duplicated(keep="last")].sort_index()
|
|
||||||
return cb_data
|
return cb_data
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class OnlineToolR(OnlineTool):
|
|||||||
exp_name = self._get_exp_name(exp_name)
|
exp_name = self._get_exp_name(exp_name)
|
||||||
return list(list_recorders(exp_name, lambda rec: self.get_online_tag(rec) == self.ONLINE_TAG).values())
|
return list(list_recorders(exp_name, lambda rec: self.get_online_tag(rec) == self.ONLINE_TAG).values())
|
||||||
|
|
||||||
def update_online_pred(self, to_date=None, exp_name: str = None):
|
def update_online_pred(self, to_date=None, from_date=None, exp_name: str = None):
|
||||||
"""
|
"""
|
||||||
Update the predictions of online models to to_date.
|
Update the predictions of online models to to_date.
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ class OnlineToolR(OnlineTool):
|
|||||||
if issubclass(cls, TSDatasetH):
|
if issubclass(cls, TSDatasetH):
|
||||||
hist_ref = kwargs.get("step_len", TSDatasetH.DEFAULT_STEP_LEN)
|
hist_ref = kwargs.get("step_len", TSDatasetH.DEFAULT_STEP_LEN)
|
||||||
try:
|
try:
|
||||||
updater = PredUpdater(rec, to_date=to_date, hist_ref=hist_ref)
|
updater = PredUpdater(rec, to_date=to_date, from_date=from_date, hist_ref=hist_ref)
|
||||||
except LoadObjectError as e:
|
except LoadObjectError as e:
|
||||||
# skip the recorder without pred
|
# skip the recorder without pred
|
||||||
self.logger.warn(f"An exception `{str(e)}` happened when load `pred.pkl`, skip it.")
|
self.logger.warn(f"An exception `{str(e)}` happened when load `pred.pkl`, skip it.")
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ class TestRolling(TestAutoData):
|
|||||||
"""
|
"""
|
||||||
task = copy.deepcopy(CSI300_GBDT_TASK)
|
task = copy.deepcopy(CSI300_GBDT_TASK)
|
||||||
|
|
||||||
task["record"] = {
|
task["record"] = ["qlib.workflow.record_temp.SignalRecord"]
|
||||||
"class": "SignalRecord",
|
|
||||||
"module_path": "qlib.workflow.record_temp",
|
|
||||||
"kwargs": {"dataset": "<DATASET>", "model": "<MODEL>"},
|
|
||||||
}
|
|
||||||
|
|
||||||
exp_name = "online_srv_test"
|
exp_name = "online_srv_test"
|
||||||
|
|
||||||
@@ -57,6 +53,27 @@ class TestRolling(TestAutoData):
|
|||||||
|
|
||||||
online_tool.update_online_pred(to_date=latest_date + pd.Timedelta(days=10))
|
online_tool.update_online_pred(to_date=latest_date + pd.Timedelta(days=10))
|
||||||
|
|
||||||
|
good_pred = rec.load_object("pred.pkl")
|
||||||
|
|
||||||
|
mod_range = slice(latest_date - pd.Timedelta(days=20), latest_date - pd.Timedelta(days=10))
|
||||||
|
mod_range2 = slice(latest_date - pd.Timedelta(days=9), latest_date - pd.Timedelta(days=2))
|
||||||
|
mod_pred = good_pred.copy()
|
||||||
|
|
||||||
|
mod_pred.loc[mod_range] = -1
|
||||||
|
mod_pred.loc[mod_range2] = -2
|
||||||
|
|
||||||
|
rec.save_objects(**{"pred.pkl": mod_pred})
|
||||||
|
online_tool.update_online_pred(
|
||||||
|
to_date=latest_date - pd.Timedelta(days=10), from_date=latest_date - pd.Timedelta(days=20)
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_pred = rec.load_object("pred.pkl")
|
||||||
|
|
||||||
|
# this range is not fixed
|
||||||
|
self.assertTrue((updated_pred.loc[mod_range] == good_pred.loc[mod_range]).all().item())
|
||||||
|
# this range is fixed now
|
||||||
|
self.assertTrue((updated_pred.loc[mod_range2] == -2).all().item())
|
||||||
|
|
||||||
def test_update_label(self):
|
def test_update_label(self):
|
||||||
|
|
||||||
task = copy.deepcopy(CSI300_GBDT_TASK)
|
task = copy.deepcopy(CSI300_GBDT_TASK)
|
||||||
|
|||||||
Reference in New Issue
Block a user