mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-13 15:56:57 +08:00
Print volume limitation log
This commit is contained in:
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qlib.strategy.base import BaseStrategy
|
from qlib.strategy.base import BaseStrategy
|
||||||
from qlib.backtest.executor import BaseExecutor
|
from qlib.backtest.executor import BaseExecutor
|
||||||
from ..utils.time import Freq
|
from ..utils.time import Freq
|
||||||
from tqdm.auto import tqdm
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
|||||||
@@ -137,9 +137,10 @@ class Exchange:
|
|||||||
if deal_price is None:
|
if deal_price is None:
|
||||||
deal_price = C.deal_price
|
deal_price = C.deal_price
|
||||||
|
|
||||||
self.logger = get_module_logger("online operator", level=logging.INFO)
|
# we have some verbose information here. So logging is enable
|
||||||
|
self.logger = get_module_logger("online operator")
|
||||||
|
|
||||||
# TODO: the quote, trade_dates, codes are not necessray.
|
# TODO: the quote, trade_dates, codes are not necessary.
|
||||||
# It is just for performance consideration.
|
# It is just for performance consideration.
|
||||||
self.limit_type = self._get_limit_type(limit_threshold)
|
self.limit_type = self._get_limit_type(limit_threshold)
|
||||||
if limit_threshold is None:
|
if limit_threshold is None:
|
||||||
@@ -387,6 +388,7 @@ class Exchange:
|
|||||||
if self.check_order(order) is False:
|
if self.check_order(order) is False:
|
||||||
order.deal_amount = 0.0
|
order.deal_amount = 0.0
|
||||||
# using np.nan instead of None to make it more convenient to should the value in format string
|
# using np.nan instead of None to make it more convenient to should the value in format string
|
||||||
|
self.logger.debug(f"Order failed due to trading limitation: {order}")
|
||||||
return 0.0, 0.0, np.nan
|
return 0.0, 0.0, np.nan
|
||||||
|
|
||||||
if trade_account is not None and position is not None:
|
if trade_account is not None and position is not None:
|
||||||
@@ -659,20 +661,19 @@ class Exchange:
|
|||||||
return (deal_amount * factor + 0.1) // self.trade_unit * self.trade_unit / factor
|
return (deal_amount * factor + 0.1) // self.trade_unit * self.trade_unit / factor
|
||||||
return deal_amount
|
return deal_amount
|
||||||
|
|
||||||
def _get_amount_by_volume(self, order: Order, dealt_order_amount: dict) -> int:
|
def _clip_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.
|
"""parse the capacity limit string and return the actual amount of orders that can be executed.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
this function will change the order.deal_amount **inplace**
|
||||||
|
- This will make the order info more accurate
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
order : Order
|
order : Order
|
||||||
the order to be executed.
|
the order to be executed.
|
||||||
dealt_order_amount : dict
|
dealt_order_amount : dict
|
||||||
:param dealt_order_amount: the dealt 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}
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
int
|
|
||||||
the actual amount of orders that can be executed, due to the volume limit.
|
|
||||||
"""
|
"""
|
||||||
if order.direction == Order.BUY:
|
if order.direction == Order.BUY:
|
||||||
vol_limit = self.buy_vol_limit
|
vol_limit = self.buy_vol_limit
|
||||||
@@ -685,21 +686,33 @@ class Exchange:
|
|||||||
vol_limit_num = []
|
vol_limit_num = []
|
||||||
for limit in vol_limit:
|
for limit in vol_limit:
|
||||||
assert isinstance(limit, tuple)
|
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":
|
if limit[0] == "current":
|
||||||
|
limit_value = self.quote.get_data(
|
||||||
|
order.stock_id,
|
||||||
|
order.start_time,
|
||||||
|
order.end_time,
|
||||||
|
fields=limit[1],
|
||||||
|
method="sum",
|
||||||
|
)
|
||||||
vol_limit_num.append(limit_value)
|
vol_limit_num.append(limit_value)
|
||||||
elif limit[0] == "cum":
|
elif limit[0] == "cum":
|
||||||
|
limit_value = 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 - dealt_order_amount[order.stock_id])
|
vol_limit_num.append(limit_value - dealt_order_amount[order.stock_id])
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"{limit[0]} is not supported")
|
raise ValueError(f"{limit[0]} is not supported")
|
||||||
vol_limit_num = min(vol_limit_num)
|
vol_limit_min = min(vol_limit_num)
|
||||||
return max(min(vol_limit_num, order.deal_amount), 0)
|
orig_deal_amount = order.deal_amount
|
||||||
|
order.deal_amount = max(min(vol_limit_min, orig_deal_amount), 0)
|
||||||
|
if vol_limit_min < orig_deal_amount:
|
||||||
|
self.logger.debug(
|
||||||
|
f"Order clipped due to volume limitation: {order}, {[(vol, rule) for vol, rule in zip(vol_limit_num, vol_limit)]}"
|
||||||
|
)
|
||||||
|
|
||||||
def _calc_trade_info_by_order(self, order, position: Position, dealt_order_amount):
|
def _calc_trade_info_by_order(self, order, position: Position, dealt_order_amount):
|
||||||
"""
|
"""
|
||||||
@@ -733,7 +746,7 @@ class Exchange:
|
|||||||
# We choose to sell all
|
# We choose to sell all
|
||||||
order.deal_amount = order.amount
|
order.deal_amount = order.amount
|
||||||
|
|
||||||
order.deal_amount = self._get_amount_by_volume(order, dealt_order_amount)
|
self._clip_amount_by_volume(order, dealt_order_amount)
|
||||||
trade_val = order.deal_amount * trade_price
|
trade_val = order.deal_amount * trade_price
|
||||||
trade_cost = max(trade_val * self.close_cost, self.min_cost)
|
trade_cost = max(trade_val * self.close_cost, self.min_cost)
|
||||||
elif order.direction == Order.BUY:
|
elif order.direction == Order.BUY:
|
||||||
@@ -746,6 +759,7 @@ class Exchange:
|
|||||||
order.deal_amount = self.round_amount_by_trade_unit(
|
order.deal_amount = self.round_amount_by_trade_unit(
|
||||||
cash / (1 + self.open_cost) / trade_price, order.factor
|
cash / (1 + self.open_cost) / trade_price, order.factor
|
||||||
)
|
)
|
||||||
|
self.logger.debug(f"Order clipped due to cash limitation: {order}")
|
||||||
else:
|
else:
|
||||||
# THe money is enough
|
# THe money is enough
|
||||||
order.deal_amount = self.round_amount_by_trade_unit(order.amount, order.factor)
|
order.deal_amount = self.round_amount_by_trade_unit(order.amount, order.factor)
|
||||||
@@ -753,7 +767,7 @@ class Exchange:
|
|||||||
# Unknown amount of money. Just round the amount
|
# 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.round_amount_by_trade_unit(order.amount, order.factor)
|
||||||
|
|
||||||
order.deal_amount = self._get_amount_by_volume(order, dealt_order_amount)
|
self._clip_amount_by_volume(order, dealt_order_amount)
|
||||||
trade_val = order.deal_amount * trade_price
|
trade_val = order.deal_amount * trade_price
|
||||||
trade_cost = max(trade_val * self.open_cost, self.min_cost)
|
trade_cost = max(trade_val * self.open_cost, self.min_cost)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -273,7 +273,24 @@ class QlibConfig(Config):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"This type of uri is not supported")
|
raise NotImplementedError(f"This type of uri is not supported")
|
||||||
|
|
||||||
def set(self, default_conf="client", **kwargs):
|
def set(self, default_conf: str = "client", **kwargs):
|
||||||
|
"""
|
||||||
|
configure qlib based on the input parameters
|
||||||
|
|
||||||
|
The configure will act like a dictionary.
|
||||||
|
|
||||||
|
Normally, it literally replace the value according to the keys.
|
||||||
|
However, sometimes it is hard for users to set the config when the configure is nested and complicated
|
||||||
|
|
||||||
|
So this API provides some special parameters for users to set the keys in a more convenient way.
|
||||||
|
- region: REG_CN, REG_US
|
||||||
|
- several region-related config will be changed
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
default_conf : str
|
||||||
|
the default config template chosen by user: "server", "client"
|
||||||
|
"""
|
||||||
from .utils import set_log_with_config, get_module_logger, can_use_cache
|
from .utils import set_log_with_config, get_module_logger, can_use_cache
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|||||||
@@ -97,10 +97,16 @@ class Freq:
|
|||||||
return _count, _freq_format_dict[_freq]
|
return _count, _freq_format_dict[_freq]
|
||||||
|
|
||||||
|
|
||||||
cn_time = [datetime.strptime("9:30", "%H:%M"), datetime.strptime("11:30", "%H:%M"),
|
cn_time = [
|
||||||
datetime.strptime("13:00", "%H:%M"), datetime.strptime("15:00", "%H:%M")]
|
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")]
|
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"):
|
|
||||||
|
|
||||||
|
def time_to_day_index(time_obj: Union[str, datetime], region: str = "cn"):
|
||||||
if isinstance(time_obj, str):
|
if isinstance(time_obj, str):
|
||||||
time_obj = datetime.strptime(time_obj, "%H:%M")
|
time_obj = datetime.strptime(time_obj, "%H:%M")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user