diff --git a/qlib/__init__.py b/qlib/__init__.py index 107819860..3989b3692 100644 --- a/qlib/__init__.py +++ b/qlib/__init__.py @@ -152,8 +152,11 @@ def init_from_yaml_conf(conf_path, **kwargs): :param conf_path: A path to the qlib config in yml format """ - with open(conf_path) as f: - config = yaml.safe_load(f) + if conf_path is None: + config = {} + else: + with open(conf_path) as f: + config = yaml.safe_load(f) config.update(kwargs) default_conf = config.pop("default_conf", "client") init(default_conf, **config) @@ -216,7 +219,7 @@ def auto_init(**kwargs): .. code-block:: yaml conf_type: ref - qlib_cfg: '' + qlib_cfg: '' # this could be null reference no config from other files # following configs in `qlib_cfg_update` is project=specific qlib_cfg_update: exp_manager: @@ -259,8 +262,8 @@ def auto_init(**kwargs): # - There is a shared configure file and you don't want to edit it inplace. # - The shared configure may be updated later and you don't want to copy it. # - You have some customized config. - qlib_conf_path = conf["qlib_cfg"] - qlib_conf_update = conf.get("qlib_cfg_update") + qlib_conf_path = conf.get("qlib_cfg", None) + qlib_conf_update = conf.get("qlib_cfg_update", {}) init_from_yaml_conf(qlib_conf_path, **qlib_conf_update, **kwargs) logger = get_module_logger("Initialization") logger.info(f"Auto load project config: {conf_pp}") diff --git a/qlib/workflow/task/gen.py b/qlib/workflow/task/gen.py index 2fc87b1a4..2aab07b4b 100644 --- a/qlib/workflow/task/gen.py +++ b/qlib/workflow/task/gen.py @@ -112,6 +112,9 @@ def handler_mod(task: dict, rolling_gen): except KeyError: # Maybe dataset do not have handler, then do nothing. pass + except TypeError: + # May be the handler is a string. `"handler.pkl"["kwargs"]` will raise TypeError + pass class RollingGen(TaskGen): @@ -259,3 +262,56 @@ class RollingGen(TaskGen): # Update the following rolling res.extend(self.gen_following_tasks(t, test_end)) return res + + +class MultiHorizonGenBase(TaskGen): + def __init__(self, horizon: List[int] = [5], label_leak_n=2): + """ + This task generator tries to genrate tasks for different horizons based on an existing task + + Parameters + ---------- + horizon : List[int] + the possible horizons of the tasks + label_leak_n : int + How many future days it will take to get complete label after the day making prediction + For example: + - User make prediction on day `T`(after getting the close price on `T`) + - The label is the return of buying stock on `T + 1` and selling it on `T + 2` + - the `label_leak_n` will be 2 (e.g. two days of information is leaked to leverage this sample) + """ + self.horizon = list(horizon) + self.label_leak_n = label_leak_n + self.ta = TimeAdjuster() + self.test_key = "test" + + @abc.abstractmethod + def set_horizon(self, task: dict, hr: int): + """ + This method is designed to change the task **in place** + + Parameters + ---------- + task : dict + Qlib's task + hr : int + the horizon of task + """ + + def generate(self, task: dict): + res = [] + for hr in self.horizon: + + # Add horizon + t = copy.deepcopy(task) + self.set_horizon(t, hr) + + # adjust segment + segments = self.ta.align_seg(t["dataset"]["kwargs"]["segments"]) + test_start = min(t for t in segments[self.test_key] if t is not None) + for k in list(segments.keys()): + if k != self.test_key: + segments[k] = self.ta.truncate(segments[k], test_start, hr + self.label_leak_n) + t["dataset"]["kwargs"]["segments"] = segments + res.append(t) + return res