1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 17:34:35 +08:00

Modify data.storage

This commit is contained in:
zhupr
2021-04-13 10:47:01 +08:00
parent 70fc58104b
commit 317357b50d
4 changed files with 141 additions and 91 deletions

View File

@@ -6,6 +6,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
import os import os
import re
import abc import abc
import time import time
import queue import queue
@@ -27,12 +28,35 @@ from .cache import DiskDatasetCache, DiskExpressionCache
from ..utils import Wrapper, init_instance_by_config, register_wrapper, get_module_by_module_path from ..utils import Wrapper, init_instance_by_config, register_wrapper, get_module_by_module_path
class CalendarProvider(abc.ABC): class ProviderBackendMixin:
def get_default_backend(self):
backend = {}
provider_name = re.findall("[A-Z][^A-Z]*", self.__class__.__name__)[-2] # type: str
# set default storage class
backend.setdefault("class", f"File{provider_name}Storage")
# set default storage module
backend.setdefault("module_path", "qlib.data.storage.file_storage")
# set default storage kwargs
backend_kwargs = backend.setdefault("kwargs", {}) # type: dict
backend_kwargs.setdefault("uri", os.path.join(C.get_data_path(), f"{provider_name.lower()}s"))
return backend
@property
def backend_obj(self):
return init_instance_by_config(self.backend)
class CalendarProvider(abc.ABC, ProviderBackendMixin):
"""Calendar provider base class """Calendar provider base class
Provide calendar data. Provide calendar data.
""" """
def __init__(self, *args, **kwargs):
self.backend = kwargs.get("backend", {})
if not self.backend:
self.backend = self.get_default_backend()
@abc.abstractmethod @abc.abstractmethod
def calendar(self, start_time=None, end_time=None, freq="day", future=False): def calendar(self, start_time=None, end_time=None, freq="day", future=False):
"""Get calendar of certain market in given time range. """Get calendar of certain market in given time range.
@@ -127,12 +151,17 @@ class CalendarProvider(abc.ABC):
return hash_args(start_time, end_time, freq, future) return hash_args(start_time, end_time, freq, future)
class InstrumentProvider(abc.ABC): class InstrumentProvider(abc.ABC, ProviderBackendMixin):
"""Instrument provider base class """Instrument provider base class
Provide instrument data. Provide instrument data.
""" """
def __init__(self, *args, **kwargs):
self.backend = kwargs.get("backend", {})
if not self.backend:
self.backend = self.get_default_backend()
@staticmethod @staticmethod
def instruments(market="all", filter_pipe=None): def instruments(market="all", filter_pipe=None):
"""Get the general config dictionary for a base market adding several dynamic filters. """Get the general config dictionary for a base market adding several dynamic filters.
@@ -215,12 +244,17 @@ class InstrumentProvider(abc.ABC):
raise ValueError(f"Unknown instrument type {inst}") raise ValueError(f"Unknown instrument type {inst}")
class FeatureProvider(abc.ABC): class FeatureProvider(abc.ABC, ProviderBackendMixin):
"""Feature provider class """Feature provider class
Provide feature data. Provide feature data.
""" """
def __init__(self, *args, **kwargs):
self.backend = kwargs.get("backend", {})
if not self.backend:
self.backend = self.get_default_backend()
@abc.abstractmethod @abc.abstractmethod
def feature(self, instrument, field, start_time, end_time, freq): def feature(self, instrument, field, start_time, end_time, freq):
"""Get feature data. """Get feature data.
@@ -497,6 +531,7 @@ class LocalCalendarProvider(CalendarProvider):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(LocalCalendarProvider, self).__init__(**kwargs)
self.remote = kwargs.get("remote", False) self.remote = kwargs.get("remote", False)
@property @property
@@ -517,18 +552,8 @@ class LocalCalendarProvider(CalendarProvider):
list list
list of timestamps list of timestamps
""" """
if future: self.backend.setdefault("kwargs", {}).update(freq=freq, future=future)
fname = self._uri_cal.format(freq + "_future") return [pd.Timestamp(x) for x in self.backend_obj.data]
# if future calendar not exists, return current calendar
if not os.path.exists(fname):
get_module_logger("data").warning(f"{freq}_future.txt not exists, return current calendar!")
fname = self._uri_cal.format(freq)
else:
fname = self._uri_cal.format(freq)
if not os.path.exists(fname):
raise ValueError("calendar not exists for freq " + freq)
with open(fname) as f:
return [pd.Timestamp(x.strip()) for x in f]
def calendar(self, start_time=None, end_time=None, freq="day", future=False): def calendar(self, start_time=None, end_time=None, freq="day", future=False):
_calendar, _calendar_index = self._get_calendar(freq, future) _calendar, _calendar_index = self._get_calendar(freq, future)
@@ -559,31 +584,15 @@ class LocalInstrumentProvider(InstrumentProvider):
Provide instrument data from local data source. Provide instrument data from local data source.
""" """
def __init__(self):
pass
@property @property
def _uri_inst(self): def _uri_inst(self):
"""Instrument file uri.""" """Instrument file uri."""
return os.path.join(C.get_data_path(), "instruments", "{}.txt") return os.path.join(C.get_data_path(), "instruments", "{}.txt")
def _load_instruments(self, market): def _load_instruments(self, market):
fname = self._uri_inst.format(market)
if not os.path.exists(fname):
raise ValueError("instruments not exists for market " + market)
_instruments = dict() self.backend.setdefault("kwargs", {}).update(market=market)
df = pd.read_csv( return self.backend_obj.data
fname,
sep="\t",
usecols=[0, 1, 2],
names=["inst", "start_datetime", "end_datetime"],
dtype={"inst": str},
parse_dates=["start_datetime", "end_datetime"],
)
for row in df.itertuples(index=False):
_instruments.setdefault(row[0], []).append((row[1], row[2]))
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):
market = instruments["market"] market = instruments["market"]
@@ -601,7 +610,7 @@ class LocalInstrumentProvider(InstrumentProvider):
inst: list( inst: list(
filter( filter(
lambda x: x[0] <= x[1], lambda x: x[0] <= x[1],
[(max(start_time, x[0]), min(end_time, x[1])) for x in spans], [(max(start_time, pd.Timestamp(x[0])), min(end_time, pd.Timestamp(x[1]))) for x in spans],
) )
) )
for inst, spans in _instruments.items() for inst, spans in _instruments.items()
@@ -627,6 +636,7 @@ class LocalFeatureProvider(FeatureProvider):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(LocalFeatureProvider, self).__init__(**kwargs)
self.remote = kwargs.get("remote", False) self.remote = kwargs.get("remote", False)
@property @property
@@ -638,14 +648,9 @@ class LocalFeatureProvider(FeatureProvider):
# validate # validate
field = str(field).lower()[1:] field = str(field).lower()[1:]
instrument = code_to_fname(instrument) instrument = code_to_fname(instrument)
uri_data = self._uri_data.format(instrument.lower(), field, freq)
if not os.path.exists(uri_data): self.backend.setdefault("kwargs", {}).update(instrument=instrument, field=field, freq=freq)
get_module_logger("data").warning("WARN: data not found for %s.%s" % (instrument, field)) return self.backend_obj[start_index : end_index + 1]
return pd.Series(dtype=np.float32)
# raise ValueError('uri_data not found: ' + uri_data)
# load
series = read_bin(uri_data, start_index, end_index)
return series
class LocalExpressionProvider(ExpressionProvider): class LocalExpressionProvider(ExpressionProvider):
@@ -1061,7 +1066,8 @@ def register_all_wrappers(C):
register_wrapper(Cal, _calendar_provider, "qlib.data") register_wrapper(Cal, _calendar_provider, "qlib.data")
logger.debug(f"registering Cal {C.calendar_provider}-{C.calendar_cache}") logger.debug(f"registering Cal {C.calendar_provider}-{C.calendar_cache}")
register_wrapper(Inst, C.instrument_provider, "qlib.data") _instrument_provider = init_instance_by_config(C.instrument_provider, module)
register_wrapper(Inst, _instrument_provider, "qlib.data")
logger.debug(f"registering Inst {C.instrument_provider}") logger.debug(f"registering Inst {C.instrument_provider}")
if getattr(C, "feature_provider", None) is not None: if getattr(C, "feature_provider", None) is not None:

