mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-12 15:26:54 +08:00
Merge branch 'nested_decision_exe' of https://github.com/microsoft/qlib into rl-dummy
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
|
||||||
|
from qlib.backtest.position import Position
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Tuple, Union
|
||||||
@@ -281,6 +282,8 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Deal order when the actual transaction
|
Deal order when the actual transaction
|
||||||
|
|
||||||
|
the results section in `Order` will be changed.
|
||||||
|
|
||||||
:param order: Deal the order.
|
:param order: Deal the order.
|
||||||
:param trade_account: Trade account to be updated after dealing 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 position: position to be updated after dealing the order.
|
||||||
@@ -343,6 +346,7 @@ class Exchange:
|
|||||||
`None`: if the stock is suspended `None` may be returned
|
`None`: if the stock is suspended `None` may be returned
|
||||||
`float`: return factor if the factor exists
|
`float`: return factor if the factor exists
|
||||||
"""
|
"""
|
||||||
|
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:
|
if stock_id not in self.quote:
|
||||||
return None
|
return None
|
||||||
return resam_ts_data(self.quote[stock_id]["$factor"], start_time, end_time, method=ts_data_last)
|
return resam_ts_data(self.quote[stock_id]["$factor"], start_time, end_time, method=ts_data_last)
|
||||||
@@ -505,20 +509,56 @@ class Exchange:
|
|||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_amount_of_trade_unit(self, factor):
|
def _get_factor_or_raise_erorr(self, factor: float = None, stock_id: str = None, start_time=None, end_time=None):
|
||||||
|
"""Please refer to the docs of get_amount_of_trade_unit"""
|
||||||
|
if factor is None:
|
||||||
|
if stock_id is not None and start_time is not None and end_time is not None :
|
||||||
|
factor = self.get_factor(stock_id=stock_id, start_time=start_time, end_time=end_time)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"`factor` and (`stock_id`, `start_time`, `end_time`) can't both be None")
|
||||||
|
return factor
|
||||||
|
|
||||||
|
def get_amount_of_trade_unit(self, factor: float = None, stock_id: str = None, start_time=None, end_time=None):
|
||||||
|
"""
|
||||||
|
get the trade unit of amount based on **factor**
|
||||||
|
|
||||||
|
the factor can be given directly or calculated in given time range and stock id.
|
||||||
|
`factor` has higher priority than `stock_id`, `start_time` and `end_time`
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
factor : float
|
||||||
|
the adjusted factor
|
||||||
|
stock_id : str
|
||||||
|
the id of the stock
|
||||||
|
start_time :
|
||||||
|
the start time of trading range
|
||||||
|
end_time :
|
||||||
|
the end time of trading range
|
||||||
|
"""
|
||||||
if not self.trade_w_adj_price and self.trade_unit is not None:
|
if not self.trade_w_adj_price and self.trade_unit is not None:
|
||||||
|
factor = self._get_factor_or_raise_erorr(factor=factor,
|
||||||
|
stock_id=stock_id,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time)
|
||||||
return self.trade_unit / factor
|
return self.trade_unit / factor
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def round_amount_by_trade_unit(self, deal_amount, factor):
|
def round_amount_by_trade_unit(self, deal_amount, factor: float = None, stock_id: str = None, start_time=None, end_time=None):
|
||||||
"""Parameter
|
"""Parameter
|
||||||
|
Please refer to the docs of get_amount_of_trade_unit
|
||||||
|
|
||||||
deal_amount : float, adjusted amount
|
deal_amount : float, adjusted amount
|
||||||
factor : float, adjusted factor
|
factor : float, adjusted factor
|
||||||
return : float, real amount
|
return : float, real amount
|
||||||
"""
|
"""
|
||||||
if not self.trade_w_adj_price and self.trade_unit is not None:
|
if not self.trade_w_adj_price and self.trade_unit is not None:
|
||||||
# the minimal amount is 1. Add 0.1 for solving precision problem.
|
# the minimal amount is 1. Add 0.1 for solving precision problem.
|
||||||
|
factor = self._get_factor_or_raise_erorr(factor=factor,
|
||||||
|
stock_id=stock_id,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time)
|
||||||
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
|
||||||
|
|
||||||
@@ -529,7 +569,7 @@ class Exchange:
|
|||||||
else:
|
else:
|
||||||
return deal_amount
|
return deal_amount
|
||||||
|
|
||||||
def _calc_trade_info_by_order(self, order, position):
|
def _calc_trade_info_by_order(self, order, position: Position):
|
||||||
"""
|
"""
|
||||||
Calculation of trade info
|
Calculation of trade info
|
||||||
|
|
||||||
@@ -541,6 +581,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
trade_price = self.get_deal_price(order.stock_id, order.start_time, order.end_time, direction=order.direction)
|
trade_price = self.get_deal_price(order.stock_id, order.start_time, order.end_time, direction=order.direction)
|
||||||
|
order.factor = self.get_factor(order.stock_id, order.start_time, order.end_time)
|
||||||
if order.direction == Order.SELL:
|
if order.direction == Order.SELL:
|
||||||
# sell
|
# sell
|
||||||
if position is not None:
|
if position is not None:
|
||||||
|
|||||||
@@ -43,16 +43,24 @@ class Order:
|
|||||||
presents the weight factor assigned in Exchange()
|
presents the weight factor assigned in Exchange()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 1) time invariant values
|
||||||
|
# - they are set by users and is time-invariant.
|
||||||
stock_id: str
|
stock_id: str
|
||||||
amount: float # `amount` is a non-negative value
|
amount: float # `amount` is a non-negative and adjusted value
|
||||||
|
direction: int
|
||||||
|
|
||||||
|
# 2) time variant values:
|
||||||
|
# - Users may want to set these values when using lower level APIs
|
||||||
|
# - If users don't, TradeDecisionWO will help users to set them
|
||||||
# The interval of the order which belongs to (NOTE: this is not the expected order dealing range time)
|
# The interval of the order which belongs to (NOTE: this is not the expected order dealing range time)
|
||||||
start_time: pd.Timestamp
|
start_time: pd.Timestamp
|
||||||
end_time: pd.Timestamp
|
end_time: pd.Timestamp
|
||||||
|
|
||||||
direction: int
|
# 3) results
|
||||||
factor: float
|
# - users should not care about these values
|
||||||
|
# - they are set by the backtest system after finishing the results.
|
||||||
deal_amount: Optional[float] = None # `deal_amount` is a non-negative value
|
deal_amount: Optional[float] = None # `deal_amount` is a non-negative value
|
||||||
|
factor: Optional[float] = None
|
||||||
|
|
||||||
# FIXME:
|
# FIXME:
|
||||||
# for compatible now.
|
# for compatible now.
|
||||||
@@ -145,9 +153,9 @@ class OrderHelper:
|
|||||||
**adjusted trading amount**
|
**adjusted trading amount**
|
||||||
direction : OrderDir
|
direction : OrderDir
|
||||||
trading direction
|
trading direction
|
||||||
start_time : Union[str, pd.Timestamp]
|
start_time : Union[str, pd.Timestamp] (optional)
|
||||||
The interval of the order which belongs to
|
The interval of the order which belongs to
|
||||||
end_time : Union[str, pd.Timestamp]
|
end_time : Union[str, pd.Timestamp] (optional)
|
||||||
The interval of the order which belongs to
|
The interval of the order which belongs to
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -159,13 +167,13 @@ class OrderHelper:
|
|||||||
start_time = pd.Timestamp(start_time)
|
start_time = pd.Timestamp(start_time)
|
||||||
if end_time is not None:
|
if end_time is not None:
|
||||||
end_time = pd.Timestamp(end_time)
|
end_time = pd.Timestamp(end_time)
|
||||||
|
# NOTE: factor is a value belongs to the results section. User don't have to care about it when creating orders
|
||||||
return Order(
|
return Order(
|
||||||
stock_id=code,
|
stock_id=code,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
end_time=end_time,
|
end_time=end_time,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
factor=self.exchange.get_factor(code, start_time, end_time),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,20 +73,20 @@ def indicator_analysis(df, method="mean"):
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
df : pandas.DataFrame
|
df : pandas.DataFrame
|
||||||
columns: like ['pa', 'pos', 'ffr', 'amount', 'value'].
|
columns: like ['pa', 'pos', 'ffr', 'deal_amount', 'value'].
|
||||||
Necessary fields:
|
Necessary fields:
|
||||||
- 'pa' is the price advantage in trade indicators
|
- 'pa' is the price advantage in trade indicators
|
||||||
- 'pos' is the positive rate in trade indicators
|
- 'pos' is the positive rate in trade indicators
|
||||||
- 'ffr' is the fulfill rate in trade indicators
|
- 'ffr' is the fulfill rate in trade indicators
|
||||||
Optional fields:
|
Optional fields:
|
||||||
- 'amount' is the total deal amount, only necessary when method is 'amount_weighted'
|
- 'deal_amount' is the total deal deal_amount, only necessary when method is 'amount_weighted'
|
||||||
- 'value' is the total trade value, only necessary when method is 'value_weighted'
|
- 'value' is the total trade value, only necessary when method is 'value_weighted'
|
||||||
|
|
||||||
index: Index(datetime)
|
index: Index(datetime)
|
||||||
method : str, optional
|
method : str, optional
|
||||||
statistics method of pa/ffr, by default "mean"
|
statistics method of pa/ffr, by default "mean"
|
||||||
- if method is 'mean', count the mean statistical value of each trade indicator
|
- if method is 'mean', count the mean statistical value of each trade indicator
|
||||||
- if method is 'amount_weighted', count the amount weighted mean statistical value of each trade indicator
|
- if method is 'amount_weighted', count the deal_amount weighted mean statistical value of each trade indicator
|
||||||
- if method is 'value_weighted', count the value weighted mean statistical value of each trade indicator
|
- if method is 'value_weighted', count the value weighted mean statistical value of each trade indicator
|
||||||
Note: statistics method of pos is always "mean"
|
Note: statistics method of pos is always "mean"
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ def indicator_analysis(df, method="mean"):
|
|||||||
"""
|
"""
|
||||||
weights_dict = {
|
weights_dict = {
|
||||||
"mean": df["count"],
|
"mean": df["count"],
|
||||||
"amount_weighted": df["amount"].abs(),
|
"amount_weighted": df["deal_amount"].abs(),
|
||||||
"value_weighted": df["value"].abs(),
|
"value_weighted": df["value"].abs(),
|
||||||
}
|
}
|
||||||
if method not in weights_dict:
|
if method not in weights_dict:
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=Order.SELL, # 0 for sell, 1 for buy
|
direction=Order.SELL, # 0 for sell, 1 for buy
|
||||||
factor=factor,
|
|
||||||
)
|
)
|
||||||
# is order executable
|
# is order executable
|
||||||
if self.trade_exchange.check_order(sell_order):
|
if self.trade_exchange.check_order(sell_order):
|
||||||
@@ -231,7 +230,6 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=Order.BUY, # 1 for buy
|
direction=Order.BUY, # 1 for buy
|
||||||
factor=factor,
|
|
||||||
)
|
)
|
||||||
buy_order_list.append(buy_order)
|
buy_order_list.append(buy_order)
|
||||||
return TradeDecisionWO(sell_order_list + buy_order_list, self)
|
return TradeDecisionWO(sell_order_list + buy_order_list, self)
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ class TWAPStrategy(BaseStrategy):
|
|||||||
stock_id=order.stock_id, start_time=trade_start_time, end_time=trade_end_time
|
stock_id=order.stock_id, start_time=trade_start_time, end_time=trade_end_time
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(order.factor)
|
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(stock_id=order.stock_id,
|
||||||
|
start_time=order.start_time,
|
||||||
|
end_time=order.end_time)
|
||||||
_order_amount = None
|
_order_amount = None
|
||||||
# considering trade unit
|
# considering trade unit
|
||||||
if _amount_trade_unit is None:
|
if _amount_trade_unit is None:
|
||||||
@@ -99,7 +101,6 @@ class TWAPStrategy(BaseStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction, # 1 for buy
|
direction=order.direction, # 1 for buy
|
||||||
factor=order.factor,
|
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
return TradeDecisionWO(order_list=order_list, strategy=self)
|
return TradeDecisionWO(order_list=order_list, strategy=self)
|
||||||
@@ -168,7 +169,9 @@ class SBBStrategyBase(BaseStrategy):
|
|||||||
self.trade_trend[order.stock_id] = _pred_trend
|
self.trade_trend[order.stock_id] = _pred_trend
|
||||||
continue
|
continue
|
||||||
# get amount of one trade unit
|
# get amount of one trade unit
|
||||||
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(order.factor)
|
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(stock_id=order.stock_id,
|
||||||
|
start_time=order.start_time,
|
||||||
|
end_time=order.end_time)
|
||||||
if _pred_trend == self.TREND_MID:
|
if _pred_trend == self.TREND_MID:
|
||||||
_order_amount = None
|
_order_amount = None
|
||||||
# considering trade unit
|
# considering trade unit
|
||||||
@@ -201,7 +204,6 @@ class SBBStrategyBase(BaseStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction,
|
direction=order.direction,
|
||||||
factor=order.factor,
|
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
|
|
||||||
@@ -248,7 +250,6 @@ class SBBStrategyBase(BaseStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction, # 1 for buy
|
direction=order.direction, # 1 for buy
|
||||||
factor=order.factor,
|
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
else:
|
else:
|
||||||
@@ -267,7 +268,6 @@ class SBBStrategyBase(BaseStrategy):
|
|||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction, # 1 for buy
|
direction=order.direction, # 1 for buy
|
||||||
factor=order.factor,
|
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
|
|
||||||
@@ -471,7 +471,9 @@ class ACStrategy(BaseStrategy):
|
|||||||
|
|
||||||
if sig_sam is None or np.isnan(sig_sam):
|
if sig_sam is None or np.isnan(sig_sam):
|
||||||
# no signal, TWAP
|
# no signal, TWAP
|
||||||
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(order.factor)
|
_amount_trade_unit = self.trade_exchange.get_amount_of_trade_unit(stock_id=order.stock_id,
|
||||||
|
start_time=order.start_time,
|
||||||
|
end_time=order.end_time)
|
||||||
if _amount_trade_unit is None:
|
if _amount_trade_unit is None:
|
||||||
# divide the order into equal parts, and trade one part
|
# divide the order into equal parts, and trade one part
|
||||||
_order_amount = self.trade_amount[order.stock_id] / (trade_len - trade_step)
|
_order_amount = self.trade_amount[order.stock_id] / (trade_len - trade_step)
|
||||||
@@ -492,7 +494,10 @@ class ACStrategy(BaseStrategy):
|
|||||||
np.sinh(kappa * (trade_len - trade_step)) - np.sinh(kappa * (trade_len - trade_step - 1))
|
np.sinh(kappa * (trade_len - trade_step)) - np.sinh(kappa * (trade_len - trade_step - 1))
|
||||||
) / np.sinh(kappa * trade_len)
|
) / np.sinh(kappa * trade_len)
|
||||||
_order_amount = order.amount * amount_ratio
|
_order_amount = order.amount * amount_ratio
|
||||||
_order_amount = self.trade_exchange.round_amount_by_trade_unit(_order_amount, order.factor)
|
_order_amount = self.trade_exchange.round_amount_by_trade_unit(_order_amount,
|
||||||
|
stock_id=order.stock_id,
|
||||||
|
start_time=order.start_time,
|
||||||
|
end_time=order.end_time)
|
||||||
|
|
||||||
if order.direction == order.SELL:
|
if order.direction == order.SELL:
|
||||||
# sell all amount at last
|
# sell all amount at last
|
||||||
|
|||||||
Reference in New Issue
Block a user