1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-12 23:36:54 +08:00

Add DataPathManager to QlibConfig && modify inst_processors to supports list only

This commit is contained in:
zhupr
2021-09-02 11:45:37 +08:00
committed by you-n-g
parent 707399a245
commit e84cc23589
10 changed files with 148 additions and 179 deletions

View File

@@ -4,9 +4,8 @@ import pandas as pd
from qlib.data.inst_processor import InstProcessor from qlib.data.inst_processor import InstProcessor
class ResampleProcessor(InstProcessor): class Resample1minProcessor(InstProcessor):
def __init__(self, freq: str, hour: int, minute: int): def __init__(self, hour: int, minute: int):
self.freq = freq
self.hour = hour self.hour = hour
self.minute = minute self.minute = minute

View File

@@ -3,6 +3,8 @@ qlib_init:
day: "~/.qlib/qlib_data/cn_data" day: "~/.qlib/qlib_data/cn_data"
1min: "~/.qlib/qlib_data/cn_data_1min" 1min: "~/.qlib/qlib_data/cn_data_1min"
region: cn region: cn
dataset_cache: null
maxtasksperchild: 1
market: &market csi300 market: &market csi300
benchmark: &benchmark SH000300 benchmark: &benchmark SH000300
data_handler_config: &data_handler_config data_handler_config: &data_handler_config
@@ -16,10 +18,9 @@ data_handler_config: &data_handler_config
label: day label: day
feature: 1min feature: 1min
# with label as reference # with label as reference
sample_benchmark: label inst_processor:
sample_config:
feature: feature:
- class: ResampleProcessor - class: Resample1minProcessor
module_path: features_sample.py module_path: features_sample.py
kwargs: kwargs:
freq: 1d freq: 1d

View File

@@ -33,41 +33,30 @@ def init(default_conf="client", **kwargs):
H.clear() H.clear()
C.set(default_conf, **kwargs) C.set(default_conf, **kwargs)
provider_uri_map = C.provider_uri if isinstance(C.provider_uri, dict) else {None: C.provider_uri} # mount nfs
if C.mount_path is not None: for _freq, provider_uri in C.provider_uri.items():
# mount nfs mount_path = C["mount_path"][_freq]
for _freq, provider_uri in provider_uri_map.items(): # check path if server/local
mount_path = C.mount_path if _freq is None else C["mount_path"][_freq] uri_type = C.dpm.get_uri_type(provider_uri)
# check path if server/local if uri_type == C.LOCAL_URI:
uri_type = C.get_uri_type(provider_uri) if not Path(provider_uri).exists():
if uri_type == C.LOCAL_URI: if C["auto_mount"]:
if not Path(provider_uri).exists(): logger.error(
if C["auto_mount"]: f"Invalid provider uri: {provider_uri}, please check if a valid provider uri has been set. This path does not exist."
logger.error( )
f"Invalid provider uri: {provider_uri}, please check if a valid provider uri has been set. This path does not exist."
)
else:
logger.warning(f"auto_path is False, please make sure {mount_path} is mounted")
elif uri_type == C.NFS_URI:
mount_path = _mount_nfs_uri(provider_uri, mount_path, C["auto_mount"])
if _freq is None:
C["mount_path"] = mount_path
else: else:
C["mount_path"][_freq] = mount_path logger.warning(f"auto_path is False, please make sure {mount_path} is mounted")
else: elif uri_type == C.NFS_URI:
raise NotImplementedError(f"This type of URI is not supported") _mount_nfs_uri(provider_uri, mount_path, C["auto_mount"])
else:
raise NotImplementedError(f"This type of URI is not supported")
C.register() C.register()
if "flask_server" in C: if "flask_server" in C:
logger.info(f"flask_server={C['flask_server']}, flask_port={C['flask_port']}") logger.info(f"flask_server={C['flask_server']}, flask_port={C['flask_port']}")
logger.info("qlib successfully initialized based on %s settings." % default_conf) logger.info("qlib successfully initialized based on %s settings." % default_conf)
data_path = ( logger.info(f"data_path={C.dpm.provider_uri}")
C.get_data_path()
if isinstance(C["provider_uri"], str)
else {_freq: C.get_data_path(_freq) for _freq in C["provider_uri"].keys()}
)
logger.info(f"data_path={data_path}")
def _mount_nfs_uri(provider_uri, mount_path, auto_mount: bool = False): def _mount_nfs_uri(provider_uri, mount_path, auto_mount: bool = False):
@@ -102,8 +91,6 @@ def _mount_nfs_uri(provider_uri, mount_path, auto_mount: bool = False):
else: else:
raise OSError(f"unknown error: {result}") raise OSError(f"unknown error: {result}")
# config mount path
mount_path += ":\\"
else: else:
# system: linux/Unix/Mac # system: linux/Unix/Mac
# check mount # check mount
@@ -156,7 +143,6 @@ def _mount_nfs_uri(provider_uri, mount_path, auto_mount: bool = False):
LOG.info("Mount finished") LOG.info("Mount finished")
else: else:
LOG.warning(f"{_remote_uri} on {_mount_path} is already mounted") LOG.warning(f"{_remote_uri} on {_mount_path} is already mounted")
return mount_path
def init_from_yaml_conf(conf_path, **kwargs): def init_from_yaml_conf(conf_path, **kwargs):