View File

@@ -8,24 +8,29 @@ from typing import Iterator, Iterable, Union, Dict, Mapping, Tuple
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from . import CalendarStorage, InstrumentStorage, FeatureStorage, CalVT, InstKT, InstVT from qlib.data.storage import CalendarStorage, InstrumentStorage, FeatureStorage, CalVT, InstKT, InstVT
class FileCalendarStorage(CalendarStorage): class FileCalendarStorage(CalendarStorage):
def __init__(self, uri: str): def __init__(self, freq: str, future: bool, uri: str):
super(FileCalendarStorage, self).__init__(uri) super(FileCalendarStorage, self).__init__(freq, future, uri)
self._uri = Path(self._uri).expanduser().resolve() _file_name = f"{freq}_future.txt" if future else f"{freq}.txt"
self.uri = Path(self.uri).expanduser().joinpath(_file_name.lower())
def _read_calendar(self, skip_rows: int = 0, n_rows: int = None) -> np.ndarray: def _read_calendar(self, skip_rows: int = 0, n_rows: int = None) -> np.ndarray:
if not self._uri.exists(): if not self.uri.exists():
self._write_calendar(values=[]) self._write_calendar(values=[])
with self._uri.open("rb") as fp: with self.uri.open("rb") as fp:
return np.loadtxt(fp, str, skiprows=skip_rows, max_rows=n_rows, encoding="utf-8") return np.loadtxt(fp, str, skiprows=skip_rows, max_rows=n_rows, encoding="utf-8")
def _write_calendar(self, values: Iterable[CalVT], mode: str = "wb"): def _write_calendar(self, values: Iterable[CalVT], mode: str = "wb"):
with self._uri.open(mode=mode) as fp: with self.uri.open(mode=mode) as fp:
np.savetxt(fp, values, fmt="%s", encoding="utf-8") np.savetxt(fp, values, fmt="%s", encoding="utf-8")
@property
def data(self) -> Iterable[CalVT]:
return self._read_calendar()
def extend(self, values: Iterable[CalVT]) -> None: def extend(self, values: Iterable[CalVT]) -> None:
self._write_calendar(values, mode="ab") self._write_calendar(values, mode="ab")
@@ -64,27 +69,27 @@ class FileCalendarStorage(CalendarStorage):
return len(self._read_calendar()) return len(self._read_calendar())
def __iter__(self): def __iter__(self):
with self._uri.open("r") as fp: return iter(self._read_calendar())
yield fp.readline()
class FileInstrumentStorage(InstrumentStorage): class FileInstrumentStorage(InstrumentStorage):
INSTRUMENT_SEP = "\t" INSTRUMENT_SEP = "\t"
INSTRUMENT_START_FIELD = "start_datetime" INSTRUMENT_START_FIELD = "start_datetime"
INSTRUMENT_END_FIELD = "end_datetime" INSTRUMENT_END_FIELD = "end_datetime"
SYMBOL_FIELD_NAME = "instrument" SYMBOL_FIELD_NAME = "instrument"
def __init__(self, uri: str): def __init__(self, market: str, uri: str):
super(FileInstrumentStorage, self).__init__(uri=uri) super(FileInstrumentStorage, self).__init__(market, uri)
self._uri = Path(self._uri).expanduser().resolve() self.uri = Path(self.uri).expanduser().joinpath(f"{market.lower()}.txt")
def _read_instrument(self) -> Dict[InstKT, InstVT]: def _read_instrument(self) -> Dict[InstKT, InstVT]:
if not self._uri.exists(): if not self.uri.exists():
self._write_instrument() self._write_instrument()
_instruments = dict() _instruments = dict()
df = pd.read_csv( df = pd.read_csv(
self._uri, self.uri,
sep="\t", sep="\t",
usecols=[0, 1, 2], usecols=[0, 1, 2],
names=[self.SYMBOL_FIELD_NAME, self.INSTRUMENT_START_FIELD, self.INSTRUMENT_END_FIELD], names=[self.SYMBOL_FIELD_NAME, self.INSTRUMENT_START_FIELD, self.INSTRUMENT_END_FIELD],
@@ -97,7 +102,7 @@ class FileInstrumentStorage(InstrumentStorage):
def _write_instrument(self, data: Dict[InstKT, InstVT] = None) -> None: def _write_instrument(self, data: Dict[InstKT, InstVT] = None) -> None:
if not data: if not data:
with self._uri.open("w") as _: with self.uri.open("w") as _:
pass pass
return return
@@ -109,13 +114,17 @@ class FileInstrumentStorage(InstrumentStorage):
df = pd.concat(res, sort=False) df = pd.concat(res, sort=False)
df.loc[:, [self.SYMBOL_FIELD_NAME, self.INSTRUMENT_START_FIELD, self.INSTRUMENT_END_FIELD]].to_csv( df.loc[:, [self.SYMBOL_FIELD_NAME, self.INSTRUMENT_START_FIELD, self.INSTRUMENT_END_FIELD]].to_csv(
self._uri, header=False, sep=self.INSTRUMENT_SEP, index=False self.uri, header=False, sep=self.INSTRUMENT_SEP, index=False
) )
df.to_csv(self._uri, sep="\t", encoding="utf-8", header=False, index=False) df.to_csv(self.uri, sep="\t", encoding="utf-8", header=False, index=False)
def clear(self) -> None: def clear(self) -> None:
self._write_instrument(data={}) self._write_instrument(data={})
@property
def data(self) -> Dict[InstKT, InstVT]:
return self._read_instrument()
def __setitem__(self, k: InstKT, v: InstVT) -> None: def __setitem__(self, k: InstKT, v: InstVT) -> None:
inst = self._read_instrument() inst = self._read_instrument()
inst[k] = v inst[k] = v
@@ -143,7 +152,7 @@ class FileInstrumentStorage(InstrumentStorage):
raise TypeError(f"update expected at most 1 arguments, got {len(args)}") raise TypeError(f"update expected at most 1 arguments, got {len(args)}")
inst = self._read_instrument() inst = self._read_instrument()
if args: if args:
other = args[0] other = args[0] # type: dict
if isinstance(other, Mapping): if isinstance(other, Mapping):
for key in other: for key in other:
inst[key] = other[key] inst[key] = other[key]
@@ -160,29 +169,35 @@ class FileInstrumentStorage(InstrumentStorage):
class FileFeatureStorage(FeatureStorage): class FileFeatureStorage(FeatureStorage):
def __init__(self, uri: str): def __init__(self, instrument: str, field: str, freq: str, uri: str):
super(FileFeatureStorage, self).__init__(uri=uri) super(FileFeatureStorage, self).__init__(instrument, field, freq, uri)
self._uri = Path(self._uri) self.uri = (
Path(self.uri).expanduser().joinpath(instrument.lower()).joinpath(f"{field.lower()}.{freq.lower()}.bin")
)
def clear(self): def clear(self):
with self._uri.open("wb") as _: with self.uri.open("wb") as _:
pass pass
@property
def data(self) -> pd.Series:
return self[:]
def extend(self, series: pd.Series) -> None: def extend(self, series: pd.Series) -> None:
extend_start_index = self[0][0] + len(self) if self._uri.exists() else series.index[0] extend_start_index = self[0][0] + len(self) if self.uri.exists() else series.index[0]
series = series.reindex(pd.RangeIndex(extend_start_index, series.index[-1] + 1)) series = series.reindex(pd.RangeIndex(extend_start_index, series.index[-1] + 1))
with self._uri.open("ab") as fp: with self.uri.open("ab") as fp:
np.array(series.values).astype("<f").tofile(fp) np.array(series.values).astype("<f").tofile(fp)
def rebase(self, series: pd.Series) -> None: def rebase(self, series: pd.Series) -> None:
origin_series = self[:] origin_series = self[:]
series = series.append(origin_series.loc[origin_series.index > series.index[-1]]) series = series.append(origin_series.loc[origin_series.index > series.index[-1]])
series = series.reindex(pd.RangeIndex(series.index[0], series.index[-1])) series = series.reindex(pd.RangeIndex(series.index[0], series.index[-1]))
with self._uri.open("wb") as fp: with self.uri.open("wb") as fp:
np.array(series.values).astype("<f").tofile(fp) np.array(series.values).astype("<f").tofile(fp)
def __getitem__(self, i: Union[int, slice]) -> Union[Tuple[int, float], pd.Series]: def __getitem__(self, i: Union[int, slice]) -> Union[Tuple[int, float], pd.Series]:
if not self._uri.exists(): if not self.uri.exists():
if isinstance(i, int): if isinstance(i, int):
return None, None return None, None
elif isinstance(i, slice): elif isinstance(i, slice):
@@ -190,14 +205,14 @@ class FileFeatureStorage(FeatureStorage):
else: else:
raise TypeError(f"type(i) = {type(i)}") raise TypeError(f"type(i) = {type(i)}")
with open(self._uri, "rb") as fp: with open(self.uri, "rb") as fp:
ref_start_index = int(np.frombuffer(fp.read(4), dtype="<f")[0]) ref_start_index = int(np.frombuffer(fp.read(4), dtype="<f")[0])
if isinstance(i, int): if isinstance(i, int):
if ref_start_index > i: if ref_start_index > i:
raise IndexError(f"{i}: start index is {ref_start_index}") raise IndexError(f"{i}: start index is {ref_start_index}")
fp.seek(4 * (i - ref_start_index) + 4) fp.seek(4 * (i - ref_start_index) + 4)
return i, struct.unpack("f", fp.read(4)) return i, struct.unpack("f", fp.read(4))[0]
elif isinstance(i, slice): elif isinstance(i, slice):
start_index = i.start start_index = i.start
end_index = i.stop - 1 end_index = i.stop - 1
@@ -213,18 +228,18 @@ class FileFeatureStorage(FeatureStorage):
raise TypeError(f"type(i) = {type(i)}") raise TypeError(f"type(i) = {type(i)}")
def __len__(self) -> int: def __len__(self) -> int:
return self._uri.stat().st_size // 4 - 1 if self._uri.exists() else 0 return self.uri.stat().st_size // 4 - 1 if self.uri.exists() else 0
def __iter__(self): def __iter__(self):
if not self._uri.exists(): if not self.uri.exists():
return return
with open(self._uri, "rb") as fp: with open(self.uri, "rb") as fp:
ref_start_index = int(np.frombuffer(fp.read(4), dtype="<f")[0]) ref_start_index = int(np.frombuffer(fp.read(4), dtype="<f")[0])
fp.seek(4) fp.seek(4)
while True: while True:
v = fp.read(4) v = fp.read(4)
if v: if v:
yield ref_start_index, struct.unpack("f", v) yield ref_start_index, struct.unpack("f", v)[0]
ref_start_index += 1 ref_start_index += 1
else: else:
break break

View File

@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. # Copyright (c) Microsoft Corporation.
# Licensed under the MIT License. # Licensed under the MIT License.
from typing import Iterable, overload, Tuple, List, Text, Iterator, Union from typing import Iterable, overload, Tuple, List, Text, Iterator, Union, Dict
import pandas as pd import pandas as pd
@@ -15,8 +15,15 @@ InstKT = Text
class CalendarStorage: class CalendarStorage:
def __init__(self, uri: str): def __init__(self, freq: str, future: bool, uri: str):
self._uri = uri self.freq = freq
self.future = future
self.uri = uri
@property
def data(self) -> Iterable[CalVT]:
"""get all data"""
raise NotImplementedError("Subclass of CalendarStorage must implement `data` method")
def extend(self, iterable: Iterable[CalVT]) -> None: def extend(self, iterable: Iterable[CalVT]) -> None:
raise NotImplementedError("Subclass of CalendarStorage must implement `extend` method") raise NotImplementedError("Subclass of CalendarStorage must implement `extend` method")
@@ -82,10 +89,19 @@ class CalendarStorage:
"""x.__len__() <==> len(x)""" """x.__len__() <==> len(x)"""
raise NotImplementedError("Subclass of CalendarStorage must implement `__len__` method") raise NotImplementedError("Subclass of CalendarStorage must implement `__len__` method")
def __iter__(self):
raise NotImplementedError("Subclass of CalendarStorage must implement `__iter__` method")
class InstrumentStorage: class InstrumentStorage:
def __init__(self, uri: str): def __init__(self, market: str, uri: str):
self._uri = uri self.market = market
self.uri = uri
@property
def data(self) -> Dict[InstKT, InstVT]:
"""get all data"""
raise NotImplementedError("Subclass of InstrumentStorage must implement `data` method")
def clear(self) -> None: def clear(self) -> None:
raise NotImplementedError("Subclass of InstrumentStorage must implement `clear` method") raise NotImplementedError("Subclass of InstrumentStorage must implement `clear` method")
@@ -120,8 +136,16 @@ class InstrumentStorage:
class FeatureStorage: class FeatureStorage:
def __init__(self, uri: str): def __init__(self, instrument: str, field: str, freq: str, uri: str):
self._uri = uri self.instrument = instrument
self.field = field
self.freq = freq
self.uri = uri
@property
def data(self) -> pd.Series:
"""get all data"""
raise NotImplementedError("Subclass of FeatureStorage must implement `data` method")
def clear(self): def clear(self):
""" Remove all items from FeatureStorage. """ """ Remove all items from FeatureStorage. """

View File

@@ -16,15 +16,16 @@ from qlib.data.storage.file_storage import (
FileFeatureStorage as FeatureStorage, FileFeatureStorage as FeatureStorage,
) )
DATA_DIR = Path(__file__).parent.joinpath("test_get_data") _file_name = Path(__file__).name.split(".")[0]
DATA_DIR = Path(__file__).parent.joinpath(f"{_file_name}_data")
QLIB_DIR = DATA_DIR.joinpath("qlib") QLIB_DIR = DATA_DIR.joinpath("qlib")
QLIB_DIR.mkdir(exist_ok=True, parents=True) QLIB_DIR.mkdir(exist_ok=True, parents=True)
# TODO: set value # TODO: set value
CALENDAR_URI = QLIB_DIR.joinpath("calendars").joinpath("day.txt") CALENDAR_URI = QLIB_DIR.joinpath("calendars")
INSTRUMENT_URI = QLIB_DIR.joinpath("instruments").joinpath("csi300.txt") INSTRUMENT_URI = QLIB_DIR.joinpath("instruments")
FEATURE_URI = QLIB_DIR.joinpath("features").joinpath("SH600004").joinpath("close.day.bin") FEATURE_URI = QLIB_DIR.joinpath("features")
class TestStorage: class TestStorage:
@@ -38,9 +39,10 @@ class TestStorage:
def test_calendar_storage(self): def test_calendar_storage(self):
calendar = CalendarStorage(uri=CALENDAR_URI) calendar = CalendarStorage(freq="day", future=False, uri=CALENDAR_URI)
assert isinstance(calendar, Iterable), f"{calendar.__class__.__name__} is not Iterable" assert isinstance(calendar, Iterable), f"{calendar.__class__.__name__} is not Iterable"
assert isinstance(calendar[:], Iterable), f"{calendar.__class__.__name__}.__getitem__(s: slice) is not Iterable" assert isinstance(calendar[:], Iterable), f"{calendar.__class__.__name__}.__getitem__(s: slice) is not Iterable"
assert isinstance(calendar.data, Iterable), f"{calendar.__class__.__name__}.data is not Iterable"
print(f"calendar[1: 5]: {calendar[1:5]}") print(f"calendar[1: 5]: {calendar[1:5]}")
print(f"calendar[0]: {calendar[0]}") print(f"calendar[0]: {calendar[0]}")
@@ -80,11 +82,11 @@ class TestStorage:
""" """
instrument = InstrumentStorage(uri=INSTRUMENT_URI) instrument = InstrumentStorage(market="csi300", uri=INSTRUMENT_URI)
assert isinstance(instrument, Iterable), f"{instrument.__class__.__name__} is not Iterable" assert isinstance(instrument, Iterable), f"{instrument.__class__.__name__} is not Iterable"
for inst, spans in instrument.items(): for inst, spans in instrument.data.items():
assert isinstance(inst, str) and isinstance( assert isinstance(inst, str) and isinstance(
spans, Iterable spans, Iterable
), f"{instrument.__class__.__name__} value is not Iterable" ), f"{instrument.__class__.__name__} value is not Iterable"
@@ -149,11 +151,14 @@ class TestStorage:
""" """
feature = FeatureStorage(uri=FEATURE_URI) feature = FeatureStorage(instrument="SH600004", field="close", freq="day", uri=FEATURE_URI)
assert isinstance(feature, Iterable), f"{feature.__class__.__name__} is not Iterable" assert isinstance(feature, Iterable), f"{feature.__class__.__name__} is not Iterable"
with pytest.raises(IndexError): with pytest.raises(IndexError):
print(feature[0]) print(feature[0])
assert isinstance(
feature[815][1], (np.float, np.float32)
), f"{feature.__class__.__name__}.__getitem__(i: int) error"
assert len(feature[815:818]) == 3, f"{feature.__class__.__name__}.__getitem__(s: slice) error" assert len(feature[815:818]) == 3, f"{feature.__class__.__name__}.__getitem__(s: slice) error"
print(f"feature[815: 818]: {feature[815: 818]}") print(f"feature[815: 818]: {feature[815: 818]}")
@@ -162,5 +167,5 @@ class TestStorage:
isinstance(_item, tuple) and len(_item) == 2 isinstance(_item, tuple) and len(_item) == 2
), f"{feature.__class__.__name__}.__iter__ item type error" ), f"{feature.__class__.__name__}.__iter__ item type error"
assert isinstance(_item[0], int) and isinstance( assert isinstance(_item[0], int) and isinstance(
_item[1], (float, np.float, np.float32) _item[1], (np.float, np.float32)
), f"{feature.__class__.__name__}.__iter__ value type error" ), f"{feature.__class__.__name__}.__iter__ value type error"