1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 17:34:35 +08:00
This commit is contained in:
wangwenxi.handsome
2021-08-27 10:48:10 +00:00
committed by you-n-g
parent 7ee4a207bc
commit 43a8f502ed
6 changed files with 109 additions and 137 deletions

View File

@@ -18,7 +18,7 @@ from ..config import C, REG_CN
from ..utils.resam import resam_ts_data, ts_data_last from ..utils.resam import resam_ts_data, ts_data_last
from ..log import get_module_logger from ..log import get_module_logger
from .order import Order, OrderDir, OrderHelper from .order import Order, OrderDir, OrderHelper
from .high_performance_ds import PandasQuote, CN1min_NumpyQuote from .high_performance_ds import PandasQuote, CN1minNumpyQuote
class Exchange: class Exchange:
@@ -36,7 +36,7 @@ class Exchange:
close_cost=0.0025, close_cost=0.0025,
min_cost=5, min_cost=5,
extra_quote=None, extra_quote=None,
quote_cls=CN1min_NumpyQuote, quote_cls=CN1minNumpyQuote,
**kwargs, **kwargs,
): ):
"""__init__ """__init__
@@ -327,20 +327,20 @@ class Exchange:
""" """
if direction is None: if direction is None:
buy_limit = self.quote.get_data(stock_id, start_time, end_time, fields="limit_buy", 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, fields="limit_sell", 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 return buy_limit or sell_limit
elif direction == Order.BUY: 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: 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: else:
raise ValueError(f"direction {direction} is not supported!") raise ValueError(f"direction {direction} is not supported!")
def check_stock_suspended(self, stock_id, start_time, end_time): def check_stock_suspended(self, stock_id, start_time, end_time):
# is suspended # is suspended
if stock_id in self.quote.get_all_stock(): 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: else:
return True return True
@@ -411,10 +411,10 @@ class Exchange:
return self.quote.get_data(stock_id, start_time, end_time, method=method) 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): 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"): 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): def get_deal_price(self, stock_id, start_time, end_time, direction: OrderDir, method=ts_data_last):
if direction == OrderDir.SELL: if direction == OrderDir.SELL:
@@ -423,7 +423,7 @@ class Exchange:
pstr = self.buy_price pstr = self.buy_price
else: else:
raise NotImplementedError(f"This type of input is not supported") 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)): 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"(stock_id:{stock_id}, trade_time:{(start_time, end_time)}, {pstr}): {deal_price}!!!")
self.logger.warning(f"setting deal_price to close 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" 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(): if stock_id not in self.quote.get_all_stock():
return None 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( def generate_amount_position_from_weight_position(
self, weight_position, cash, start_time, end_time, direction=OrderDir.BUY self, weight_position, cash, start_time, end_time, direction=OrderDir.BUY
@@ -684,7 +684,7 @@ class Exchange:
order.stock_id, order.stock_id,
order.start_time, order.start_time,
order.end_time, order.end_time,
fields=limit[1], field=limit[1],
method="sum", method="sum",
) )
vol_limit_num.append(limit_value) vol_limit_num.append(limit_value)
@@ -693,7 +693,7 @@ class Exchange:
order.stock_id, order.stock_id,
order.start_time, order.start_time,
order.end_time, order.end_time,
fields=limit[1], field=limit[1],
method=ts_data_last, method=ts_data_last,
) )
vol_limit_num.append(limit_value - dealt_order_amount[order.stock_id]) vol_limit_num.append(limit_value - dealt_order_amount[order.stock_id])

View File

