From 43a8f502ede828c27e37a3092e9396821efa7850 Mon Sep 17 00:00:00 2001 From: "wangwenxi.handsome" Date: Fri, 27 Aug 2021 10:48:10 +0000 Subject: [PATCH] fix bug --- qlib/backtest/exchange.py | 26 ++--- qlib/backtest/high_performance_ds.py | 158 ++++++++++++--------------- qlib/backtest/order.py | 2 +- qlib/backtest/report.py | 16 +-- qlib/utils/index_data.py | 40 +++---- qlib/utils/time.py | 4 +- 6 files changed, 109 insertions(+), 137 deletions(-) diff --git a/qlib/backtest/exchange.py b/qlib/backtest/exchange.py index 4c726720c..347f33790 100644 --- a/qlib/backtest/exchange.py +++ b/qlib/backtest/exchange.py @@ -18,7 +18,7 @@ from ..config import C, REG_CN from ..utils.resam import resam_ts_data, ts_data_last from ..log import get_module_logger from .order import Order, OrderDir, OrderHelper -from .high_performance_ds import PandasQuote, CN1min_NumpyQuote +from .high_performance_ds import PandasQuote, CN1minNumpyQuote class Exchange: @@ -36,7 +36,7 @@ class Exchange: close_cost=0.0025, min_cost=5, extra_quote=None, - quote_cls=CN1min_NumpyQuote, + quote_cls=CN1minNumpyQuote, **kwargs, ): """__init__ @@ -327,20 +327,20 @@ class Exchange: """ if direction is None: - buy_limit = self.quote.get_data(stock_id, start_time, end_time, fields="limit_buy", method="all") - sell_limit = self.quote.get_data(stock_id, start_time, end_time, fields="limit_sell", method="all") + buy_limit = self.quote.get_data(stock_id, start_time, end_time, field="limit_buy", method="all") + sell_limit = self.quote.get_data(stock_id, start_time, end_time, field="limit_sell", method="all") return buy_limit or sell_limit elif direction == Order.BUY: - return self.quote.get_data(stock_id, start_time, end_time, fields="limit_buy", method="all") + return self.quote.get_data(stock_id, start_time, end_time, field="limit_buy", method="all") elif direction == Order.SELL: - return self.quote.get_data(stock_id, start_time, end_time, fields="limit_sell", method="all") + return self.quote.get_data(stock_id, start_time, end_time, field="limit_sell", method="all") else: raise ValueError(f"direction {direction} is not supported!") def check_stock_suspended(self, stock_id, start_time, end_time): # is suspended if stock_id in self.quote.get_all_stock(): - return self.quote.get_data(stock_id, start_time, end_time) is None + return self.quote.get_data(stock_id, start_time, end_time, "$close") is None else: return True @@ -411,10 +411,10 @@ class Exchange: return self.quote.get_data(stock_id, start_time, end_time, method=method) def get_close(self, stock_id, start_time, end_time, method=ts_data_last): - return self.quote.get_data(stock_id, start_time, end_time, fields="$close", method=method) + return self.quote.get_data(stock_id, start_time, end_time, field="$close", method=method) def get_volume(self, stock_id, start_time, end_time, method="sum"): - return self.quote.get_data(stock_id, start_time, end_time, fields="$volume", method=method) + return self.quote.get_data(stock_id, start_time, end_time, field="$volume", method=method) def get_deal_price(self, stock_id, start_time, end_time, direction: OrderDir, method=ts_data_last): if direction == OrderDir.SELL: @@ -423,7 +423,7 @@ class Exchange: pstr = self.buy_price else: raise NotImplementedError(f"This type of input is not supported") - deal_price = self.quote.get_data(stock_id, start_time, end_time, fields=pstr, method=method) + deal_price = self.quote.get_data(stock_id, start_time, end_time, field=pstr, method=method) if method is not None and (np.isclose(deal_price, 0.0) or np.isnan(deal_price)): self.logger.warning(f"(stock_id:{stock_id}, trade_time:{(start_time, end_time)}, {pstr}): {deal_price}!!!") self.logger.warning(f"setting deal_price to close price") @@ -441,7 +441,7 @@ class Exchange: assert start_time is not None and end_time is not None, "the time range must be given" if stock_id not in self.quote.get_all_stock(): return None - return self.quote.get_data(stock_id, start_time, end_time, fields="$factor", method=ts_data_last) + return self.quote.get_data(stock_id, start_time, end_time, field="$factor", method=ts_data_last) def generate_amount_position_from_weight_position( self, weight_position, cash, start_time, end_time, direction=OrderDir.BUY @@ -684,7 +684,7 @@ class Exchange: order.stock_id, order.start_time, order.end_time, - fields=limit[1], + field=limit[1], method="sum", ) vol_limit_num.append(limit_value) @@ -693,7 +693,7 @@ class Exchange: order.stock_id, order.start_time, order.end_time, - fields=limit[1], + field=limit[1], method=ts_data_last, ) vol_limit_num.append(limit_value - dealt_order_amount[order.stock_id]) diff --git a/qlib/backtest/high_performance_ds.py b/qlib/backtest/high_performance_ds.py index f17f6e14c..d125fadc8 100644 --- a/qlib/backtest/high_performance_ds.py +++ b/qlib/backtest/high_performance_ds.py @@ -2,21 +2,19 @@ # Licensed under the MIT License. -from builtins import ValueError, isinstance from functools import lru_cache import logging from typing import List, Text, Union, Callable, Iterable, Dict from collections import OrderedDict import inspect -import bisect import pandas as pd import numpy as np -from ..utils.index_data import IndexData +from ..utils.index_data import IndexData, SingleData from ..utils.resam import resam_ts_data, ts_data_last from ..log import get_module_logger -from ..utils.time import if_single_data +from ..utils.time import is_single_value class BaseQuote: @@ -39,10 +37,10 @@ class BaseQuote: stock_id: str, start_time: Union[pd.Timestamp, str], end_time: Union[pd.Timestamp, str], - fields: Union[str, None] = None, + field: Union[str], method: Union[str, Callable, None] = None, - ) -> Union[None, Union[int, float, bool], "IndexData"]: - """get the specific fields of stock data during start time and end_time, + ) -> Union[None, int, float, bool, "IndexData"]: + """get the specific field of stock data during start time and end_time, and apply method to the data. Example: @@ -63,22 +61,13 @@ class BaseQuote: this function is used for three case: - 1. Both fields and method are not None. It returns int/float/bool. - print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", fields="$close", method="last")) + 1. method is not None. It returns int/float/bool. + print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", field="$close", method="last")) 85.713585 - 2. Both fields and method are None. It returns np.ndarray. - print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", fields=None, method=None)) - - [ - [86.778313, 16162960.0], - [87.433578, 28117442.0], - [85.713585, 23632884.0], - ] - - 3. fields is not None, and method is None. It returns IndexData. - print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", fields="$close", method=None)) + 2. method is None. It returns IndexData. + print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", field="$close", method=None)) IndexData([86.778313, 87.433578, 85.713585], [2010-01-04, 2010-01-05, 2010-01-06]) @@ -89,7 +78,7 @@ class BaseQuote: closed start time for backtest end_time : Union[pd.Timestamp, str] closed end time for backtest - fields : Union[str, None] + field : str the columns of data to fetch method : Union[str, Callable, None] the method apply to data. @@ -97,7 +86,8 @@ class BaseQuote: Return ---------- - Union[None, Union[int, float, bool], IndexData] + Union[None, int, float, bool, IndexData] + None means there is no stock data from data source. please refer to Example as following. """ @@ -115,32 +105,21 @@ class PandasQuote(BaseQuote): def get_all_stock(self): return self.data.keys() - def get_data(self, stock_id, start_time, end_time, fields=None, method=None): - if fields is None and method is not None: - raise ValueError(f"method must be None when fields is None") - - if fields is None: - stock_data = resam_ts_data(self.data[stock_id], start_time, end_time, method=method) - elif isinstance(fields, str): - stock_data = resam_ts_data(self.data[stock_id][fields], start_time, end_time, method=method) - else: - raise ValueError(f"fields must be None, str") - + def get_data(self, stock_id, start_time, end_time, field, method=None): + stock_data = resam_ts_data(self.data[stock_id][field], start_time, end_time, method=method) if stock_data is None: return None - elif isinstance(stock_data, (bool, np.bool_, int, float, np.signedinteger, np.floating)): + elif isinstance(stock_data, (bool, np.bool_, int, float, np.number)): return stock_data elif isinstance(stock_data, pd.Series): return IndexData.Series(stock_data) - elif isinstance(stock_data, pd.DataFrame): - return stock_data.values else: raise ValueError(f"stock data from resam_ts_data must be a number, pd.Series or pd.DataFrame") -class CN1min_NumpyQuote(BaseQuote): +class CN1minNumpyQuote(BaseQuote): def __init__(self, quote_df: pd.DataFrame): - """CN1min_NumpyQuote + """CN1minNumpyQuote Parameters ---------- @@ -153,48 +132,37 @@ class CN1min_NumpyQuote(BaseQuote): for stock_id, stock_val in quote_df.groupby(level="instrument"): quote_dict[stock_id] = IndexData.DataFrame(stock_val.droplevel(level="instrument")) self.data = quote_dict - self.freq = np.timedelta64(1, "m") + self.freq = pd.Timedelta(minutes=1) def get_all_stock(self): return self.data.keys() @lru_cache(maxsize=512) - def get_data(self, stock_id, start_time, end_time, fields=None, method=None): - if fields is None and method is not None: - raise ValueError(f"method must be None when fields is None") - + def get_data(self, stock_id, start_time, end_time, field, method=None): # check stock id if stock_id not in self.get_all_stock(): return None # single data # If it don't consider the classification of single data, it will consume a lot of time. - if if_single_data(start_time, end_time, self.freq): + if is_single_value(start_time, end_time, self.freq): now_index_map = self.data[stock_id].index_map now_columns_map = self.data[stock_id].columns_map if start_time not in now_index_map: return None - if fields is None: - return self.data[stock_id].values[now_index_map[start_time]] else: - return self.data[stock_id].values[now_index_map[start_time], now_columns_map[fields]] + return self.data[stock_id].values[now_index_map[start_time], now_columns_map[field]] # multi data else: - if fields is None and method is None: - stock_data = self.data[stock_id].loc(start_time, end_time) - if stock_data.empty: - return None - else: - return stock_data.values - elif fields is not None and method is None: - stock_data = self.data[stock_id].loc(start_time, end_time, fields) + if method is None: + stock_data = self.data[stock_id].loc(start_time, end_time, field) if stock_data.empty: return None else: return stock_data - elif fields is not None and method is not None: - stock_data = self.data[stock_id].loc(start_time, end_time, fields) + else: + stock_data = self.data[stock_id].loc(start_time, end_time, field) if stock_data.empty: return None elif len(stock_data) == 1: @@ -231,6 +199,20 @@ class BaseSingleMetric: """ def __init__(self, metric: Union[dict, pd.Series]): + """Single data structure for each metric. + + Parameters + ---------- + metric : Union[dict, pd.Series] + keys/index is stock_id, value is the metric value. + for example: + SH600068 NaN + SH600079 1.0 + SH600266 NaN + ... + SZ300692 NaN + SZ300719 NaN, + """ raise NotImplementedError(f"Please implement the `__init__` method") def __add__(self, other: Union["BaseSingleMetric", int, float]) -> "BaseSingleMetric": @@ -277,7 +259,7 @@ class BaseSingleMetric: def abs(self) -> "BaseSingleMetric": raise NotImplementedError(f"Please implement the `abs` method") - def astype(self, type: type) -> "BaseSingleMetric": + def astype(self, dtype: type) -> "BaseSingleMetric": raise NotImplementedError(f"Please implement the `astype` method") @property @@ -316,7 +298,8 @@ class BaseOrderIndicator: to inherit the BaseSingleMetric. """ - def __init__(self): + def __init__(self, data): + self.data = data self.logger = get_module_logger("online operator") def assign(self, col: str, metric: Union[dict, pd.Series]): @@ -358,8 +341,13 @@ class BaseOrderIndicator: BaseSingleMetric new metric. """ - - raise NotImplementedError(f"Please implement the 'transfer' method") + func_sig = inspect.signature(func).parameters.keys() + func_kwargs = {sig: self.data[sig] for sig in func_sig} + tmp_metric = func(**func_kwargs) + if new_col is not None: + self.data[new_col] = tmp_metric + else: + return tmp_metric def get_metric_series(self, metric: str) -> pd.Series: """return the single metric with pd.Series format. @@ -378,8 +366,8 @@ class BaseOrderIndicator: raise NotImplementedError(f"Please implement the 'get_metric_series' method") - def get_index_data(self, metric) -> IndexData.Series: - """get one metric with the format of IndexData.Series + def get_index_data(self, metric) -> SingleData: + """get one metric with the format of SingleData Parameters ---------- @@ -389,7 +377,7 @@ class BaseOrderIndicator: Return ------ IndexData.Series - one metric with the format of IndexData.Series + one metric with the format of SingleData """ raise NotImplementedError(f"Please implement the 'get_index_data' method") @@ -431,6 +419,9 @@ class BaseOrderIndicator: class SingleMetric(BaseSingleMetric): + def __init__(self, metric): + self.metric = metric + def __add__(self, other): if isinstance(other, (int, float)): return self.__class__(self.metric + other) @@ -502,7 +493,7 @@ class SingleMetric(BaseSingleMetric): class PandasSingleMetric(SingleMetric): """Each SingleMetric is based on pd.Series.""" - def __init__(self, metric: Union[dict, pd.Series]): + def __init__(self, metric: Union[dict, pd.Series] = {}): if isinstance(metric, dict): self.metric = pd.Series(metric) elif isinstance(metric, pd.Series): @@ -522,13 +513,17 @@ class PandasSingleMetric(SingleMetric): def abs(self): return self.__class__(self.metric.abs()) - def astype(self, type): - return self.__class__(self.metric.astype(type)) + def astype(self, dtype): + return self.__class__(self.metric.astype(dtype)) @property def empty(self): return self.metric.empty + @property + def index(self): + return list(self.metric.index) + def add(self, other, fill_value=None): return self.__class__(self.metric.add(other.metric, fill_value=fill_value)) @@ -538,6 +533,9 @@ class PandasSingleMetric(SingleMetric): def apply(self, func: Callable): return self.__class__(self.metric.apply(func)) + def reindex(self, index, fill_value): + return self.__class__(self.metric.reindex(index, fill_value=fill_value)) + class PandasOrderIndicator(BaseOrderIndicator): """ @@ -552,15 +550,6 @@ class PandasOrderIndicator(BaseOrderIndicator): def assign(self, col: str, metric: Union[dict, pd.Series]): self.data[col] = PandasSingleMetric(metric) - def transfer(self, func: Callable, new_col: str = None) -> Union[None, PandasSingleMetric]: - func_sig = inspect.signature(func).parameters.keys() - func_kwargs = {sig: self.data[sig] for sig in func_sig} - tmp_metric = func(**func_kwargs) - if new_col is not None: - self.data[new_col] = tmp_metric - else: - return tmp_metric - def get_index_data(self, metric): if metric in self.data: return IndexData.Series(self.data[metric].metric) @@ -577,7 +566,7 @@ class PandasOrderIndicator(BaseOrderIndicator): return {k: v.metric for k, v in self.data.items()} @staticmethod - def sum_all_indicators(order_indicator, indicators: list, metrics: Union[str, List[str]], fill_value=None): + def sum_all_indicators(order_indicator, indicators: list, metrics: Union[str, List[str]], fill_value=0): if isinstance(metrics, str): metrics = [metrics] for metric in metrics: @@ -589,26 +578,17 @@ class PandasOrderIndicator(BaseOrderIndicator): class NumpyOrderIndicator(BaseOrderIndicator): """ - The data structure is OrderedDict(str: IndexData.Series). + The data structure is OrderedDict(str: SingleData). Each IndexData.Series is one metric. Str is the name of metric. """ def __init__(self): - self.data: Dict[str, IndexData.Series] = OrderedDict() + self.data: Dict[str, SingleData] = OrderedDict() def assign(self, col: str, metric: dict): self.data[col] = IndexData.Series(metric) - def transfer(self, func: Callable, new_col: str = None) -> Union[None, IndexData.Series]: - func_sig = inspect.signature(func).parameters.keys() - func_kwargs = {sig: self.data[sig] for sig in func_sig} - tmp_metric = func(**func_kwargs) - if new_col is not None: - self.data[new_col] = tmp_metric - else: - return tmp_metric - def get_index_data(self, metric): if metric in self.data: return self.data[metric] @@ -616,7 +596,7 @@ class NumpyOrderIndicator(BaseOrderIndicator): return IndexData.Series() def get_metric_series(self, metric: str) -> Union[pd.Series]: - return self.data[metric].to_pd_series() + return self.data[metric].to_series() def to_series(self) -> Dict[str, pd.Series]: tmp_metric_dict = {} diff --git a/qlib/backtest/order.py b/qlib/backtest/order.py index 42af5f24e..e169ffd64 100644 --- a/qlib/backtest/order.py +++ b/qlib/backtest/order.py @@ -109,7 +109,7 @@ class Order: return self.direction * 2 - 1 @staticmethod - def parse_dir(direction: Union[str, int, np.integer, OrderDir, np.ndarray]) -> OrderDir: + def parse_dir(direction: Union[str, int, np.integer, OrderDir, np.ndarray]) -> Union[OrderDir, np.ndarray]: if isinstance(direction, OrderDir): return direction elif isinstance(direction, (int, float, np.integer, np.floating)): diff --git a/qlib/backtest/report.py b/qlib/backtest/report.py index 31c3e7b0a..e29921da6 100644 --- a/qlib/backtest/report.py +++ b/qlib/backtest/report.py @@ -4,15 +4,14 @@ from collections import OrderedDict import pathlib -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Union import numpy as np import pandas as pd from qlib.backtest.exchange import Exchange from qlib.backtest.order import BaseTradeDecision, Order, OrderDir - -from .high_performance_ds import PandasOrderIndicator, NumpyOrderIndicator +from .high_performance_ds import PandasOrderIndicator, NumpyOrderIndicator, SingleMetric from ..utils.index_data import IndexData, SingleData from ..tests.config import CSI300_BENCH from ..utils.resam import get_higher_eq_freq_feature, resam_ts_data @@ -305,8 +304,9 @@ class Indicator: def _update_order_fulfill_rate(self): def func(deal_amount, amount): - # deal_amount is np.NaN when there is no inner decision. So full fill rate is 0. - tmp_deal_amount = deal_amount.replace({np.NaN: 0}) + # deal_amount is np.NaN or None when there is no inner decision. So full fill rate is 0. + tmp_deal_amount = deal_amount.reindex(amount.index, 0) + tmp_deal_amount = tmp_deal_amount.replace({np.NaN: 0}) return tmp_deal_amount / amount self.order_indicator.transfer(func, "ffr") @@ -385,7 +385,7 @@ class Indicator: if price_s is None: return None, None - if isinstance(price_s, (int, float, np.signedinteger, np.floating)): + if isinstance(price_s, (int, float, np.number)): price_s = IndexData.Series(price_s, [trade_start_time]) elif isinstance(price_s, SingleData): pass @@ -400,7 +400,7 @@ class Indicator: if agg == "vwap": volume_s = trade_exchange.get_volume(inst, trade_start_time, trade_end_time, method=None) - if isinstance(volume_s, (int, float, np.floating)): + if isinstance(volume_s, (int, float, np.number)): volume_s = IndexData.Series(volume_s, [trade_start_time]) volume_s = volume_s.reindex(price_s.index) elif agg == "twap": @@ -414,7 +414,7 @@ class Indicator: def _agg_base_price( self, - inner_order_indicators: List[Dict[str, pd.Series]], + inner_order_indicators: List[Dict[str, Union[SingleMetric, SingleData]]], decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]], trade_exchange: Exchange, pa_config: dict = {}, diff --git a/qlib/utils/index_data.py b/qlib/utils/index_data.py index 8c0d1874e..8a92cfcf5 100644 --- a/qlib/utils/index_data.py +++ b/qlib/utils/index_data.py @@ -35,7 +35,7 @@ class IndexData: return MultiData(data, index, columns) @staticmethod - def concat(data_list, axis=0): + def concat(data_list: Union["SingleData"], axis=0) -> "MultiData": """concat all SingleData by index. TODO: now just for SingleData. @@ -50,7 +50,7 @@ class IndexData: the MultiData with ndim == 2 """ if axis == 0: - raise NotImplementedError(f"please implement this fuc when axis == 0") + raise NotImplementedError(f"please implement this func when axis == 0") elif axis == 1: # get all index and row all_index = set() @@ -90,7 +90,7 @@ class BaseData: raise NotImplementedError(f"please implement _align_index func") def __add__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data + other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -99,7 +99,7 @@ class BaseData: return NotImplemented def __sub__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data - other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -108,7 +108,7 @@ class BaseData: return NotImplemented def __rsub__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(other - self.data, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -117,7 +117,7 @@ class BaseData: return NotImplemented def __mul__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data * other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -126,7 +126,7 @@ class BaseData: return NotImplemented def __truediv__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data / other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -135,7 +135,7 @@ class BaseData: return NotImplemented def __eq__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data == other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -144,7 +144,7 @@ class BaseData: return NotImplemented def __gt__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data > other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -153,7 +153,7 @@ class BaseData: return NotImplemented def __lt__(self, other): - if isinstance(other, (int, float, np.floating)): + if isinstance(other, (int, float, np.number)): return self.__class__(self.data < other, *self.index_columns) elif isinstance(other, self.__class__): tmp_data1, tmp_data2 = self._align_index(other) @@ -169,9 +169,9 @@ class BaseData: tmp_data = np.absolute(self.data) return self.__class__(tmp_data, *self.index_columns) - def astype(self, type): + def astype(self, dtype): """change the type of data.""" - tmp_data = self.data.astype(type) + tmp_data = self.data.astype(dtype) return self.__class__(tmp_data, *self.index_columns) def replace(self, to_replace: dict): @@ -234,7 +234,7 @@ class BaseData: class SingleData(BaseData): - def __init__(self, data: Union[int, float, np.floating, list, np.ndarray] = [], index: Union[list, pd.Index] = []): + def __init__(self, data: Union[int, float, np.number, list] = [], index: Union[list, pd.Index] = []): """A data structure of index and numpy data. It's used to replace pd.Series due to high-speed. @@ -301,6 +301,8 @@ class SingleData(BaseData): SingleData reindex data """ + if self.index == index: + return self tmp_data = np.full(len(index), fill_value, dtype=np.float64) for index_id, index_item in enumerate(index): if index_item in self.index: @@ -323,17 +325,7 @@ class SingleData(BaseData): """ return dict(zip(self.index, self.data.tolist())) - def to_frame(self): - """convert SingleData to MultiData. - - Returns - ------- - MultiData - data with the MultiData format. - """ - return MultiData(self.data[:, np.newaxis], self.index) - - def to_pd_series(self): + def to_series(self): return pd.Series(self.data, index=self.index) def __getitem__(self, index: Union["SingleData", int, str]): diff --git a/qlib/utils/time.py b/qlib/utils/time.py index e9ae82c5f..2b2a9d8ec 100644 --- a/qlib/utils/time.py +++ b/qlib/utils/time.py @@ -38,8 +38,8 @@ def get_min_cal(shift: int = 0) -> List[time]: return cal -def if_single_data(start_time, end_time, freq): - """Is there only one piece of data to obtain. +def is_single_value(start_time, end_time, freq): + """Is there only one piece of data for cn stock market. Parameters ----------