From 7048bef7c69e3a3e56bbf8ffb34b85eac490c192 Mon Sep 17 00:00:00 2001 From: Young Date: Sun, 4 Jul 2021 06:41:34 +0000 Subject: [PATCH] fix ffr and order amount --- qlib/backtest/account.py | 7 +++++-- qlib/backtest/executor.py | 2 ++ qlib/backtest/order.py | 31 +++++++++++++++++++++++++++++-- qlib/backtest/report.py | 29 +++++++++++++++++++++-------- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/qlib/backtest/account.py b/qlib/backtest/account.py index 6167ee407..0d89dde87 100644 --- a/qlib/backtest/account.py +++ b/qlib/backtest/account.py @@ -9,7 +9,7 @@ import pandas as pd from .position import BasePosition, InfPosition, Position from .report import Report, Indicator -from .order import Order +from .order import BaseTradeDecision, Order from .exchange import Exchange """ @@ -226,6 +226,7 @@ class Account: trade_end_time: pd.Timestamp, trade_exchange: Exchange, atomic: bool, + outer_trade_decision: BaseTradeDecision, generate_report: bool = False, trade_info: list = None, inner_order_indicators: Indicator = None, @@ -276,7 +277,9 @@ class Account: if atomic: self.indicator.update_order_indicators(trade_start_time, trade_end_time, trade_info, trade_exchange) else: - self.indicator.agg_order_indicators(inner_order_indicators, indicator_config) + self.indicator.agg_order_indicators( + inner_order_indicators, indicator_config=indicator_config, outer_trade_decision=outer_trade_decision + ) self.indicator.cal_trade_indicators(trade_start_time, self.freq, indicator_config) self.indicator.record(trade_start_time) diff --git a/qlib/backtest/executor.py b/qlib/backtest/executor.py index 3f7b2f4ed..7341e5225 100644 --- a/qlib/backtest/executor.py +++ b/qlib/backtest/executor.py @@ -299,6 +299,7 @@ class NestedExecutor(BaseExecutor): trade_end_time, self.trade_exchange, atomic=False, + outer_trade_decision=trade_decision, generate_report=self.generate_report, inner_order_indicators=inner_order_indicators, indicator_config=self.indicator_config, @@ -409,6 +410,7 @@ class SimulatorExecutor(BaseExecutor): trade_end_time, self.trade_exchange, atomic=True, + outer_trade_decision=trade_decision, generate_report=self.generate_report, trade_info=execute_result, indicator_config=self.indicator_config, diff --git a/qlib/backtest/order.py b/qlib/backtest/order.py index 32c4121fc..64ff2a56f 100644 --- a/qlib/backtest/order.py +++ b/qlib/backtest/order.py @@ -40,7 +40,7 @@ class Order: """ stock_id: str - amount: float + amount: float # `amount` is a non-negative value # The interval of the order which belongs to (NOTE: this is not the expected order dealing range time) start_time: pd.Timestamp @@ -48,7 +48,7 @@ class Order: direction: int factor: float - deal_amount: Optional[float] = None + deal_amount: Optional[float] = None # `deal_amount` is a non-negative value # FIXME: # for compatible now. @@ -62,6 +62,33 @@ class Order: raise NotImplementedError("direction not supported, `Order.SELL` for sell, `Order.BUY` for buy") self.deal_amount = 0 + @property + def amount_delta(self) -> float: + """ + return the delta of amount. + - Positive value indicates buying `amount` of share + - Negative value indicates selling `amount` of share + """ + return self.amount * self.sign + + @property + def deal_amount_delta(self) -> float: + """ + return the delta of deal_amount. + - Positive value indicates buying `deal_amount` of share + - Negative value indicates selling `deal_amount` of share + """ + return self.deal_amount * self.sign + + @property + def sign(self) -> float: + """ + return the sign of trading + - `+1` indicates buying + - `-1` value indicates selling + """ + return self.direction * 2 - 1 + @staticmethod def parse_dir(direction: Union[str, int, np.integer, OrderDir]) -> OrderDir: if isinstance(direction, OrderDir): diff --git a/qlib/backtest/report.py b/qlib/backtest/report.py index f217ea169..4f645c564 100644 --- a/qlib/backtest/report.py +++ b/qlib/backtest/report.py @@ -4,6 +4,8 @@ from collections import OrderedDict from logging import warning +from typing import List +from qlib.backtest.order import BaseTradeDecision, Order import pandas as pd import pathlib import warnings @@ -241,13 +243,13 @@ class Indicator: trade_cost = dict() for order, _trade_val, _trade_cost, _trade_price in trade_info: - amount[order.stock_id] = order.amount * (order.direction * 2 - 1) - deal_amount[order.stock_id] = order.deal_amount * (order.direction * 2 - 1) + amount[order.stock_id] = order.amount_delta + deal_amount[order.stock_id] = order.deal_amount_delta trade_price[order.stock_id] = _trade_price - trade_value[order.stock_id] = _trade_val * (order.direction * 2 - 1) + trade_value[order.stock_id] = _trade_val * order.sign trade_cost[order.stock_id] = _trade_cost - self.order_indicator["amount"] = pd.Series(amount) + self.order_indicator["amount"] = self.order_indicator["inner_amount"] = pd.Series(amount) self.order_indicator["deal_amount"] = pd.Series(deal_amount) self.order_indicator["trade_price"] = pd.Series(trade_price) self.order_indicator["trade_value"] = pd.Series(trade_value) @@ -271,13 +273,13 @@ class Indicator: ) / self.order_indicator["base_price"] def _agg_order_trade_info(self, inner_order_indicators): - amount = pd.Series() + inner_amount = pd.Series() deal_amount = pd.Series() trade_price = pd.Series() trade_value = pd.Series() trade_cost = pd.Series() for _order_indicator in inner_order_indicators: - amount = amount.add(_order_indicator["amount"], fill_value=0) + inner_amount = inner_amount.add(_order_indicator["inner_amount"], fill_value=0) deal_amount = deal_amount.add(_order_indicator["deal_amount"], fill_value=0) trade_price = trade_price.add( _order_indicator["trade_price"] * _order_indicator["deal_amount"], fill_value=0 @@ -285,13 +287,21 @@ class Indicator: trade_value = trade_value.add(_order_indicator["trade_value"], fill_value=0) trade_cost = trade_cost.add(_order_indicator["trade_cost"], fill_value=0) - self.order_indicator["amount"] = amount + self.order_indicator["inner_amount"] = inner_amount self.order_indicator["deal_amount"] = deal_amount trade_price /= self.order_indicator["deal_amount"] self.order_indicator["trade_price"] = trade_price self.order_indicator["trade_value"] = trade_value self.order_indicator["trade_cost"] = trade_cost + def _update_trade_amount(self, outer_trade_decision: BaseTradeDecision): + # NOTE: these indicator is designed for order execution, so the + decision: List[Order] = outer_trade_decision.get_decision() + if decision is None: + self.order_indicator["amount"] = pd.Series() + else: + self.order_indicator["amount"] = pd.Series({order.stock_id: order.amount_delta for order in decision}) + def _agg_order_fulfill_rate(self): self.order_indicator["ffr"] = self.order_indicator["deal_amount"] / self.order_indicator["amount"] @@ -367,8 +377,11 @@ class Indicator: self._update_order_fulfill_rate() self._update_order_price_advantage(trade_exchange, trade_start_time, trade_end_time) - def agg_order_indicators(self, inner_order_indicators, indicator_config={}): + def agg_order_indicators( + self, inner_order_indicators, outer_trade_decision: BaseTradeDecision, indicator_config={} + ): self._agg_order_trade_info(inner_order_indicators) + self._update_trade_amount(outer_trade_decision) self._agg_order_fulfill_rate() pa_config = indicator_config.get("pa_config", {}) self._agg_order_price_advantage(inner_order_indicators, base_price=pa_config.get("base_price", "twap"))