View File

@@ -15,6 +15,7 @@ import os
import re import re
import copy import copy
import logging import logging
import platform
import multiprocessing import multiprocessing
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union
@@ -238,11 +239,43 @@ class QlibConfig(Config):
# URI_TYPE # URI_TYPE
LOCAL_URI = "local" LOCAL_URI = "local"
NFS_URI = "nfs" NFS_URI = "nfs"
DEFAULT_FREQ = "__DEFAULT_FREQ"
def __init__(self, default_conf): def __init__(self, default_conf):
super().__init__(default_conf) super().__init__(default_conf)
self._registered = False self._registered = False
class DataPathManager:
def __init__(self, provider_uri: Union[str, Path, dict], mount_path: Union[str, Path, dict]):
self.provider_uri = provider_uri
self.mount_path = mount_path
@staticmethod
def get_uri_type(uri: Union[str, Path]):
uri = uri if isinstance(uri, str) else str(uri.expanduser().resolve())
is_win = re.match("^[a-zA-Z]:.*", uri) is not None # such as 'C:\\data', 'D:'
# such as 'host:/data/' (User may define short hostname by themselves or use localhost)
is_nfs_or_win = re.match("^[^/]+:.+", uri) is not None
if is_nfs_or_win and not is_win:
return QlibConfig.NFS_URI
else:
return QlibConfig.LOCAL_URI
def get_data_path(self, freq: str = None) -> Path:
if freq is None or freq not in self.provider_uri:
freq = QlibConfig.DEFAULT_FREQ
_provider_uri = self.provider_uri[freq]
if self.get_uri_type(_provider_uri) == QlibConfig.LOCAL_URI:
return Path(_provider_uri)
elif self.get_uri_type(_provider_uri) == QlibConfig.NFS_URI:
if "win" in platform.system().lower():
# windows, mount_path is the drive
return Path(f"{self.mount_path[freq]}:\\")
return Path(self.mount_path[freq])
else:
raise NotImplementedError(f"This type of uri is not supported")
def set_mode(self, mode): def set_mode(self, mode):
# raise KeyError # raise KeyError
self.update(MODE_CONF[mode]) self.update(MODE_CONF[mode])
@@ -252,59 +285,39 @@ class QlibConfig(Config):
# raise KeyError # raise KeyError
self.update(_default_region_config[region]) self.update(_default_region_config[region])
@property
def dpm(self):
return self.DataPathManager(self["provider_uri"], self["mount_path"])
def resolve_path(self): def resolve_path(self):
# resolve path # resolve path
_mount_path = self["mount_path"] _mount_path = self["mount_path"]
_provider_uri = self["provider_uri"] _provider_uri = self["provider_uri"]
if isinstance(_provider_uri, dict): if _provider_uri is None:
if _mount_path is not None: raise ValueError("provider_uri cannot be None")
# check provider_uri and mount_path if not isinstance(_provider_uri, dict):
assert isinstance(_mount_path, dict), f"type(provider_uri) != type(mount_path); {_mount_path}" _provider_uri = {self.DEFAULT_FREQ: _provider_uri}
_miss_freq = set(_provider_uri.keys()) - set(_mount_path.keys()) if not isinstance(_mount_path, dict):
assert len(_miss_freq) == 0, f"mount_path is missing freq: {_miss_freq}" _mount_path = {_freq: _mount_path for _freq in _provider_uri.keys()}
self["mount_path"] = {_freq: str(Path(_path).expanduser().resolve()) for _freq, _path in _mount_path}
for _freq, _uri in _provider_uri.items():
if self.get_uri_type(_uri) == QlibConfig.LOCAL_URI:
self["provider_uri"][_freq] = str(Path(_uri).expanduser().resolve())
elif isinstance(_provider_uri, str):
if _mount_path is not None:
self["mount_path"] = str(Path(_mount_path).expanduser().resolve())
if self.get_uri_type(_provider_uri) == QlibConfig.LOCAL_URI: # check provider_uri and mount_path
self["provider_uri"] = str(Path(_provider_uri).expanduser().resolve()) _miss_freq = set(_provider_uri.keys()) - set(_mount_path.keys())
else: assert len(_miss_freq) == 0, f"mount_path is missing freq: {_miss_freq}"
raise TypeError(
f"The types supported by provider_uri are [str, dict], " f"not {type(_provider_uri)}: {_provider_uri}" # resolve
for _freq, _uri in _provider_uri.items():
# provider_uri
if self.DataPathManager.get_uri_type(_uri) == QlibConfig.LOCAL_URI:
_provider_uri[_freq] = str(Path(_uri).expanduser().resolve())
# mount_path
_mount_path[_freq] = (
_mount_path[_freq]
if _mount_path[_freq] is None
else str(Path(_mount_path[_freq]).expanduser().resolve())
) )
@staticmethod self["provider_uri"] = _provider_uri
def get_uri_type(uri: Union[str, Path]): self["mount_path"] = _mount_path
uri = uri if isinstance(uri, str) else str(uri.expanduser().resolve())
is_win = re.match("^[a-zA-Z]:.*", uri) is not None # such as 'C:\\data', 'D:'
# such as 'host:/data/' (User may define short hostname by themselves or use localhost)
is_nfs_or_win = re.match("^[^/]+:.+", uri) is not None
if is_nfs_or_win and not is_win:
return QlibConfig.NFS_URI
else:
return QlibConfig.LOCAL_URI
def get_data_path(self, freq: str = None) -> Path:
if freq is None and not isinstance(self["provider_uri"], str):
raise ValueError(f"type(provider_uri) == dict, freq cannot be None; provider_uri: {self['provider_uri']}")
_provider_uri = self["provider_uri"] if isinstance(self["provider_uri"], str) else self["provider_uri"][freq]
_mount_path = (
self["mount_path"]
if self["mount_path"] is None or isinstance(self["mount_path"], str)
else self["mount_path"][freq]
)
if self.get_uri_type(_provider_uri) == QlibConfig.LOCAL_URI:
return Path(_provider_uri)
elif self.get_uri_type(_provider_uri) == QlibConfig.NFS_URI:
return Path(_mount_path)
else:
raise NotImplementedError(f"This type of uri is not supported")
def set(self, default_conf="client", **kwargs): def set(self, default_conf="client", **kwargs):
from .utils import set_log_with_config, get_module_logger, can_use_cache from .utils import set_log_with_config, get_module_logger, can_use_cache