@@ -2,21 +2,19 @@
# Licensed under the MIT License. # Licensed under the MIT License.
from builtins import ValueError, isinstance
from functools import lru_cache from functools import lru_cache
import logging import logging
from typing import List, Text, Union, Callable, Iterable, Dict from typing import List, Text, Union, Callable, Iterable, Dict
from collections import OrderedDict from collections import OrderedDict
import inspect import inspect
import bisect
import pandas as pd import pandas as pd
import numpy as np 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 ..utils.resam import resam_ts_data, ts_data_last
from ..log import get_module_logger from ..log import get_module_logger
from ..utils.time import if_single_data from ..utils.time import is_single_value
class BaseQuote: class BaseQuote:
@@ -39,10 +37,10 @@ class BaseQuote:
stock_id: str, stock_id: str,
start_time: Union[pd.Timestamp, str], start_time: Union[pd.Timestamp, str],
end_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, method: Union[str, Callable, None] = None,
) -> Union[None, Union[int, float, bool], "IndexData"]: ) -> Union[None, int, float, bool, "IndexData"]:
"""get the specific fields of stock data during start time and end_time, """get the specific field of stock data during start time and end_time,
and apply method to the data. and apply method to the data.
Example: Example:
@@ -63,22 +61,13 @@ class BaseQuote:
this function is used for three case: this function is used for three case:
1. Both fields and method are not None. It returns int/float/bool. 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", fields="$close", method="last")) print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", field="$close", method="last"))
85.713585 85.713585
2. Both fields and method are None. It returns np.ndarray. 2. method is None. It returns IndexData.
print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", fields=None, method=None)) print(get_data(stock_id="SH600000", start_time="2010-01-04", end_time="2010-01-06", field="$close", 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))
IndexData([86.778313, 87.433578, 85.713585], [2010-01-04, 2010-01-05, 2010-01-06]) 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 closed start time for backtest
end_time : Union[pd.Timestamp, str] end_time : Union[pd.Timestamp, str]
closed end time for backtest closed end time for backtest
fields : Union[str, None] field : str
the columns of data to fetch the columns of data to fetch
method : Union[str, Callable, None] method : Union[str, Callable, None]
the method apply to data. the method apply to data.
@@ -97,7 +86,8 @@ class BaseQuote:
Return 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. please refer to Example as following.
""" """
@@ -115,32 +105,21 @@ class PandasQuote(BaseQuote):
def get_all_stock(self): def get_all_stock(self):
return self.data.keys() return self.data.keys()
def get_data(self, stock_id, start_time, end_time, fields=None, method=None): def get_data(self, stock_id, start_time, end_time, field, method=None):
if fields is None and method is not None: stock_data = resam_ts_data(self.data[stock_id][field], start_time, end_time, method=method)
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")
if stock_data is None: if stock_data is None:
return 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 return stock_data
elif isinstance(stock_data, pd.Series): elif isinstance(stock_data, pd.Series):
return IndexData.Series(stock_data) return IndexData.Series(stock_data)
elif isinstance(stock_data, pd.DataFrame):
return stock_data.values
else: else:
raise ValueError(f"stock data from resam_ts_data must be a number, pd.Series or pd.DataFrame") 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): def __init__(self, quote_df: pd.DataFrame):
"""CN1min_NumpyQuote """CN1minNumpyQuote
Parameters Parameters
---------- ----------
@@ -153,48 +132,37 @@ class CN1min_NumpyQuote(BaseQuote):
for stock_id, stock_val in quote_df.groupby(level="instrument"): for stock_id, stock_val in quote_df.groupby(level="instrument"):
quote_dict[stock_id] = IndexData.DataFrame(stock_val.droplevel(level="instrument")) quote_dict[stock_id] = IndexData.DataFrame(stock_val.droplevel(level="instrument"))
self.data = quote_dict self.data = quote_dict
self.freq = np.timedelta64(1, "m") self.freq = pd.Timedelta(minutes=1)
def get_all_stock(self): def get_all_stock(self):
return self.data.keys() return self.data.keys()
@lru_cache(maxsize=512) @lru_cache(maxsize=512)
def get_data(self, stock_id, start_time, end_time, fields=None, method=None): def get_data(self, stock_id, start_time, end_time, field, method=None):
if fields is None and method is not None:
raise ValueError(f"method must be None when fields is None")
# check stock id # check stock id
if stock_id not in self.get_all_stock(): if stock_id not in self.get_all_stock():
return None return None
# single data # single data
# If it don't consider the classification of single data, it will consume a lot of time. # 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_index_map = self.data[stock_id].index_map
now_columns_map = self.data[stock_id].columns_map now_columns_map = self.data[stock_id].columns_map
if start_time not in now_index_map: if start_time not in now_index_map:
return None return None
if fields is None:
return self.data[stock_id].values[now_index_map[start_time]]
else: 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 # multi data
else: else:
if fields is None and method is None: if method is None:
stock_data = self.data[stock_id].loc(start_time, end_time) stock_data = self.data[stock_id].loc(start_time, end_time, field)
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 stock_data.empty: if stock_data.empty:
return None return None
else: else:
return stock_data return stock_data
elif fields is not None and method is not None: else:
stock_data = self.data[stock_id].loc(start_time, end_time, fields) stock_data = self.data[stock_id].loc(start_time, end_time, field)
if stock_data.empty: if stock_data.empty:
return None return None
elif len(stock_data) == 1: elif len(stock_data) == 1:
@@ -231,6 +199,20 @@ class BaseSingleMetric:
""" """
def __init__(self, metric: Union[dict, pd.Series]): 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") raise NotImplementedError(f"Please implement the `__init__` method")
def __add__(self, other: Union["BaseSingleMetric", int, float]) -> "BaseSingleMetric": def __add__(self, other: Union["BaseSingleMetric", int, float]) -> "BaseSingleMetric":
@@ -277,7 +259,7 @@ class BaseSingleMetric:
def abs(self) -> "BaseSingleMetric": def abs(self) -> "BaseSingleMetric":
raise NotImplementedError(f"Please implement the `abs` method") 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") raise NotImplementedError(f"Please implement the `astype` method")
@property @property
@@ -316,7 +298,8 @@ class BaseOrderIndicator:
to inherit the BaseSingleMetric. to inherit the BaseSingleMetric.
""" """
def __init__(self): def __init__(self, data):
self.data = data
self.logger = get_module_logger("online operator") self.logger = get_module_logger("online operator")
def assign(self, col: str, metric: Union[dict, pd.Series]): def assign(self, col: str, metric: Union[dict, pd.Series]):
@@ -358,8 +341,13 @@ class BaseOrderIndicator:
BaseSingleMetric BaseSingleMetric
new metric. new metric.
""" """
func_sig = inspect.signature(func).parameters.keys()
raise NotImplementedError(f"Please implement the 'transfer' method") 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: def get_metric_series(self, metric: str) -> pd.Series:
"""return the single metric with pd.Series format. """return the single metric with pd.Series format.
@@ -378,8 +366,8 @@ class BaseOrderIndicator:
raise NotImplementedError(f"Please implement the 'get_metric_series' method") raise NotImplementedError(f"Please implement the 'get_metric_series' method")
def get_index_data(self, metric) -> IndexData.Series: def get_index_data(self, metric) -> SingleData:
"""get one metric with the format of IndexData.Series """get one metric with the format of SingleData
Parameters Parameters
---------- ----------
@@ -389,7 +377,7 @@ class BaseOrderIndicator:
Return Return
------ ------
IndexData.Series 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") raise NotImplementedError(f"Please implement the 'get_index_data' method")
@@ -431,6 +419,9 @@ class BaseOrderIndicator:
class SingleMetric(BaseSingleMetric): class SingleMetric(BaseSingleMetric):
def __init__(self, metric):
self.metric = metric
def __add__(self, other): def __add__(self, other):
if isinstance(other, (int, float)): if isinstance(other, (int, float)):
return self.__class__(self.metric + other) return self.__class__(self.metric + other)
@@ -502,7 +493,7 @@ class SingleMetric(BaseSingleMetric):
class PandasSingleMetric(SingleMetric): class PandasSingleMetric(SingleMetric):
"""Each SingleMetric is based on pd.Series.""" """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): if isinstance(metric, dict):
self.metric = pd.Series(metric) self.metric = pd.Series(metric)
elif isinstance(metric, pd.Series): elif isinstance(metric, pd.Series):
@@ -522,13 +513,17 @@ class PandasSingleMetric(SingleMetric):
def abs(self): def abs(self):
return self.__class__(self.metric.abs()) return self.__class__(self.metric.abs())
def astype(self, type): def astype(self, dtype):
return self.__class__(self.metric.astype(type)) return self.__class__(self.metric.astype(dtype))
@property @property
def empty(self): def empty(self):
return self.metric.empty return self.metric.empty
@property
def index(self):
return list(self.metric.index)
def add(self, other, fill_value=None): def add(self, other, fill_value=None):
return self.__class__(self.metric.add(other.metric, fill_value=fill_value)) return self.__class__(self.metric.add(other.metric, fill_value=fill_value))
@@ -538,6 +533,9 @@ class PandasSingleMetric(SingleMetric):
def apply(self, func: Callable): def apply(self, func: Callable):
return self.__class__(self.metric.apply(func)) 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): class PandasOrderIndicator(BaseOrderIndicator):
""" """
@@ -552,15 +550,6 @@ class PandasOrderIndicator(BaseOrderIndicator):
def assign(self, col: str, metric: Union[dict, pd.Series]): def assign(self, col: str, metric: Union[dict, pd.Series]):
self.data[col] = PandasSingleMetric(metric) 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): def get_index_data(self, metric):
if metric in self.data: if metric in self.data:
return IndexData.Series(self.data[metric].metric) 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()} return {k: v.metric for k, v in self.data.items()}
@staticmethod @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): if isinstance(metrics, str):
metrics = [metrics] metrics = [metrics]
for metric in metrics: for metric in metrics:
@@ -589,26 +578,17 @@ class PandasOrderIndicator(BaseOrderIndicator):
class NumpyOrderIndicator(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. Each IndexData.Series is one metric.
Str is the name of metric. Str is the name of metric.
""" """
def __init__(self): def __init__(self):
self.data: Dict[str, IndexData.Series] = OrderedDict() self.data: Dict[str, SingleData] = OrderedDict()
def assign(self, col: str, metric: dict): def assign(self, col: str, metric: dict):
self.data[col] = IndexData.Series(metric) 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): def get_index_data(self, metric):
if metric in self.data: if metric in self.data:
return self.data[metric] return self.data[metric]
@@ -616,7 +596,7 @@ class NumpyOrderIndicator(BaseOrderIndicator):
return IndexData.Series() return IndexData.Series()
def get_metric_series(self, metric: str) -> Union[pd.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]: def to_series(self) -> Dict[str, pd.Series]:
tmp_metric_dict = {} tmp_metric_dict = {}

View File

@@ -109,7 +109,7 @@ class Order:
return self.direction * 2 - 1 return self.direction * 2 - 1
@staticmethod @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): if isinstance(direction, OrderDir):
return direction return direction
elif isinstance(direction, (int, float, np.integer, np.floating)): elif isinstance(direction, (int, float, np.integer, np.floating)):

View File

@@ -4,15 +4,14 @@
from collections import OrderedDict from collections import OrderedDict
import pathlib import pathlib
from typing import Dict, List, Tuple from typing import Dict, List, Tuple, Union
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from qlib.backtest.exchange import Exchange from qlib.backtest.exchange import Exchange
from qlib.backtest.order import BaseTradeDecision, Order, OrderDir from qlib.backtest.order import BaseTradeDecision, Order, OrderDir
from .high_performance_ds import PandasOrderIndicator, NumpyOrderIndicator, SingleMetric
from .high_performance_ds import PandasOrderIndicator, NumpyOrderIndicator
from ..utils.index_data import IndexData, SingleData from ..utils.index_data import IndexData, SingleData
from ..tests.config import CSI300_BENCH from ..tests.config import CSI300_BENCH
from ..utils.resam import get_higher_eq_freq_feature, resam_ts_data 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 _update_order_fulfill_rate(self):
def func(deal_amount, amount): def func(deal_amount, amount):
# deal_amount is np.NaN when there is no inner decision. So full fill rate is 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.replace({np.NaN: 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 return tmp_deal_amount / amount
self.order_indicator.transfer(func, "ffr") self.order_indicator.transfer(func, "ffr")
@@ -385,7 +385,7 @@ class Indicator:
if price_s is None: if price_s is None:
return None, 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]) price_s = IndexData.Series(price_s, [trade_start_time])
elif isinstance(price_s, SingleData): elif isinstance(price_s, SingleData):
pass pass
@@ -400,7 +400,7 @@ class Indicator:
if agg == "vwap": if agg == "vwap":
volume_s = trade_exchange.get_volume(inst, trade_start_time, trade_end_time, method=None) 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 = IndexData.Series(volume_s, [trade_start_time])
volume_s = volume_s.reindex(price_s.index) volume_s = volume_s.reindex(price_s.index)
elif agg == "twap": elif agg == "twap":
@@ -414,7 +414,7 @@ class Indicator:
def _agg_base_price( def _agg_base_price(
self, 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]], decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]],
trade_exchange: Exchange, trade_exchange: Exchange,
pa_config: dict = {}, pa_config: dict = {},

View File

@@ -35,7 +35,7 @@ class IndexData:
return MultiData(data, index, columns) return MultiData(data, index, columns)
@staticmethod @staticmethod
def concat(data_list, axis=0): def concat(data_list: Union["SingleData"], axis=0) -> "MultiData":
"""concat all SingleData by index. """concat all SingleData by index.
TODO: now just for SingleData. TODO: now just for SingleData.
@@ -50,7 +50,7 @@ class IndexData:
the MultiData with ndim == 2 the MultiData with ndim == 2
""" """
if axis == 0: 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: elif axis == 1:
# get all index and row # get all index and row
all_index = set() all_index = set()
@@ -90,7 +90,7 @@ class BaseData:
raise NotImplementedError(f"please implement _align_index func") raise NotImplementedError(f"please implement _align_index func")
def __add__(self, other): 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) return self.__class__(self.data + other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -99,7 +99,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __sub__(self, other): 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) return self.__class__(self.data - other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -108,7 +108,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __rsub__(self, other): 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) return self.__class__(other - self.data, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -117,7 +117,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __mul__(self, other): 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) return self.__class__(self.data * other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -126,7 +126,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __truediv__(self, other): 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) return self.__class__(self.data / other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -135,7 +135,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __eq__(self, other): 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) return self.__class__(self.data == other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -144,7 +144,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __gt__(self, other): 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) return self.__class__(self.data > other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -153,7 +153,7 @@ class BaseData:
return NotImplemented return NotImplemented
def __lt__(self, other): 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) return self.__class__(self.data < other, *self.index_columns)
elif isinstance(other, self.__class__): elif isinstance(other, self.__class__):
tmp_data1, tmp_data2 = self._align_index(other) tmp_data1, tmp_data2 = self._align_index(other)
@@ -169,9 +169,9 @@ class BaseData:
tmp_data = np.absolute(self.data) tmp_data = np.absolute(self.data)
return self.__class__(tmp_data, *self.index_columns) return self.__class__(tmp_data, *self.index_columns)
def astype(self, type): def astype(self, dtype):
"""change the type of data.""" """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) return self.__class__(tmp_data, *self.index_columns)
def replace(self, to_replace: dict): def replace(self, to_replace: dict):
@@ -234,7 +234,7 @@ class BaseData:
class SingleData(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. """A data structure of index and numpy data.
It's used to replace pd.Series due to high-speed. It's used to replace pd.Series due to high-speed.
@@ -301,6 +301,8 @@ class SingleData(BaseData):
SingleData SingleData
reindex data reindex data
""" """
if self.index == index:
return self
tmp_data = np.full(len(index), fill_value, dtype=np.float64) tmp_data = np.full(len(index), fill_value, dtype=np.float64)
for index_id, index_item in enumerate(index): for index_id, index_item in enumerate(index):
if index_item in self.index: if index_item in self.index:
@@ -323,17 +325,7 @@ class SingleData(BaseData):
""" """
return dict(zip(self.index, self.data.tolist())) return dict(zip(self.index, self.data.tolist()))
def to_frame(self): def to_series(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):
return pd.Series(self.data, index=self.index) return pd.Series(self.data, index=self.index)
def __getitem__(self, index: Union["SingleData", int, str]): def __getitem__(self, index: Union["SingleData", int, str]):

View File

@@ -38,8 +38,8 @@ def get_min_cal(shift: int = 0) -> List[time]:
return cal return cal
def if_single_data(start_time, end_time, freq): def is_single_value(start_time, end_time, freq):
"""Is there only one piece of data to obtain. """Is there only one piece of data for cn stock market.
Parameters Parameters
---------- ----------