1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-11 06:46:56 +08:00

DDG-DA paper code (#743)

* Merge data selection to main

* Update trainer for reweighter

* Typos fixed.

* update data selection interface

* successfully run exp after refactor some interface

* data selection share handler &  trainer

* fix meta model time series bug

* fix online workflow set_uri bug

* fix set_uri bug

* updawte ds docs and delay trainer bug

* docs

* resume reweighter

* add reweighting result

* fix qlib model import

* make recorder more friendly

* fix experiment workflow bug

* commit for merging master incase of conflictions

* Successful run DDG-DA with a single command

* remove unused code

* asdd more docs

* Update README.md

* Update & fix some bugs.

* Update configuration & remove debug functions

* Update README.md

* Modfify horizon from code rather than yaml

* Update performance in README.md

* fix part comments

* Remove unfinished TCTS.

* Fix some details.

* Update meta docs

* Update README.md of the benchmarks_dynamic

* Update README.md files

* Add README.md to the rolling_benchmark baseline.

* Refine the docs and link

* Rename README.md in benchmarks_dynamic.

* Remove comments.

* auto download data

Co-authored-by: wendili-cs <wendili.academic@qq.com>
Co-authored-by: demon143 <785696300@qq.com>
This commit is contained in:
you-n-g
2022-01-10 16:52:37 +08:00
committed by GitHub
parent 184ce34a34
commit cf35562e84
52 changed files with 2441 additions and 456 deletions

View File

@@ -14,8 +14,9 @@ from ..data.dataset import DatasetH
from ..data.dataset.handler import DataHandlerLP
from ..backtest import backtest as normal_backtest
from ..log import get_module_logger
from ..utils import flatten_dict, class_casting
from ..utils import fill_placeholder, flatten_dict, class_casting, get_date_by_shift
from ..utils.time import Freq
from ..utils.data import deepcopy_basic_type
from ..contrib.eva.alpha import calc_ic, calc_long_short_return, calc_long_short_prec
@@ -175,9 +176,10 @@ class SignalRecord(RecordTemp):
del params["data_key"]
# The backend handler should be DataHandler
raw_label = dataset.prepare(**params)
except AttributeError:
except AttributeError as e:
# The data handler is initialize with `drop_raw=True`...
# So raw_label is not available
logger.warning(f"Exception: {e}")
raw_label = None
return raw_label
@@ -203,6 +205,35 @@ class SignalRecord(RecordTemp):
return ["pred.pkl", "label.pkl"]
class ACRecordTemp(RecordTemp):
"""Automatically checking record template"""
def __init__(self, recorder, skip_existing=False):
self.skip_existing = skip_existing
super().__init__(recorder=recorder)
def generate(self, *args, **kwargs):
"""automatically checking the files and then run the concrete generating task"""
if self.skip_existing:
try:
self.check(include_self=True, parents=False)
except FileNotFoundError:
pass # continue to generating metrics
else:
logger.info("The results has previously generated, Generation skipped.")
return
try:
self.check()
except FileNotFoundError:
logger.warning("The dependent data does not exists. Generation skipped.")
return
return self._generate(*args, **kwargs)
def _generate(self, *args, **kwargs):
raise NotImplementedError(f"Please implement the `_generate` method")
class HFSignalRecord(SignalRecord):
"""
This is the Signal Analysis Record class that generates the analysis results such as IC and IR. This class inherits the ``RecordTemp`` class.
@@ -250,7 +281,7 @@ class HFSignalRecord(SignalRecord):
return ["ic.pkl", "ric.pkl", "long_pre.pkl", "short_pre.pkl", "long_short_r.pkl", "long_avg_r.pkl"]
class SigAnaRecord(RecordTemp):
class SigAnaRecord(ACRecordTemp):
"""
This is the Signal Analysis Record class that generates the analysis results such as IC and IR. This class inherits the ``RecordTemp`` class.
"""
@@ -259,39 +290,23 @@ class SigAnaRecord(RecordTemp):
depend_cls = SignalRecord
def __init__(self, recorder, ana_long_short=False, ann_scaler=252, label_col=0, skip_existing=False):
super().__init__(recorder=recorder)
super().__init__(recorder=recorder, skip_existing=skip_existing)
self.ana_long_short = ana_long_short
self.ann_scaler = ann_scaler
self.label_col = label_col
self.skip_existing = skip_existing
def generate(self, label: Optional[pd.DataFrame] = None, **kwargs):
def _generate(self, label: Optional[pd.DataFrame] = None, **kwargs):
"""
Parameters
----------
label : Optional[pd.DataFrame]
Label should be a dataframe.
"""
if self.skip_existing:
try:
self.check(include_self=True, parents=False)
except FileNotFoundError:
pass # continue to generating metrics
else:
logger.info("The results has previously generated, Generation skipped.")
return
try:
self.check()
except FileNotFoundError:
logger.warning("The dependent data does not exists. Generation skipped.")
return
pred = self.load("pred.pkl")
if label is None:
label = self.load("label.pkl")
if label is None or not isinstance(label, pd.DataFrame) or label.empty:
logger.warn(f"Empty label.")
logger.warning(f"Empty label.")
return
ic, ric = calc_ic(pred.iloc[:, 0], label.iloc[:, self.label_col])
metrics = {
@@ -328,7 +343,7 @@ class SigAnaRecord(RecordTemp):
return paths
class PortAnaRecord(RecordTemp):
class PortAnaRecord(ACRecordTemp):
"""
This is the Portfolio Analysis Record class that generates the analysis results such as those of backtest. This class inherits the ``RecordTemp`` class.
@@ -339,14 +354,35 @@ class PortAnaRecord(RecordTemp):
"""
artifact_path = "portfolio_analysis"
depend_cls = SignalRecord
def __init__(
self,
recorder,
config,
config: dict = { # Default config for daily trading
"strategy": {
"class": "TopkDropoutStrategy",
"module_path": "qlib.contrib.strategy",
"kwargs": {"signal": "<PRED>", "topk": 50, "n_drop": 5},
},
"backtest": {
"start_time": None,
"end_time": None,
"account": 100000000,
"benchmark": "SH000300",
"exchange_kwargs": {
"limit_threshold": 0.095,
"deal_price": "close",
"open_cost": 0.0005,
"close_cost": 0.0015,
"min_cost": 5,
},
},
},
risk_analysis_freq: Union[List, str] = None,
indicator_analysis_freq: Union[List, str] = None,
indicator_analysis_method=None,
skip_existing=False,
**kwargs,
):
"""
@@ -363,7 +399,12 @@ class PortAnaRecord(RecordTemp):
indicator_analysis_method : str, optional, default by None
the candidated values include 'mean', 'amount_weighted', 'value_weighted'
"""
super().__init__(recorder=recorder, **kwargs)
super().__init__(recorder=recorder, skip_existing=skip_existing, **kwargs)
# We only deepcopy_basic_type because
# - We don't want to affect the config outside.
# - We don't want to deepcopy complex object to avoid overhead
config = deepcopy_basic_type(config)
self.strategy_config = config["strategy"]
_default_executor_config = {
@@ -405,7 +446,21 @@ class PortAnaRecord(RecordTemp):
ret_freq.extend(self._get_report_freq(executor_config["kwargs"]["inner_executor"]))
return ret_freq
def generate(self, **kwargs):
def _generate(self, **kwargs):
pred = self.load("pred.pkl")
# replace the "<PRED>" with prediction saved before
placehorder_value = {"<PRED>": pred}
for k in "executor_config", "strategy_config":
setattr(self, k, fill_placeholder(getattr(self, k), placehorder_value))
# if the backtesting time range is not set, it will automatically extract time range from the prediction file
dt_values = pred.index.get_level_values("datetime")
if self.backtest_config["start_time"] is None:
self.backtest_config["start_time"] = dt_values.min()
if self.backtest_config["end_time"] is None:
self.backtest_config["end_time"] = get_date_by_shift(dt_values.max(), 1)
# custom strategy and get backtest
portfolio_metric_dict, indicator_dict = normal_backtest(
executor=self.executor_config, strategy=self.strategy_config, **self.backtest_config