mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-17 17:34:35 +08:00
fix bug
This commit is contained in:
committed by
you-n-g
parent
7ee4a207bc
commit
43a8f502ed
@@ -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])
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
|||||||
@@ -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)):
|
||||||
|
|||||||
@@ -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 = {},
|
||||||
|
|||||||
@@ -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]):
|
||||||
|
|||||||
@@ -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
|
||||||
----------
|
----------
|
||||||
|
|||||||
Reference in New Issue
Block a user