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

fix us instruments

This commit is contained in:
zhupr
2020-11-22 15:26:27 +08:00
parent 8958656222
commit bac16060ff
9 changed files with 70 additions and 26 deletions

View File

@@ -155,6 +155,7 @@ MODE_CONF = {
# cache # cache
"expression_cache": "DiskExpressionCache", "expression_cache": "DiskExpressionCache",
"dataset_cache": "DiskDatasetCache", "dataset_cache": "DiskDatasetCache",
"mount_path": None,
}, },
"client": { "client": {
# data provider config # data provider config

View File

@@ -15,6 +15,7 @@ import importlib
import traceback import traceback
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from pathlib import Path
from multiprocessing import Pool from multiprocessing import Pool
from .cache import H from .cache import H
@@ -211,6 +212,20 @@ class InstrumentProvider(abc.ABC):
return cls.LIST return cls.LIST
raise ValueError(f"Unknown instrument type {inst}") raise ValueError(f"Unknown instrument type {inst}")
def convert_instruments(self, instrument):
_instruments_map = getattr(self, "_instruments_map", None)
if _instruments_map is None:
_df_list = []
# FIXME: each process will read these files
for _path in Path(C.get_data_path()).joinpath("instruments").glob("*.txt"):
_df = pd.read_csv(_path, sep="\t", names=["inst", "start_datetime", "end_datetime", "save_inst"])
_df_list.append(_df.iloc[:, [0, -1]])
df = pd.concat(_df_list, sort=False).sort_values("save_inst")
df = df.drop_duplicates(subset=["save_inst"], keep="first").fillna(axis=1, method="ffill")
_instruments_map = df.set_index("inst").iloc[:, 0].to_dict()
setattr(self, "_instruments_map", _instruments_map)
return _instruments_map.get(instrument, instrument)
class FeatureProvider(abc.ABC): class FeatureProvider(abc.ABC):
"""Feature provider class """Feature provider class
@@ -570,19 +585,11 @@ class LocalInstrumentProvider(InstrumentProvider):
if not os.path.exists(fname): if not os.path.exists(fname):
raise ValueError("instruments not exists for market " + market) raise ValueError("instruments not exists for market " + market)
_instruments = dict() _instruments = dict()
with open(fname) as f: df = pd.read_csv(fname, sep="\t", names=["inst", "start_datetime", "end_datetime", "save_inst"])
for line in f: df["start_datetime"] = pd.to_datetime(df["start_datetime"])
inst_time = line.strip().split() df["end_datetime"] = pd.to_datetime(df["end_datetime"])
inst = inst_time[0] for row in df.itertuples(index=False):
if len(inst_time) == 3: _instruments.setdefault(row[0], []).append((row[1], row[2]))
# `day`
begin = inst_time[1]
end = inst_time[2]
elif len(inst_time) == 5:
# `1min`
begin = inst_time[1] + " " + inst_time[2]
end = inst_time[3] + " " + inst_time[4]
_instruments.setdefault(inst, []).append((pd.Timestamp(begin), pd.Timestamp(end)))
return _instruments return _instruments
def list_instruments(self, instruments, start_time=None, end_time=None, freq="day", as_list=False): def list_instruments(self, instruments, start_time=None, end_time=None, freq="day", as_list=False):
@@ -637,6 +644,7 @@ class LocalFeatureProvider(FeatureProvider):
def feature(self, instrument, field, start_index, end_index, freq): def feature(self, instrument, field, start_index, end_index, freq):
# validate # validate
field = str(field).lower()[1:] field = str(field).lower()[1:]
instrument = Inst.convert_instruments(instrument)
uri_data = self._uri_data.format(instrument.lower(), field, freq) uri_data = self._uri_data.format(instrument.lower(), field, freq)
if not os.path.exists(uri_data): if not os.path.exists(uri_data):
get_module_logger("data").warning("WARN: data not found for %s.%s" % (instrument, field)) get_module_logger("data").warning("WARN: data not found for %s.%s" % (instrument, field))

View File

@@ -613,7 +613,9 @@ def exists_qlib_data(qlib_dir):
# check instruments # check instruments
code_names = set(map(lambda x: x.name.lower(), features_dir.iterdir())) code_names = set(map(lambda x: x.name.lower(), features_dir.iterdir()))
_instrument = instruments_dir.joinpath("all.txt") _instrument = instruments_dir.joinpath("all.txt")
miss_code = set(pd.read_csv(_instrument, sep="\t", header=None).loc[:, 0].apply(str.lower)) - set(code_names) df = pd.read_csv(_instrument, sep="\t", names=["inst", "start_datetime", "end_datetime", "save_inst"])
df = df.iloc[:, [0, -1]].fillna(axis=1, method="ffill")
miss_code = set(df.iloc[:, -1].apply(str.lower)) - set(code_names)
if miss_code and any(map(lambda x: "sht" not in x, miss_code)): if miss_code and any(map(lambda x: "sht" not in x, miss_code)):
return False return False

View File

@@ -20,7 +20,6 @@ python get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn
### Downlaod US Data ### Downlaod US Data
> The US stock code contains 'PRN', and the directory cannot be created on Windows system: https://superuser.com/questions/613313/why-cant-we-make-con-prn-null-folder-in-windows
```bash ```bash
python get_data.py qlib_data --target_dir ~/.qlib/qlib_data/us_data --region us python get_data.py qlib_data --target_dir ~/.qlib/qlib_data/us_data --region us

View File

@@ -24,6 +24,7 @@ class IndexBase:
INSTRUMENTS_COLUMNS = [SYMBOL_FIELD_NAME, START_DATE_FIELD, END_DATE_FIELD] INSTRUMENTS_COLUMNS = [SYMBOL_FIELD_NAME, START_DATE_FIELD, END_DATE_FIELD]
REMOVE = "remove" REMOVE = "remove"
ADD = "add" ADD = "add"
INST_PREFIX = ""
def __init__(self, index_name: str, qlib_dir: [str, Path] = None, request_retry: int = 5, retry_sleep: int = 3): def __init__(self, index_name: str, qlib_dir: [str, Path] = None, request_retry: int = 5, retry_sleep: int = 3):
""" """
@@ -196,7 +197,11 @@ class IndexBase:
_tmp_df = pd.DataFrame([[_row.symbol, self.bench_start_date, _row.date]], columns=instruments_columns) _tmp_df = pd.DataFrame([[_row.symbol, self.bench_start_date, _row.date]], columns=instruments_columns)
new_df = new_df.append(_tmp_df, sort=False) new_df = new_df.append(_tmp_df, sort=False)
new_df.loc[:, instruments_columns].to_csv( inst_df = new_df.loc[:, instruments_columns]
_inst_prefix = self.INST_PREFIX.strip()
if _inst_prefix:
inst_df["save_inst"] = inst_df[self.SYMBOL_FIELD_NAME].apply(lambda x: f"{_inst_prefix}{x}")
inst_df.to_csv(
self.instruments_dir.joinpath(f"{self.index_name.lower()}.txt"), sep="\t", index=False, header=None self.instruments_dir.joinpath(f"{self.index_name.lower()}.txt"), sep="\t", index=False, header=None
) )
logger.info(f"parse {self.index_name.lower()} companies finished.") logger.info(f"parse {self.index_name.lower()} companies finished.")

View File

@@ -33,6 +33,10 @@ WIKI_INDEX_NAME_MAP = {
class WIKIIndex(IndexBase): class WIKIIndex(IndexBase):
# NOTE: The US stock code contains "PRN", and the directory cannot be created on Windows system, use the "_" prefix
# https://superuser.com/questions/613313/why-cant-we-make-con-prn-null-folder-in-windows
INST_PREFIX = "_"
def __init__(self, index_name: str, qlib_dir: [str, Path] = None, request_retry: int = 5, retry_sleep: int = 3): def __init__(self, index_name: str, qlib_dir: [str, Path] = None, request_retry: int = 5, retry_sleep: int = 3):
super(WIKIIndex, self).__init__( super(WIKIIndex, self).__init__(
index_name=index_name, qlib_dir=qlib_dir, request_retry=request_retry, retry_sleep=retry_sleep index_name=index_name, qlib_dir=qlib_dir, request_retry=request_retry, retry_sleep=retry_sleep

View File

@@ -184,9 +184,14 @@ def get_us_stock_symbols(qlib_data_path: [str, Path] = None) -> list:
names=["symbol", "start_date", "end_date"], names=["symbol", "start_date", "end_date"],
) )
_all_symbols += ins_df["symbol"].unique().tolist() _all_symbols += ins_df["symbol"].unique().tolist()
_US_SYMBOLS = sorted(
set(map(lambda x: x.replace(".", "-"), filter(lambda x: len(x) < 8 and not x.endswith("WS"), _all_symbols))) def _format(s_):
) s_ = s_.replace(".", "-")
s_ = s_.strip("$")
s_ = s_.strip("*")
return s_
_US_SYMBOLS = sorted(set(map(_format, filter(lambda x: len(x) < 8 and not x.endswith("WS"), _all_symbols))))
return _US_SYMBOLS return _US_SYMBOLS

View File

@@ -27,6 +27,7 @@ class DumpDataBase:
HIGH_FREQ_FORMAT = "%Y-%m-%d %H:%M:%S" HIGH_FREQ_FORMAT = "%Y-%m-%d %H:%M:%S"
INSTRUMENTS_SEP = "\t" INSTRUMENTS_SEP = "\t"
INSTRUMENTS_FILE_NAME = "all.txt" INSTRUMENTS_FILE_NAME = "all.txt"
SAVE_INST_FIELD = "save_inst"
UPDATE_MODE = "update" UPDATE_MODE = "update"
ALL_MODE = "all" ALL_MODE = "all"
@@ -44,6 +45,7 @@ class DumpDataBase:
exclude_fields: str = "", exclude_fields: str = "",
include_fields: str = "", include_fields: str = "",
limit_nums: int = None, limit_nums: int = None,
inst_prefix: str = "",
): ):
""" """
@@ -71,6 +73,9 @@ class DumpDataBase:
fields not dumped fields not dumped
limit_nums: int limit_nums: int
Use when debugging, default None Use when debugging, default None
inst_prefix: str
add a column to the instruments file and record the saved instrument name,
the US stock code contains "PRN", and the directory cannot be created on Windows system, use the "_" prefix.
""" """
csv_path = Path(csv_path).expanduser() csv_path = Path(csv_path).expanduser()
if isinstance(exclude_fields, str): if isinstance(exclude_fields, str):
@@ -79,6 +84,7 @@ class DumpDataBase:
include_fields = include_fields.split(",") include_fields = include_fields.split(",")
self._exclude_fields = tuple(filter(lambda x: len(x) > 0, map(str.strip, exclude_fields))) self._exclude_fields = tuple(filter(lambda x: len(x) > 0, map(str.strip, exclude_fields)))
self._include_fields = tuple(filter(lambda x: len(x) > 0, map(str.strip, include_fields))) self._include_fields = tuple(filter(lambda x: len(x) > 0, map(str.strip, include_fields)))
self._inst_prefix = inst_prefix.strip()
self.file_suffix = file_suffix self.file_suffix = file_suffix
self.symbol_field_name = symbol_field_name self.symbol_field_name = symbol_field_name
self.csv_files = sorted(csv_path.glob(f"*{self.file_suffix}") if csv_path.is_dir() else [csv_path]) self.csv_files = sorted(csv_path.glob(f"*{self.file_suffix}") if csv_path.is_dir() else [csv_path])
@@ -160,12 +166,19 @@ class DumpDataBase:
) )
def _read_instruments(self, instrument_path: Path) -> pd.DataFrame: def _read_instruments(self, instrument_path: Path) -> pd.DataFrame:
return pd.read_csv( df = pd.read_csv(
instrument_path, instrument_path,
sep=self.INSTRUMENTS_SEP, sep=self.INSTRUMENTS_SEP,
names=[self.symbol_field_name, self.INSTRUMENTS_START_FIELD, self.INSTRUMENTS_END_FIELD], names=[
self.symbol_field_name,
self.INSTRUMENTS_START_FIELD,
self.INSTRUMENTS_END_FIELD,
self.SAVE_INST_FIELD,
],
) )
return df
def save_calendars(self, calendars_data: list): def save_calendars(self, calendars_data: list):
self._calendars_dir.mkdir(parents=True, exist_ok=True) self._calendars_dir.mkdir(parents=True, exist_ok=True)
calendars_path = str(self._calendars_dir.joinpath(f"{self.freq}.txt").expanduser().resolve()) calendars_path = str(self._calendars_dir.joinpath(f"{self.freq}.txt").expanduser().resolve())
@@ -176,7 +189,13 @@ class DumpDataBase:
self._instruments_dir.mkdir(parents=True, exist_ok=True) self._instruments_dir.mkdir(parents=True, exist_ok=True)
instruments_path = str(self._instruments_dir.joinpath(self.INSTRUMENTS_FILE_NAME).resolve()) instruments_path = str(self._instruments_dir.joinpath(self.INSTRUMENTS_FILE_NAME).resolve())
if isinstance(instruments_data, pd.DataFrame): if isinstance(instruments_data, pd.DataFrame):
instruments_data = instruments_data.loc[:, [self.INSTRUMENTS_START_FIELD, self.INSTRUMENTS_END_FIELD]] _df_fields = [self.symbol_field_name, self.INSTRUMENTS_START_FIELD, self.INSTRUMENTS_END_FIELD]
if self._inst_prefix:
_df_fields.append(self.SAVE_INST_FIELD)
instruments_data[self.SAVE_INST_FIELD] = instruments_data[self.symbol_field_name].apply(
lambda x: f"{self._inst_prefix}{x}"
)
instruments_data = instruments_data.loc[:, _df_fields]
instruments_data.to_csv(instruments_path, header=False, sep=self.INSTRUMENTS_SEP) instruments_data.to_csv(instruments_path, header=False, sep=self.INSTRUMENTS_SEP)
else: else:
np.savetxt(instruments_path, instruments_data, fmt="%s", encoding="utf-8") np.savetxt(instruments_path, instruments_data, fmt="%s", encoding="utf-8")
@@ -234,6 +253,7 @@ class DumpDataBase:
logger.warning(f"{code} data is None or empty") logger.warning(f"{code} data is None or empty")
return return
# features save dir # features save dir
code = self._inst_prefix + code if self._inst_prefix else code
features_dir = self._features_dir.joinpath(code) features_dir = self._features_dir.joinpath(code)
features_dir.mkdir(parents=True, exist_ok=True) features_dir.mkdir(parents=True, exist_ok=True)
self._data_to_bin(df, calendar_list, features_dir) self._data_to_bin(df, calendar_list, features_dir)
@@ -262,7 +282,10 @@ class DumpDataAll(DumpDataBase):
_begin_time = self._format_datetime(_begin_time) _begin_time = self._format_datetime(_begin_time)
_end_time = self._format_datetime(_end_time) _end_time = self._format_datetime(_end_time)
symbol = self.get_symbol_from_file(file_path) symbol = self.get_symbol_from_file(file_path)
date_range_list.append(f"{self.INSTRUMENTS_SEP.join((symbol.upper(), _begin_time, _end_time))}") _inst_fields = [symbol.upper(), _begin_time, _end_time]
if self._inst_prefix:
_inst_fields.append(self._inst_prefix + symbol.upper())
date_range_list.append(f"{self.INSTRUMENTS_SEP.join(_inst_fields)}")
p_bar.update() p_bar.update()
self._kwargs["all_datetime_set"] = all_datetime self._kwargs["all_datetime_set"] = all_datetime
self._kwargs["date_range_list"] = date_range_list self._kwargs["date_range_list"] = date_range_list

View File

@@ -79,9 +79,6 @@ class GetData:
------- -------
""" """
# TODO: The US stock code contains "PRN", and the directory cannot be created on Windows system
if region.lower() == "us":
logger.warning(f"The US stock code contains 'PRN', and the directory cannot be created on Windows system")
file_name = f"{name}_{region.lower()}_{interval.lower()}_{version}.zip" file_name = f"{name}_{region.lower()}_{interval.lower()}_{version}.zip"
self._download_data(file_name.lower(), target_dir) self._download_data(file_name.lower(), target_dir)