mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-14 16:26:55 +08:00
update exchange
This commit is contained in:
committed by
you-n-g
parent
2da6a8c770
commit
f67b99a30e
@@ -21,7 +21,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
|
from .high_performance_ds import PandasQuote, NumpyQuote
|
||||||
|
|
||||||
|
|
||||||
class Exchange:
|
class Exchange:
|
||||||
@@ -39,7 +39,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=PandasQuote,
|
quote_cls=NumpyQuote,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""__init__
|
"""__init__
|
||||||
@@ -725,9 +725,9 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
max_trade_amount = 0
|
max_trade_amount = 0
|
||||||
if cash >= self.min_cost:
|
if cash >= self.min_cost:
|
||||||
# critical_amount means the stock transaction amount when the service fee is equal to min_cost.
|
# critical_price means the stock transaction price when the service fee is equal to min_cost.
|
||||||
critical_amount = self.min_cost / self.open_cost + self.min_cost
|
critical_price = self.min_cost / self.open_cost + self.min_cost
|
||||||
if cash >= critical_amount:
|
if cash >= critical_price:
|
||||||
# the service fee is equal to open_cost * trade_amount
|
# the service fee is equal to open_cost * trade_amount
|
||||||
max_trade_amount = cash / (1 + self.open_cost) / trade_price
|
max_trade_amount = cash / (1 + self.open_cost) / trade_price
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -3,13 +3,16 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from qlib.data.base import Feature
|
||||||
from typing import List, Text, Tuple, Union, Callable, Iterable, Dict
|
from typing import List, Text, Tuple, 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
|
||||||
|
|
||||||
from ..utils.resam import resam_ts_data
|
from ..utils.resam import resam_ts_data, ts_data_last
|
||||||
from ..log import get_module_logger
|
from ..log import get_module_logger
|
||||||
|
|
||||||
|
|
||||||
@@ -112,6 +115,136 @@ class PandasQuote(BaseQuote):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"fields must be None, str or list")
|
raise ValueError(f"fields must be None, str or list")
|
||||||
|
|
||||||
|
def _if_single_data(self, start_time, end_time):
|
||||||
|
if end_time - start_time < np.timedelta64(1, 'm'):
|
||||||
|
return True
|
||||||
|
if start_time.hour == 11 and start_time.minute == 29 and start_time.second == 0:
|
||||||
|
return True
|
||||||
|
if start_time.hour == 14 and start_time.minute == 59 and start_time.second == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class NumpyQuote(BaseQuote):
|
||||||
|
def __init__(self, quote_df: pd.DataFrame):
|
||||||
|
"""NumpyQuote
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
quote_df : pd.DataFrame
|
||||||
|
the init dataframe from qlib.
|
||||||
|
|
||||||
|
Variables
|
||||||
|
self.data: Dict[stock_id, np.array]
|
||||||
|
each stock has one two-dimensional np.array to represent data.
|
||||||
|
self.columns: Dict[str, int]
|
||||||
|
map column name to column id in self.data.
|
||||||
|
self.dates: Dict[stock_id, Dict[pd.Timestap, int]]
|
||||||
|
map timestap to row id in self.data.
|
||||||
|
self.dates_list: Dict[stock_id, List[pd.Timestap]]
|
||||||
|
the dates of each stock for searching.
|
||||||
|
"""
|
||||||
|
super().__init__(quote_df=quote_df)
|
||||||
|
# init data
|
||||||
|
columns = quote_df.columns.values
|
||||||
|
self.columns = dict(zip(columns, range(len(columns))))
|
||||||
|
self.data, self.dates, self.dates_list = self._to_numpy(quote_df)
|
||||||
|
|
||||||
|
# lru
|
||||||
|
self.muti_lru = {}
|
||||||
|
|
||||||
|
def _to_numpy(self, quote_df):
|
||||||
|
"""convert dataframe to numpy.
|
||||||
|
"""
|
||||||
|
quote_dict = {}
|
||||||
|
date_dict = {}
|
||||||
|
date_list = {}
|
||||||
|
for stock_id, stock_val in quote_df.groupby(level="instrument"):
|
||||||
|
quote_dict[stock_id] = stock_val.values
|
||||||
|
date_dict[stock_id] = stock_val.index.get_level_values("datetime")
|
||||||
|
date_list[stock_id] = list(date_dict[stock_id])
|
||||||
|
for stock_id in date_dict:
|
||||||
|
date_dict[stock_id] = dict(zip(date_dict[stock_id], range(len(date_dict[stock_id]))))
|
||||||
|
return quote_dict, date_dict, date_list
|
||||||
|
|
||||||
|
def get_all_stock(self):
|
||||||
|
return self.data.keys()
|
||||||
|
|
||||||
|
def get_data(self, stock_id, start_time, end_time, fields=None, method=None):
|
||||||
|
# check stock id
|
||||||
|
if stock_id not in self.get_all_stock():
|
||||||
|
return None
|
||||||
|
|
||||||
|
# get single data
|
||||||
|
if self._if_single_data(start_time, end_time):
|
||||||
|
if start_time not in self.dates[stock_id]:
|
||||||
|
return None
|
||||||
|
if fields is None:
|
||||||
|
# it used for check if data is None
|
||||||
|
return self.data[stock_id][self.dates[stock_id][start_time]]
|
||||||
|
else:
|
||||||
|
return self.data[stock_id][self.dates[stock_id][start_time]][self.columns[fields]]
|
||||||
|
# get muti row data
|
||||||
|
else:
|
||||||
|
# check lru
|
||||||
|
if (start_time, end_time, fields, method) in self.muti_lru:
|
||||||
|
return self.muti_lru[(start_time, end_time, fields, method)]
|
||||||
|
|
||||||
|
start_id = bisect.bisect_left(self.dates_list[stock_id], start_time)
|
||||||
|
end_id = bisect.bisect_right(self.dates_list[stock_id], end_time)
|
||||||
|
if start_id == end_id:
|
||||||
|
return None
|
||||||
|
# it used for check if data is None
|
||||||
|
if fields is None:
|
||||||
|
return self.data[stock_id][start_id: end_id]
|
||||||
|
agg_stock_data = self._agg_data(self.data[stock_id][start_id: end_id, self.columns[fields]], method)
|
||||||
|
|
||||||
|
# result lru
|
||||||
|
self.muti_lru[(start_time, end_time, fields, method)] = agg_stock_data
|
||||||
|
return agg_stock_data
|
||||||
|
|
||||||
|
def _agg_data(self, data, method):
|
||||||
|
"""Agg data by specific method.
|
||||||
|
"""
|
||||||
|
if method == "sum":
|
||||||
|
return data.sum()
|
||||||
|
if method == "mean":
|
||||||
|
return data.mean()
|
||||||
|
if method == "last":
|
||||||
|
return data[-1]
|
||||||
|
if method == "all":
|
||||||
|
return data.all()
|
||||||
|
if method == "any":
|
||||||
|
return data.any()
|
||||||
|
if method == ts_data_last:
|
||||||
|
valid_data = data[data != np.NaN]
|
||||||
|
if len(valid_data) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return valid_data[0]
|
||||||
|
|
||||||
|
def _if_single_data(self, start_time, end_time):
|
||||||
|
"""Is there only one piece of data to obtaine.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start_time : Union[pd.Timestamp, str]
|
||||||
|
closed start time for data.
|
||||||
|
end_time : Union[pd.Timestamp, str]
|
||||||
|
closed end time for data.
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True means one piece of data to obtaine.
|
||||||
|
"""
|
||||||
|
if end_time - start_time < np.timedelta64(1, 'm'):
|
||||||
|
return True
|
||||||
|
if start_time.hour == 11 and start_time.minute == 29 and start_time.second == 0:
|
||||||
|
return True
|
||||||
|
if start_time.hour == 14 and start_time.minute == 59 and start_time.second == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class BaseSingleMetric:
|
class BaseSingleMetric:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -389,6 +389,9 @@ class Indicator:
|
|||||||
if price_s is None:
|
if price_s is None:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
if isinstance(price_s, (int, float)):
|
||||||
|
price_s = pd.Series(price_s, index=[trade_start_time])
|
||||||
|
|
||||||
# NOTE: there are some zeros in the trading price. These cases are known meaningless
|
# NOTE: there are some zeros in the trading price. These cases are known meaningless
|
||||||
# for aligning the previous logic, remove it.
|
# for aligning the previous logic, remove it.
|
||||||
price_s = price_s[~(price_s < 1e-08)] # remove zero and negative values.
|
price_s = price_s[~(price_s < 1e-08)] # remove zero and negative values.
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class FileStrTest(TestAutoData):
|
|||||||
# test cash limit for buying
|
# test cash limit for buying
|
||||||
["20200103", self.TEST_INST, "1000", "buy"],
|
["20200103", self.TEST_INST, "1000", "buy"],
|
||||||
# test min_cost for buying
|
# test min_cost for buying
|
||||||
["20200103", self.TEST_INST, "1", "buy"],
|
["20200106", self.TEST_INST, "1", "buy"],
|
||||||
# test held stock limit for selling
|
# test held stock limit for selling
|
||||||
["20200106", self.TEST_INST, "1000", "sell"],
|
["20200107", self.TEST_INST, "1000", "sell"],
|
||||||
# test cash limit for buying
|
# test cash limit for buying
|
||||||
["20200107", self.TEST_INST, "1000", "buy"],
|
["20200108", self.TEST_INST, "1000", "buy"],
|
||||||
# test min_cost for selling
|
# test min_cost for selling
|
||||||
["20200108", self.TEST_INST, "1", "sell"],
|
["20200109", self.TEST_INST, "1", "sell"],
|
||||||
# test selling all stocks
|
# test selling all stocks
|
||||||
["20200110", self.TEST_INST, str(self.DEAL_NUM_FOR_1000), "sell"],
|
["20200110", self.TEST_INST, str(self.DEAL_NUM_FOR_1000), "sell"],
|
||||||
]
|
]
|
||||||
@@ -94,10 +94,11 @@ class FileStrTest(TestAutoData):
|
|||||||
# ffr valid
|
# ffr valid
|
||||||
ffr_dict = indicator_dict["1day"]["ffr"].to_dict()
|
ffr_dict = indicator_dict["1day"]["ffr"].to_dict()
|
||||||
ffr_dict = {str(date).split()[0]: ffr_dict[date] for date in ffr_dict}
|
ffr_dict = {str(date).split()[0]: ffr_dict[date] for date in ffr_dict}
|
||||||
assert ffr_dict["2020-01-03"] == 0
|
assert ffr_dict["2020-01-03"] == self.DEAL_NUM_FOR_1000 / 1000
|
||||||
assert ffr_dict["2020-01-06"] == self.DEAL_NUM_FOR_1000 / 1000
|
assert ffr_dict["2020-01-06"] == 0
|
||||||
assert ffr_dict["2020-01-07"] == self.DEAL_NUM_FOR_1000 / 1000
|
assert ffr_dict["2020-01-07"] == self.DEAL_NUM_FOR_1000 / 1000
|
||||||
assert ffr_dict["2020-01-08"] == 0
|
assert ffr_dict["2020-01-08"] == self.DEAL_NUM_FOR_1000 / 1000
|
||||||
|
assert ffr_dict["2020-01-09"] == 0
|
||||||
assert ffr_dict["2020-01-10"] == 1
|
assert ffr_dict["2020-01-10"] == 1
|
||||||
|
|
||||||
self.EXAMPLE_FILE.unlink()
|
self.EXAMPLE_FILE.unlink()
|
||||||
|
|||||||
Reference in New Issue
Block a user