View File

@@ -35,7 +35,7 @@ def get_benchmark_weight(
""" """
if not path: if not path:
path = Path(C.get_data_path(freq)).expanduser() / "raw" / "AIndexMembers" / "weights.csv" path = Path(C.dpm.get_data_path(freq)).expanduser() / "raw" / "AIndexMembers" / "weights.csv"
# TODO: the storage of weights should be implemented in a more elegent way # TODO: the storage of weights should be implemented in a more elegent way
# TODO: The benchmark is not consistant with the filename in instruments. # TODO: The benchmark is not consistant with the filename in instruments.
bench_weight_df = pd.read_csv(path, usecols=["code", "date", "index", "weight"]) bench_weight_df = pd.read_csv(path, usecols=["code", "date", "index", "weight"])

View File

@@ -58,8 +58,7 @@ class Alpha360(DataHandlerLP):
fit_start_time=None, fit_start_time=None,
fit_end_time=None, fit_end_time=None,
filter_pipe=None, filter_pipe=None,
sample_config=None, inst_processor=None,
sample_benchmark=None,
**kwargs, **kwargs,
): ):
infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time) infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time)
@@ -74,8 +73,7 @@ class Alpha360(DataHandlerLP):
}, },
"filter_pipe": filter_pipe, "filter_pipe": filter_pipe,
"freq": freq, "freq": freq,
"sample_config": sample_config, "inst_processor": inst_processor,
"sample_benchmark": sample_benchmark,
}, },
} }
@@ -148,8 +146,7 @@ class Alpha158(DataHandlerLP):
fit_end_time=None, fit_end_time=None,
process_type=DataHandlerLP.PTYPE_A, process_type=DataHandlerLP.PTYPE_A,
filter_pipe=None, filter_pipe=None,
sample_config=None, inst_processor=None,
sample_benchmark=None,
**kwargs, **kwargs,
): ):
infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time) infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time)
@@ -164,8 +161,7 @@ class Alpha158(DataHandlerLP):
}, },
"filter_pipe": filter_pipe, "filter_pipe": filter_pipe,
"freq": freq, "freq": freq,
"sample_config": sample_config, "inst_processor": inst_processor,
"sample_benchmark": sample_benchmark,
}, },
} }
super().__init__( super().__init__(

View File

@@ -319,7 +319,7 @@ class BaseProviderCache:
@staticmethod @staticmethod
def get_cache_dir(dir_name: str, freq: str = None) -> Path: def get_cache_dir(dir_name: str, freq: str = None) -> Path:
cache_dir = Path(C.get_data_path(freq)).joinpath(dir_name) cache_dir = Path(C.dpm.get_data_path(freq)).joinpath(dir_name)
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir return cache_dir
@@ -356,7 +356,7 @@ class ExpressionCache(BaseProviderCache):
""" """
raise NotImplementedError("Implement this method if you want to use expression cache") raise NotImplementedError("Implement this method if you want to use expression cache")
def update(self, cache_uri: Union[str, Path]): def update(self, cache_uri: Union[str, Path], freq: str = "day"):
"""Update expression cache to latest calendar. """Update expression cache to latest calendar.
Overide this method to define how to update expression cache corresponding to users' own cache mechanism. Overide this method to define how to update expression cache corresponding to users' own cache mechanism.
@@ -365,6 +365,7 @@ class ExpressionCache(BaseProviderCache):
---------- ----------
cache_uri : str or Path cache_uri : str or Path
the complete uri of expression cache file (include dir path). the complete uri of expression cache file (include dir path).
freq : str
Returns Returns
------- -------
@@ -385,7 +386,7 @@ class DatasetCache(BaseProviderCache):
HDF_KEY = "df" HDF_KEY = "df"
def dataset( def dataset(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=[]
): ):
"""Get feature dataset. """Get feature dataset.
@@ -419,7 +420,7 @@ class DatasetCache(BaseProviderCache):
raise NotImplementedError("Implement this function to match your own cache mechanism") raise NotImplementedError("Implement this function to match your own cache mechanism")
def _dataset( def _dataset(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=[]
): ):
"""Get feature dataset using cache. """Get feature dataset using cache.
@@ -428,7 +429,7 @@ class DatasetCache(BaseProviderCache):
raise NotImplementedError("Implement this method if you want to use dataset feature cache") raise NotImplementedError("Implement this method if you want to use dataset feature cache")
def _dataset_uri( def _dataset_uri(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=[]
): ):
"""Get a uri of feature dataset using cache. """Get a uri of feature dataset using cache.
specially: specially:
@@ -441,7 +442,7 @@ class DatasetCache(BaseProviderCache):
"Implement this method if you want to use dataset feature cache as a cache file for client" "Implement this method if you want to use dataset feature cache as a cache file for client"
) )
def update(self, cache_uri: Union[str, Path]): def update(self, cache_uri: Union[str, Path], freq: str = "day"):
"""Update dataset cache to latest calendar. """Update dataset cache to latest calendar.
Overide this method to define how to update dataset cache corresponding to users' own cache mechanism. Overide this method to define how to update dataset cache corresponding to users' own cache mechanism.
@@ -450,6 +451,7 @@ class DatasetCache(BaseProviderCache):
---------- ----------
cache_uri : str or Path cache_uri : str or Path
the complete uri of dataset cache file (include dir path). the complete uri of dataset cache file (include dir path).
freq : str
Returns Returns
------- -------
@@ -542,7 +544,7 @@ class DiskExpressionCache(ExpressionCache):
series = self.provider.expression(instrument, field, _calendar[0], _calendar[-1], freq) series = self.provider.expression(instrument, field, _calendar[0], _calendar[-1], freq)
if not series.empty: if not series.empty:
# This expresion is empty, we don't generate any cache for it. # This expresion is empty, we don't generate any cache for it.
with CacheUtils.writer_lock(self.r, f"{str(C.get_data_path(freq))}:expression-{_cache_uri}"): with CacheUtils.writer_lock(self.r, f"{str(C.dpm.get_data_path(freq))}:expression-{_cache_uri}"):
self.gen_expression_cache( self.gen_expression_cache(
expression_data=series, expression_data=series,
cache_path=cache_path, cache_path=cache_path,
@@ -578,18 +580,16 @@ class DiskExpressionCache(ExpressionCache):
r = np.hstack([df.index[0], expression_data]).astype("<f") r = np.hstack([df.index[0], expression_data]).astype("<f")
r.tofile(str(cache_path)) r.tofile(str(cache_path))
def update(self, sid, cache_uri): def update(self, sid, cache_uri, freq: str = "day"):
# FIXME: when updating the cache, the type of C.provider_uri must be str cp_cache_uri = self.get_cache_dir(freq).joinpath(sid).joinpath(cache_uri)
assert isinstance(C.provider_uri, str), "when updating the cache, the type of C.provider_uri must be str"
cp_cache_uri = self.get_cache_dir().joinpath(sid).joinpath(cache_uri)
meta_path = cp_cache_uri.with_suffix(".meta") meta_path = cp_cache_uri.with_suffix(".meta")
if not self.check_cache_exists(cp_cache_uri, suffix_list=[".meta"]): if not self.check_cache_exists(cp_cache_uri, suffix_list=[".meta"]):
self.logger.info(f"The cache {cp_cache_uri} has corrupted. It will be removed") self.logger.info(f"The cache {cp_cache_uri} has corrupted. It will be removed")
self.clear_cache(cp_cache_uri) self.clear_cache(cp_cache_uri)
return 2 return 2
with CacheUtils.writer_lock(self.r, f"{str(C.get_data_path())}:expression-{cache_uri}"): with CacheUtils.writer_lock(self.r, f"{str(C.dpm.get_data_path())}:expression-{cache_uri}"):
with meta_path.open("rb") as f: with meta_path.open("rb") as f:
d = pickle.load(f) d = pickle.load(f)
instrument = d["info"]["instrument"] instrument = d["info"]["instrument"]
@@ -651,7 +651,7 @@ class DiskDatasetCache(DatasetCache):
self.remote = kwargs.get("remote", False) self.remote = kwargs.get("remote", False)
@staticmethod @staticmethod
def _uri(instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=None, **kwargs): def _uri(instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=[], **kwargs):
return hash_args(*DatasetCache.normalize_uri_args(instruments, fields, freq), disk_cache, inst_processors) return hash_args(*DatasetCache.normalize_uri_args(instruments, fields, freq), disk_cache, inst_processors)
def get_cache_dir(self, freq: str = None) -> Path: def get_cache_dir(self, freq: str = None) -> Path:
@@ -692,7 +692,7 @@ class DiskDatasetCache(DatasetCache):
return df return df
def _dataset( def _dataset(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=[]
): ):
if disk_cache == 0: if disk_cache == 0:
@@ -701,7 +701,7 @@ class DiskDatasetCache(DatasetCache):
instruments, fields, start_time, end_time, freq, inst_processors=inst_processors instruments, fields, start_time, end_time, freq, inst_processors=inst_processors
) )
# FIXME: The cache after resample, when read again and intercepted with end_time, results in incomplete data date # FIXME: The cache after resample, when read again and intercepted with end_time, results in incomplete data date
if not inst_processors: if inst_processors:
raise ValueError( raise ValueError(
f"{self.__class__.__name__} does not support inst_processor. " f"{self.__class__.__name__} does not support inst_processor. "
f"Please use `D.features(disk_cache=0)` or `qlib.init(dataset_cache=None)`" f"Please use `D.features(disk_cache=0)` or `qlib.init(dataset_cache=None)`"
@@ -724,7 +724,7 @@ class DiskDatasetCache(DatasetCache):
if self.check_cache_exists(cache_path): if self.check_cache_exists(cache_path):
if disk_cache == 1: if disk_cache == 1:
# use cache # use cache
with CacheUtils.reader_lock(self.r, f"{str(C.get_data_path(freq))}:dataset-{_cache_uri}"): with CacheUtils.reader_lock(self.r, f"{str(C.dpm.get_data_path(freq))}:dataset-{_cache_uri}"):
CacheUtils.visit(cache_path) CacheUtils.visit(cache_path)
features = self.read_data_from_cache(cache_path, start_time, end_time, fields) features = self.read_data_from_cache(cache_path, start_time, end_time, fields)
elif disk_cache == 2: elif disk_cache == 2:
@@ -734,7 +734,7 @@ class DiskDatasetCache(DatasetCache):
if gen_flag: if gen_flag:
# cache unavailable, generate the cache # cache unavailable, generate the cache
with CacheUtils.writer_lock(self.r, f"{str(C.get_data_path(freq))}:dataset-{_cache_uri}"): with CacheUtils.writer_lock(self.r, f"{str(C.dpm.get_data_path(freq))}:dataset-{_cache_uri}"):
features = self.gen_dataset_cache( features = self.gen_dataset_cache(
cache_path=cache_path, cache_path=cache_path,
instruments=instruments, instruments=instruments,
@@ -747,7 +747,7 @@ class DiskDatasetCache(DatasetCache):
return features return features
def _dataset_uri( def _dataset_uri(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=[]
): ):
if disk_cache == 0: if disk_cache == 0:
# In this case, server only checks the expression cache. # In this case, server only checks the expression cache.
@@ -775,12 +775,12 @@ class DiskDatasetCache(DatasetCache):
if self.check_cache_exists(cache_path): if self.check_cache_exists(cache_path):
self.logger.debug(f"The cache dataset has already existed {cache_path}. Return the uri directly") self.logger.debug(f"The cache dataset has already existed {cache_path}. Return the uri directly")
with CacheUtils.reader_lock(self.r, f"{str(C.get_data_path(freq))}:dataset-{_cache_uri}"): with CacheUtils.reader_lock(self.r, f"{str(C.dpm.get_data_path(freq))}:dataset-{_cache_uri}"):
CacheUtils.visit(cache_path) CacheUtils.visit(cache_path)
return _cache_uri return _cache_uri
else: else:
# cache unavailable, generate the cache # cache unavailable, generate the cache
with CacheUtils.writer_lock(self.r, f"{str(C.get_data_path(freq))}:dataset-{_cache_uri}"): with CacheUtils.writer_lock(self.r, f"{str(C.dpm.get_data_path(freq))}:dataset-{_cache_uri}"):
self.gen_dataset_cache( self.gen_dataset_cache(
cache_path=cache_path, cache_path=cache_path,
instruments=instruments, instruments=instruments,
@@ -854,7 +854,7 @@ class DiskDatasetCache(DatasetCache):
index_data += start_index index_data += start_index
return index_data return index_data
def gen_dataset_cache(self, cache_path: Union[str, Path], instruments, fields, freq, inst_processors=None): def gen_dataset_cache(self, cache_path: Union[str, Path], instruments, fields, freq, inst_processors=[]):
"""gen_dataset_cache """gen_dataset_cache
.. note:: This function does not consider the cache read write lock. Please .. note:: This function does not consider the cache read write lock. Please
@@ -949,10 +949,8 @@ class DiskDatasetCache(DatasetCache):
# the fields of the cached features are converted to the original fields # the fields of the cached features are converted to the original fields
return features.swaplevel("datetime", "instrument") return features.swaplevel("datetime", "instrument")
def update(self, cache_uri): def update(self, cache_uri, freq: str = "day"):
# FIXME: when updating the cache, the type of C.provider_uri must be str cp_cache_uri = self.get_cache_dir(freq).joinpath(cache_uri)
assert isinstance(C.provider_uri, str), "when updating the cache, the type of C.provider_uri must be str"
cp_cache_uri = self.get_cache_dir().joinpath(cache_uri)
meta_path = cp_cache_uri.with_suffix(".meta") meta_path = cp_cache_uri.with_suffix(".meta")
if not self.check_cache_exists(cp_cache_uri): if not self.check_cache_exists(cp_cache_uri):
self.logger.info(f"The cache {cp_cache_uri} has corrupted. It will be removed") self.logger.info(f"The cache {cp_cache_uri} has corrupted. It will be removed")
@@ -960,7 +958,7 @@ class DiskDatasetCache(DatasetCache):
return 2 return 2
im = DiskDatasetCache.IndexManager(cp_cache_uri) im = DiskDatasetCache.IndexManager(cp_cache_uri)
with CacheUtils.writer_lock(self.r, f"{str(C.get_data_path())}:dataset-{cache_uri}"): with CacheUtils.writer_lock(self.r, f"{str(C.dpm.get_data_path())}:dataset-{cache_uri}"):
with meta_path.open("rb") as f: with meta_path.open("rb") as f:
d = pickle.load(f) d = pickle.load(f)
instruments = d["info"]["instruments"] instruments = d["info"]["instruments"]
@@ -1073,14 +1071,14 @@ class SimpleDatasetCache(DatasetCache):
except KeyError as e: except KeyError as e:
self.logger.error("Assign a local_cache_path in config if you want to use this cache mechanism") self.logger.error("Assign a local_cache_path in config if you want to use this cache mechanism")
def _uri(self, instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=None, **kwargs): def _uri(self, instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=[], **kwargs):
instruments, fields, freq = self.normalize_uri_args(instruments, fields, freq) instruments, fields, freq = self.normalize_uri_args(instruments, fields, freq)
return hash_args( return hash_args(
instruments, fields, start_time, end_time, freq, disk_cache, str(self.local_cache_path), inst_processors instruments, fields, start_time, end_time, freq, disk_cache, str(self.local_cache_path), inst_processors
) )
def _dataset( def _dataset(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=1, inst_processors=[]
): ):
if disk_cache == 0: if disk_cache == 0:
# In this case, data_set cache is configured but will not be used. # In this case, data_set cache is configured but will not be used.
@@ -1115,11 +1113,11 @@ class SimpleDatasetCache(DatasetCache):
class DatasetURICache(DatasetCache): class DatasetURICache(DatasetCache):
"""Prepared cache mechanism for server.""" """Prepared cache mechanism for server."""
def _uri(self, instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=None, **kwargs): def _uri(self, instruments, fields, start_time, end_time, freq, disk_cache=1, inst_processors=[], **kwargs):
return hash_args(*self.normalize_uri_args(instruments, fields, freq), disk_cache, inst_processors) return hash_args(*self.normalize_uri_args(instruments, fields, freq), disk_cache, inst_processors)
def dataset( def dataset(
self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=None self, instruments, fields, start_time=None, end_time=None, freq="day", disk_cache=0, inst_processors=[]
): ):
if "local" in C.dataset_provider.lower(): if "local" in C.dataset_provider.lower():
@@ -1151,7 +1149,7 @@ class DatasetURICache(DatasetCache):
instruments, fields, None, None, freq, disk_cache=disk_cache, inst_processors=inst_processors instruments, fields, None, None, freq, disk_cache=disk_cache, inst_processors=inst_processors
) )
value, expire = MemCacheExpire.get_cache(H["f"], feature_uri) value, expire = MemCacheExpire.get_cache(H["f"], feature_uri)
mnt_feature_uri = C.get_data_path(freq).joinpath(C.dataset_cache_dir_name).joinpath(feature_uri) mnt_feature_uri = C.dpm.get_data_path(freq).joinpath(C.dataset_cache_dir_name).joinpath(feature_uri)
if value is None or expire or not mnt_feature_uri.exists(): if value is None or expire or not mnt_feature_uri.exists():
df, uri = self.provider.dataset( df, uri = self.provider.dataset(
instruments, instruments,

View File

@@ -61,7 +61,7 @@ class ProviderBackendMixin:
provider_uri_map = backend_kwargs.setdefault("provider_uri_map", {}) provider_uri_map = backend_kwargs.setdefault("provider_uri_map", {})
freq = kwargs.get("freq", "day") freq = kwargs.get("freq", "day")
if freq not in provider_uri_map: if freq not in provider_uri_map:
provider_uri_map[freq] = C.get_data_path(freq) provider_uri_map[freq] = C.dpm.get_data_path(freq)
backend_kwargs["provider_uri"] = provider_uri_map[freq] backend_kwargs["provider_uri"] = provider_uri_map[freq]
backend.setdefault("kwargs", {}).update(**kwargs) backend.setdefault("kwargs", {}).update(**kwargs)
return init_instance_by_config(backend) return init_instance_by_config(backend)
@@ -353,7 +353,7 @@ class DatasetProvider(abc.ABC):
""" """
@abc.abstractmethod @abc.abstractmethod
def dataset(self, instruments, fields, start_time=None, end_time=None, freq="day", inst_processors=None): def dataset(self, instruments, fields, start_time=None, end_time=None, freq="day", inst_processors=[]):
"""Get dataset data. """Get dataset data.
Parameters Parameters
@@ -386,7 +386,7 @@ class DatasetProvider(abc.ABC):
end_time=None, end_time=None,
freq="day", freq="day",
disk_cache=1, disk_cache=1,
inst_processors=None, inst_processors=[],
**kwargs, **kwargs,
): ):
"""Get task uri, used when generating rabbitmq task in qlib_server """Get task uri, used when generating rabbitmq task in qlib_server
@@ -449,7 +449,7 @@ class DatasetProvider(abc.ABC):
return [ExpressionD.get_expression_instance(f) for f in fields] return [ExpressionD.get_expression_instance(f) for f in fields]
@staticmethod @staticmethod
def dataset_processor(instruments_d, column_names, start_time, end_time, freq, inst_processors=None): def dataset_processor(instruments_d, column_names, start_time, end_time, freq, inst_processors=[]):
""" """
Load and process the data, return the data set. Load and process the data, return the data set.
- default using multi-kernel method. - default using multi-kernel method.
@@ -513,7 +513,7 @@ class DatasetProvider(abc.ABC):
@staticmethod @staticmethod
def expression_calculator( def expression_calculator(
inst, start_time, end_time, freq, column_names, spans=None, g_config=None, inst_processors=None inst, start_time, end_time, freq, column_names, spans=None, g_config=None, inst_processors=[]
): ):
""" """
Calculate the expressions for one instrument, return a df result. Calculate the expressions for one instrument, return a df result.
@@ -544,10 +544,10 @@ class DatasetProvider(abc.ABC):
mask |= (data.index >= begin) & (data.index <= end) mask |= (data.index >= begin) & (data.index <= end)
data = data[mask] data = data[mask]
if inst_processors is not None: for _processor in inst_processors:
for _processor in inst_processors if isinstance(inst_processors, (list, tuple, set)) else [inst_processors]: if _processor:
_processor = init_instance_by_config(_processor, accept_types=InstProcessor) _processor_obj = init_instance_by_config(_processor, accept_types=InstProcessor)
data = _processor(data) data = _processor_obj(data)
return data return data
@@ -719,7 +719,7 @@ class LocalDatasetProvider(DatasetProvider):
start_time=None, start_time=None,
end_time=None, end_time=None,
freq="day", freq="day",
inst_processors=None, inst_processors=[],
): ):
instruments_d = self.get_instruments_d(instruments, freq) instruments_d = self.get_instruments_d(instruments, freq)
column_names = self.get_column_names(fields) column_names = self.get_column_names(fields)
@@ -874,7 +874,7 @@ class ClientDatasetProvider(DatasetProvider):
freq="day", freq="day",
disk_cache=0, disk_cache=0,
return_uri=False, return_uri=False,
inst_processors=None, inst_processors=[],
): ):
if Inst.get_inst_type(instruments) == Inst.DICT: if Inst.get_inst_type(instruments) == Inst.DICT:
get_module_logger("data").warning( get_module_logger("data").warning(
@@ -914,7 +914,7 @@ class ClientDatasetProvider(DatasetProvider):
start_time = cal[0] start_time = cal[0]
end_time = cal[-1] end_time = cal[-1]
data = self.dataset_processor(instruments_d, column_names, start_time, end_time, freq) data = self.dataset_processor(instruments_d, column_names, start_time, end_time, freq, inst_processors)
if return_uri: if return_uri:
return data, feature_uri return data, feature_uri
else: else:
@@ -953,7 +953,7 @@ class ClientDatasetProvider(DatasetProvider):
get_module_logger("data").debug("get result") get_module_logger("data").debug("get result")
try: try:
# pre-mound nfs, used for demo # pre-mound nfs, used for demo
mnt_feature_uri = C.get_data_path(freq).joinpath(C.dataset_cache_dir_name, feature_uri) mnt_feature_uri = C.dpm.get_data_path(freq).joinpath(C.dataset_cache_dir_name, feature_uri)
df = DiskDatasetCache.read_data_from_cache(mnt_feature_uri, start_time, end_time, fields) df = DiskDatasetCache.read_data_from_cache(mnt_feature_uri, start_time, end_time, fields)
get_module_logger("data").debug("finish slicing data") get_module_logger("data").debug("finish slicing data")
if return_uri: if return_uri:
@@ -991,7 +991,7 @@ class BaseProvider:
end_time=None, end_time=None,
freq="day", freq="day",
disk_cache=None, disk_cache=None,
inst_processors=None, inst_processors=[],
): ):
""" """
Parameters: Parameters:

