mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-03 02:50:58 +08:00
fix vol limit bug
This commit is contained in:
@@ -84,7 +84,7 @@ class Exchange:
|
||||
such as DayCumsum. !!!NOTE: if you want you use the custom operator, you need to
|
||||
register it in qlib_init.
|
||||
- "cum" means that this is a cumulative value over time, such as cumulative market volume.
|
||||
So when it is used as a volume limit, it is necessary to subtract the dealed amount.
|
||||
So when it is used as a volume limit, it is necessary to subtract the dealt amount.
|
||||
- "current" means that this is a real-time value and will not accumulate over time,
|
||||
so it can be directly used as a capacity limit.
|
||||
e.g. ("cum", "0.2 * DayCumsum($volume, '9:45', '14:45')"), ("current", "$bidV1")
|
||||
@@ -304,10 +304,10 @@ class Exchange:
|
||||
if isinstance(volume_threshold, tuple):
|
||||
volume_threshold = {"all": volume_threshold}
|
||||
|
||||
assert type(volume_threshold) == dict
|
||||
assert isinstance(volume_threshold, dict)
|
||||
for key in volume_threshold:
|
||||
vol_limit = volume_threshold[key]
|
||||
assert type(vol_limit) == tuple
|
||||
assert isinstance(vol_limit, tuple)
|
||||
fields.add(vol_limit[1])
|
||||
|
||||
if key in ("buy", "all"):
|
||||
@@ -370,7 +370,7 @@ class Exchange:
|
||||
order,
|
||||
trade_account: Account = None,
|
||||
position: BasePosition = None,
|
||||
dealed_order_amount: defaultdict = defaultdict(float),
|
||||
dealt_order_amount: defaultdict = defaultdict(float),
|
||||
):
|
||||
"""
|
||||
Deal order when the actual transaction
|
||||
@@ -380,7 +380,7 @@ class Exchange:
|
||||
:param order: Deal the order.
|
||||
:param trade_account: Trade account to be updated after dealing the order.
|
||||
:param position: position to be updated after dealing the order.
|
||||
:param dealed_order_amount: the dealed order amount dict with the format of {stock_id: float}
|
||||
:param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
|
||||
:return: trade_val, trade_cost, trade_price
|
||||
"""
|
||||
# check order first.
|
||||
@@ -395,7 +395,7 @@ class Exchange:
|
||||
trade_price = self.get_deal_price(order.stock_id, order.start_time, order.end_time, order.direction)
|
||||
# NOTE: order will be changed in this function
|
||||
trade_val, trade_cost = self._calc_trade_info_by_order(
|
||||
order, trade_account.current if trade_account else position, dealed_order_amount
|
||||
order, trade_account.current if trade_account else position, dealt_order_amount
|
||||
)
|
||||
if order.deal_amount > 1e-5:
|
||||
# If the order can only be deal 0 amount. Nothing to be updated
|
||||
@@ -659,15 +659,15 @@ class Exchange:
|
||||
return (deal_amount * factor + 0.1) // self.trade_unit * self.trade_unit / factor
|
||||
return deal_amount
|
||||
|
||||
def _get_amount_by_volume(self, order: Order, dealed_order_amount: dict) -> int:
|
||||
def _get_amount_by_volume(self, order: Order, dealt_order_amount: dict) -> int:
|
||||
"""parse the capacity limit string and return the actual amount of orders that can be executed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order : Order
|
||||
the order to be executed.
|
||||
dealed_order_amount : dict
|
||||
:param dealed_order_amount: the dealed order amount dict with the format of {stock_id: float}
|
||||
dealt_order_amount : dict
|
||||
:param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -685,33 +685,23 @@ class Exchange:
|
||||
vol_limit_num = []
|
||||
for limit in vol_limit:
|
||||
assert isinstance(limit, tuple)
|
||||
limit_value = self.quote.get_data(
|
||||
order.stock_id,
|
||||
order.start_time,
|
||||
order.end_time,
|
||||
fields=limit[1],
|
||||
method=ts_data_last,
|
||||
)
|
||||
if limit[0] == "current":
|
||||
vol_limit_num.append(
|
||||
self.quote.get_data(
|
||||
order.stock_id,
|
||||
order.start_time,
|
||||
order.end_time,
|
||||
fields=limit[1],
|
||||
method=ts_data_last,
|
||||
)
|
||||
)
|
||||
vol_limit_num.append(limit_value)
|
||||
elif limit[0] == "cum":
|
||||
vol_limit_num.append(
|
||||
self.quote.get_data(
|
||||
order.stock_id,
|
||||
order.start_time,
|
||||
order.end_time,
|
||||
fields=limit[1],
|
||||
method=ts_data_last,
|
||||
)
|
||||
- dealed_order_amount[order.stock_id]
|
||||
)
|
||||
vol_limit_num.append(limit_value - dealt_order_amount[order.stock_id])
|
||||
else:
|
||||
raise ValueError(f"{limit[0]} is not supported")
|
||||
vol_limit_num = min(vol_limit_num)
|
||||
return max(min(vol_limit_num, order.deal_amount), 0)
|
||||
|
||||
def _calc_trade_info_by_order(self, order, position: Position, dealed_order_amount):
|
||||
def _calc_trade_info_by_order(self, order, position: Position, dealt_order_amount):
|
||||
"""
|
||||
Calculation of trade info
|
||||
|
||||
@@ -719,7 +709,7 @@ class Exchange:
|
||||
|
||||
:param order:
|
||||
:param position: Position
|
||||
:param dealed_order_amount: the dealed order amount dict with the format of {stock_id: float}
|
||||
:param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
|
||||
:return: trade_val, trade_cost
|
||||
"""
|
||||
|
||||
@@ -743,7 +733,7 @@ class Exchange:
|
||||
# We choose to sell all
|
||||
order.deal_amount = order.amount
|
||||
|
||||
order.deal_amount = self._get_amount_by_volume(order, dealed_order_amount)
|
||||
order.deal_amount = self._get_amount_by_volume(order, dealt_order_amount)
|
||||
trade_val = order.deal_amount * trade_price
|
||||
trade_cost = max(trade_val * self.close_cost, self.min_cost)
|
||||
elif order.direction == Order.BUY:
|
||||
@@ -763,7 +753,7 @@ class Exchange:
|
||||
# Unknown amount of money. Just round the amount
|
||||
order.deal_amount = self.round_amount_by_trade_unit(order.amount, order.factor)
|
||||
|
||||
order.deal_amount = self._get_amount_by_volume(order, dealed_order_amount)
|
||||
order.deal_amount = self._get_amount_by_volume(order, dealt_order_amount)
|
||||
trade_val = order.deal_amount * trade_price
|
||||
trade_cost = max(trade_val * self.open_cost, self.min_cost)
|
||||
else:
|
||||
|
||||
@@ -115,7 +115,7 @@ class BaseExecutor:
|
||||
get_module_logger("BaseExecutor").warning(f"`common_infra` is not set for {self}")
|
||||
|
||||
# record deal order amount in one day
|
||||
self.dealed_order_amount = defaultdict(float)
|
||||
self.dealt_order_amount = defaultdict(float)
|
||||
self.deal_day = None
|
||||
|
||||
def reset_common_infra(self, common_infra):
|
||||
@@ -500,14 +500,14 @@ class SimulatorExecutor(BaseExecutor):
|
||||
raise NotImplementedError(f"This type of input is not supported")
|
||||
return order_it
|
||||
|
||||
def _update_dealed_order_amount(self, order):
|
||||
"""update date and dealed order amount in the day."""
|
||||
def _update_dealt_order_amount(self, order):
|
||||
"""update date and dealt order amount in the day."""
|
||||
|
||||
now_deal_day = self.trade_calendar.get_step_time()[0].floor(freq="D")
|
||||
if self.deal_day is None or now_deal_day > self.deal_day:
|
||||
self.dealed_order_amount = defaultdict(float)
|
||||
self.dealt_order_amount = defaultdict(float)
|
||||
self.deal_day = now_deal_day
|
||||
self.dealed_order_amount[order.stock_id] += order.deal_amount
|
||||
self.dealt_order_amount[order.stock_id] += order.deal_amount
|
||||
|
||||
def _collect_data(self, trade_decision: BaseTradeDecision, level: int = 0):
|
||||
|
||||
@@ -520,10 +520,10 @@ class SimulatorExecutor(BaseExecutor):
|
||||
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(
|
||||
order,
|
||||
trade_account=self.trade_account,
|
||||
dealed_order_amount=self.dealed_order_amount,
|
||||
dealt_order_amount=self.dealt_order_amount,
|
||||
)
|
||||
execute_result.append((order, trade_val, trade_cost, trade_price))
|
||||
self._update_dealed_order_amount(order)
|
||||
self._update_dealt_order_amount(order)
|
||||
if self.verbose:
|
||||
print(
|
||||
"[I {:%Y-%m-%d %H:%M:%S}]: {} {}, price {:.2f}, amount {}, deal_amount {}, factor {}, value {:.2f}, cash {:.2f}.".format(
|
||||
|
||||
@@ -10,6 +10,7 @@ from qlib.data import D
|
||||
from qlib.data.cache import H
|
||||
from qlib.data.data import Cal
|
||||
from qlib.data.ops import ElemOperator
|
||||
from qlib.utils.time import time_to_day_index
|
||||
|
||||
|
||||
def get_calendar_day(freq="1min", future=False):
|
||||
@@ -71,18 +72,11 @@ class DayCumsum(ElemOperator):
|
||||
self.noon_open = datetime.strptime("13:00", "%H:%M")
|
||||
self.noon_close = datetime.strptime("15:00", "%H:%M")
|
||||
|
||||
self.start_id = self.time_to_index(self.start)
|
||||
self.end_id = self.time_to_index(self.end)
|
||||
|
||||
def time_to_index(self, t):
|
||||
if t >= self.morning_open and t < self.morning_close:
|
||||
return int((t - self.morning_open).total_seconds() / 60)
|
||||
elif t >= self.noon_open and t < self.noon_close:
|
||||
return int((t - self.noon_open).total_seconds() / 60) + 120
|
||||
else:
|
||||
raise ValueError(f"{t} is not the opening time of the stock market")
|
||||
self.start_id = time_to_day_index(self.start)
|
||||
self.end_id = time_to_day_index(self.end)
|
||||
|
||||
def period_cusum(self, df):
|
||||
df = df.copy()
|
||||
assert len(df) == 240
|
||||
df.iloc[0 : self.start_id] = 0
|
||||
df = df.cumsum()
|
||||
|
||||
@@ -11,6 +11,7 @@ from numpy import append
|
||||
import pandas as pd
|
||||
from qlib.config import C
|
||||
import functools
|
||||
from typing import Union
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=240)
|
||||
@@ -96,6 +97,29 @@ class Freq:
|
||||
return _count, _freq_format_dict[_freq]
|
||||
|
||||
|
||||
cn_time = [datetime.strptime("9:30", "%H:%M"), datetime.strptime("11:30", "%H:%M"),
|
||||
datetime.strptime("13:00", "%H:%M"), datetime.strptime("15:00", "%H:%M")]
|
||||
us_time = [datetime.strptime("9:30", "%H:%M"), datetime.strptime("16:00", "%H:%M")]
|
||||
def time_to_day_index(time_obj: Union[str, datetime], region: str="cn"):
|
||||
if isinstance(time_obj, str):
|
||||
time_obj = datetime.strptime(time_obj, "%H:%M")
|
||||
|
||||
if region == "cn":
|
||||
if time_obj >= cn_time[0] and time_obj < cn_time[1]:
|
||||
return int((time_obj - cn_time[0]).total_seconds() / 60)
|
||||
elif time_obj >= cn_time[2] and time_obj < cn_time[3]:
|
||||
return int((time_obj - cn_time[2]).total_seconds() / 60) + 120
|
||||
else:
|
||||
raise ValueError(f"{time_obj} is not the opening time of the {region} stock market")
|
||||
elif region == "us":
|
||||
if time_obj >= us_time[0] and time_obj < us_time[1]:
|
||||
return int((time_obj - us_time[0]).total_seconds() / 60)
|
||||
else:
|
||||
raise ValueError(f"{time_obj} is not the opening time of the {region} stock market")
|
||||
else:
|
||||
raise ValueError(f"{region} is not supported")
|
||||
|
||||
|
||||
def get_day_min_idx_range(start: str, end: str, freq: str) -> Tuple[int, int]:
|
||||
"""
|
||||
get the min-bar index in a day for a time range (both left and right is closed) given a fixed frequency
|
||||
|
||||
Reference in New Issue
Block a user