View File

@@ -156,8 +156,7 @@ class QlibDataLoader(DLWParser):
filter_pipe: List = None, filter_pipe: List = None,
swap_level: bool = True, swap_level: bool = True,
freq: Union[str, dict] = "day", freq: Union[str, dict] = "day",
sample_benchmark: str = None, inst_processor: dict = None,
sample_config: dict = None,
): ):
""" """
Parameters Parameters
@@ -168,6 +167,11 @@ class QlibDataLoader(DLWParser):
Filter pipe for the instruments Filter pipe for the instruments
swap_level : swap_level :
Whether to swap level of MultiIndex Whether to swap level of MultiIndex
freq: dict or str
If type(config) == dict and type(freq) == str, load config data using freq.
If type(config) == dict and type(freq) == dict, load config[<group_name>] data using freq[<group_name>]
inst_processor: dict
If inst_processor is not None and type(config) == dict; load config[<group_name>] data using inst_processor[<group_name>]
""" """
if filter_pipe is not None: if filter_pipe is not None:
assert isinstance(filter_pipe, list), "The type of `filter_pipe` must be list." assert isinstance(filter_pipe, list), "The type of `filter_pipe` must be list."
@@ -181,9 +185,9 @@ class QlibDataLoader(DLWParser):
self.freq = freq self.freq = freq
# sample # sample
self.sample_config = sample_config if sample_config is not None else {} self.inst_processor = inst_processor if inst_processor is not None else {}
self.sample_benchmark = sample_benchmark assert isinstance(self.inst_processor, dict), f"inst_processor(={self.inst_processor}) must be dict"
self.can_sample = False
super().__init__(config) super().__init__(config)
if self.is_group: if self.is_group:
@@ -192,12 +196,9 @@ class QlibDataLoader(DLWParser):
for _gp in config.keys(): for _gp in config.keys():
if _gp not in freq: if _gp not in freq:
raise ValueError(f"freq(={freq}) missing group(={_gp})") raise ValueError(f"freq(={freq}) missing group(={_gp})")
assert sample_config, f"freq(={self.freq}), sample_config(={sample_config}) cannot be None/empty"
assert isinstance(sample_config, dict), f"sample_config(={sample_config}) must be dict"
assert ( assert (
self.sample_benchmark and self.sample_benchmark in self.fields self.inst_processor
), f"sample_benchmark not to specification" ), f"freq(={self.freq}), inst_processor(={self.inst_processor}) cannot be None/empty"
self.can_sample = True
def load_group_df( def load_group_df(
self, self,
@@ -216,33 +217,15 @@ class QlibDataLoader(DLWParser):
elif self.filter_pipe is not None: elif self.filter_pipe is not None:
warnings.warn("`filter_pipe` is not None, but it will not be used with `instruments` as list") warnings.warn("`filter_pipe` is not None, but it will not be used with `instruments` as list")
freq = self.freq[gp_name] if self.can_sample else self.freq freq = self.freq[gp_name] if isinstance(self.freq, dict) else self.freq
inst_processor = self.sample_config.get(gp_name, None) if self.can_sample else None df = D.features(
df = D.features(instruments, exprs, start_time, end_time, freq=freq, inst_processors=inst_processor) instruments, exprs, start_time, end_time, freq=freq, inst_processors=self.inst_processor.get(gp_name, [])
)
df.columns = names df.columns = names
if self.swap_level: if self.swap_level:
df = df.swaplevel().sort_index() # NOTE: if swaplevel, return <datetime, instrument> df = df.swaplevel().sort_index() # NOTE: if swaplevel, return <datetime, instrument>
return df return df
def load(self, instruments=None, start_time=None, end_time=None) -> pd.DataFrame:
if self.is_group:
group = {
grp: self.load_group_df(instruments, exprs, names, start_time, end_time, grp)
for grp, (exprs, names) in self.fields.items()
}
if self.can_sample:
# reindex: alignment to index of sample_benchmark
for grp, _df in group.items():
if grp == self.sample_benchmark:
continue
else:
group[grp] = _df.reindex(group[self.sample_benchmark].index)
df = pd.concat(group, axis=1)
else:
exprs, names = self.fields
df = self.load_group_df(instruments, exprs, names, start_time, end_time)
return df
class StaticDataLoader(DataLoader): class StaticDataLoader(DataLoader):
""" """

View File

@@ -1,4 +1,5 @@
import abc import abc
import json
import pandas as pd import pandas as pd
@@ -18,13 +19,5 @@ class InstProcessor:
""" """
pass pass
def __str__(self):
class ResampleProcessor(InstProcessor): return f"{self.__class__.__name__}:{json.dumps(self.__dict__, sort_keys=True, default=str)}"
"""resample data"""
def __init__(self, freq: str, func: str, *args, **kwargs):
self.freq = freq
self.func = func
def __call__(self, df: pd.DataFrame, *args, **kwargs):
return getattr(df.resample(self.freq, level="datetime"), self.func)().dropna(how="all")