mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-14 16:26:55 +08:00
update backtest
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from ...utils import get_date_by_shift, get_date_range
|
||||
from ..online.executor import SimulatorExecutor
|
||||
from ...data import D
|
||||
from .account import Account
|
||||
from ...config import C
|
||||
@@ -15,7 +14,7 @@ from ...data.dataset.utils import get_level_index
|
||||
LOG = get_module_logger("backtest")
|
||||
|
||||
|
||||
def backtest(pred, strategy, trade_exchange, shift, verbose, account, benchmark, return_order):
|
||||
def backtest(pred, strategy, executor, trade_exchange, shift, verbose, account, benchmark, return_order):
|
||||
"""Parameters
|
||||
----------
|
||||
pred : pandas.DataFrame
|
||||
@@ -70,8 +69,8 @@ def backtest(pred, strategy, trade_exchange, shift, verbose, account, benchmark,
|
||||
bench = _temp_result.groupby(level="datetime")[_temp_result.columns.tolist()[0]].mean()
|
||||
|
||||
trade_dates = np.append(predict_dates[shift:], get_date_range(predict_dates[-1], left_shift=1, right_shift=shift))
|
||||
executor = SimulatorExecutor(trade_exchange, verbose=verbose)
|
||||
order_set = []
|
||||
if return_order:
|
||||
multi_order_list = []
|
||||
# trading apart
|
||||
for pred_date, trade_date in zip(predict_dates, trade_dates):
|
||||
# for loop predict date and trading date
|
||||
@@ -103,8 +102,8 @@ def backtest(pred, strategy, trade_exchange, shift, verbose, account, benchmark,
|
||||
)
|
||||
else:
|
||||
order_list = []
|
||||
|
||||
order_set.append((trade_account, order_list, trade_date))
|
||||
if return_order:
|
||||
multi_order_list.append((trade_account, order_list, trade_date))
|
||||
# 4. Get result after executing order list
|
||||
# NOTE: The following operation will modify order.amount.
|
||||
# NOTE: If it is buy and the cash is insufficient, the tradable amount will be recalculated
|
||||
@@ -113,53 +112,16 @@ def backtest(pred, strategy, trade_exchange, shift, verbose, account, benchmark,
|
||||
# 5. Update account information according to transaction
|
||||
update_account(trade_account, trade_info, trade_exchange, trade_date)
|
||||
|
||||
if return_order:
|
||||
return order_set
|
||||
else:
|
||||
# generate backtest report
|
||||
report_df = trade_account.report.generate_report_dataframe()
|
||||
report_df["bench"] = bench
|
||||
positions = trade_account.get_positions()
|
||||
return report_df, positions
|
||||
|
||||
def backtest_highfreq(pred, executor, trade_exchange, shift, order_set, verbose, account, benchmark):
|
||||
trade_account_highfreq = Account(init_cash=account)
|
||||
_pred_dates = pred.index.get_level_values(level="datetime")
|
||||
predict_dates = D.calendar(start_time=_pred_dates.min(), end_time=_pred_dates.max())
|
||||
|
||||
if isinstance(benchmark, pd.Series):
|
||||
bench = benchmark
|
||||
else:
|
||||
_codes = benchmark if isinstance(benchmark, list) else [benchmark]
|
||||
_temp_result = D.features(
|
||||
_codes,
|
||||
["$close/Ref($close,1)-1"],
|
||||
predict_dates[0],
|
||||
get_date_by_shift(predict_dates[-1], shift=shift),
|
||||
disk_cache=1,
|
||||
)
|
||||
if len(_temp_result) == 0:
|
||||
raise ValueError(f"The benchmark {_codes} does not exist. Please provide the right benchmark")
|
||||
bench = _temp_result.groupby(level="datetime")[_temp_result.columns.tolist()[0]].mean()
|
||||
|
||||
for trade_account, order_list, trade_date in order_set:
|
||||
if verbose:
|
||||
LOG.info("[I {:%Y-%m-%d}]: highfreq trade begin.".format(trade_date))
|
||||
## TODO: kanren group need to merge code here
|
||||
print(trade_account, order_list, trade_date)
|
||||
executor.execute(trade_account, order_list, trade_date)
|
||||
|
||||
for trade_account, order_list, trade_date in order_set:
|
||||
trade_info = executor.get_res()
|
||||
print(trade_info)
|
||||
update_account(trade_account_highfreq, trade_info, trade_exchange, trade_date)
|
||||
if verbose:
|
||||
LOG.info("[I {:%Y-%m-%d}]: highfreq trade end.".format(trade_date))
|
||||
executor.close()
|
||||
report_df = trade_account_highfreq.report.generate_report_dataframe()
|
||||
# generate backtest report
|
||||
report_df = trade_account.report.generate_report_dataframe()
|
||||
report_df["bench"] = bench
|
||||
positions = trade_account_highfreq.get_positions()
|
||||
return report_df, positions
|
||||
positions = trade_account.get_positions()
|
||||
|
||||
report_dict = {"report_df": report_df, "positions": positions}
|
||||
if return_order:
|
||||
report_dict.update({"order_list": multi_order_list})
|
||||
return report_dict
|
||||
|
||||
|
||||
def update_account(trade_account, trade_info, trade_exchange, trade_date):
|
||||
"""Update the account and strategy
|
||||
|
||||
@@ -11,7 +11,8 @@ from ..log import get_module_logger
|
||||
from . import strategy as strategy_pool
|
||||
from .strategy.strategy import BaseStrategy
|
||||
from .backtest.exchange import Exchange
|
||||
from .backtest.backtest import backtest as backtest_func, get_date_range, backtest_highfreq as backtest_highfreq_func
|
||||
from .backtest.backtest import backtest as backtest_func, get_date_range
|
||||
from .online.executor import BaseExecutor, SimulatorExecutor
|
||||
|
||||
from ..data import D
|
||||
from ..config import C
|
||||
@@ -100,7 +101,7 @@ def get_strategy(
|
||||
"weight": "TopkWeightStrategy",
|
||||
"dropout": "TopkDropoutStrategy",
|
||||
}
|
||||
logger.info("Create new streategy ")
|
||||
logger.info("Create new strategy ")
|
||||
str_cls = getattr(strategy_pool, str_cls_dict.get(str_type))
|
||||
strategy = str_cls(
|
||||
topk=topk,
|
||||
@@ -111,6 +112,7 @@ def get_strategy(
|
||||
)
|
||||
elif isinstance(strategy, (dict, str)):
|
||||
# 2) create strategy with init_instance_by_config
|
||||
logger.info("Create new strategy ")
|
||||
strategy = init_instance_by_config(strategy)
|
||||
|
||||
# else: nothing happens. 3) Use the strategy directly
|
||||
@@ -196,8 +198,48 @@ def get_exchange(
|
||||
return exchange
|
||||
|
||||
|
||||
def get_executor(
|
||||
executor=None,
|
||||
trade_exchange=None,
|
||||
verbose=True,
|
||||
):
|
||||
"""get_executor
|
||||
|
||||
There will be 3 ways to return a executor. Please follow the code.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
executor : BaseExecutor
|
||||
executor used in backtest.
|
||||
trade_exchange : Exchange
|
||||
exchange used in executor
|
||||
verbose : bool
|
||||
whether to print log.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class: BaseExecutor
|
||||
an initialized BaseExecutor object
|
||||
"""
|
||||
# There will be 3 ways to return a executor.
|
||||
if executor is None:
|
||||
# 1) create executor with param `executor`
|
||||
logger.info("Create new executor ")
|
||||
executor = SimulatorExecutor(trade_exchange=trade_exchange, verbose=verbose)
|
||||
elif isinstance(executor, (dict, str)):
|
||||
# 2) create executor with config
|
||||
logger.info("Create new executor ")
|
||||
executor = init_instance_by_config(executor)
|
||||
|
||||
# 3) Use the executor directly
|
||||
if not isinstance(executor, BaseExecutor):
|
||||
raise TypeError("Executor not supported")
|
||||
return executor
|
||||
|
||||
|
||||
# This is the API for compatibility for legacy code
|
||||
def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **kwargs):
|
||||
def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, return_order=False, **kwargs):
|
||||
"""This function will help you set a reasonable Exchange and provide default value for strategy
|
||||
Parameters
|
||||
----------
|
||||
@@ -214,6 +256,8 @@ def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **k
|
||||
benchmark code, default is SH000905 CSI 500.
|
||||
verbose : bool
|
||||
whether to print log.
|
||||
return_order : bool
|
||||
whther to return order list
|
||||
|
||||
- **strategy related arguments**
|
||||
|
||||
@@ -261,6 +305,14 @@ def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **k
|
||||
will we pass the codes extracted from the pred to the exchange.
|
||||
|
||||
.. note:: This will be faster with offline qlib.
|
||||
|
||||
- **executor related arguments**
|
||||
|
||||
executor : BaseExecutor()
|
||||
executor used in backtest.
|
||||
verbose : bool
|
||||
whether to print log.
|
||||
|
||||
"""
|
||||
# check strategy:
|
||||
spec = inspect.getfullargspec(get_strategy)
|
||||
@@ -271,45 +323,27 @@ def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **k
|
||||
spec = inspect.getfullargspec(get_exchange)
|
||||
ex_args = {k: v for k, v in kwargs.items() if k in spec.args}
|
||||
trade_exchange = get_exchange(pred, **ex_args)
|
||||
if kwargs.get('highfreq_executor', False):
|
||||
order_set = backtest_func(
|
||||
pred=pred,
|
||||
strategy=strategy,
|
||||
trade_exchange=trade_exchange,
|
||||
shift=shift,
|
||||
verbose=verbose,
|
||||
account=account,
|
||||
benchmark=benchmark,
|
||||
return_order=True,
|
||||
)
|
||||
executor = init_instance_by_config(kwargs.get('highfreq_executor'))
|
||||
report_df, positions = backtest_highfreq_func(
|
||||
pred=pred,
|
||||
executor=executor,
|
||||
trade_exchange=trade_exchange,
|
||||
shift=shift,
|
||||
order_set=order_set,
|
||||
verbose=verbose,
|
||||
account=account,
|
||||
benchmark=benchmark
|
||||
)
|
||||
positions = {k: p.position for k, p in positions.items()}
|
||||
return report_df, positions
|
||||
else:
|
||||
# run backtest
|
||||
report_df, positions = backtest_func(
|
||||
pred=pred,
|
||||
strategy=strategy,
|
||||
trade_exchange=trade_exchange,
|
||||
shift=shift,
|
||||
verbose=verbose,
|
||||
account=account,
|
||||
benchmark=benchmark,
|
||||
return_order=False,
|
||||
)
|
||||
# for compatibility of the old API. return the dict positions
|
||||
positions = {k: p.position for k, p in positions.items()}
|
||||
return report_df, positions
|
||||
|
||||
# init executor:
|
||||
executor = get_executor(executor=kwargs.get("executor"), trade_exchange=trade_exchange, verbose=verbose)
|
||||
|
||||
# run backtest
|
||||
report_dict = backtest_func(
|
||||
pred=pred,
|
||||
strategy=strategy,
|
||||
executor=executor,
|
||||
trade_exchange=trade_exchange,
|
||||
shift=shift,
|
||||
verbose=verbose,
|
||||
account=account,
|
||||
benchmark=benchmark,
|
||||
return_order=return_order,
|
||||
)
|
||||
# for compatibility of the old API. return the dict positions
|
||||
|
||||
positions = report_dict.get("positions")
|
||||
report_dict.update({"positions": {k: p.position for k, p in positions.items()}})
|
||||
return report_dict
|
||||
|
||||
|
||||
def long_short_backtest(
|
||||
|
||||
@@ -241,9 +241,14 @@ class PortAnaRecord(SignalRecord):
|
||||
|
||||
# custom strategy and get backtest
|
||||
pred_score = super().load()
|
||||
report_normal, positions_normal = normal_backtest(pred_score, strategy=self.strategy, **self.backtest_config)
|
||||
report_dict = normal_backtest(pred_score, strategy=self.strategy, **self.backtest_config)
|
||||
report_normal = report_dict.get("report_df")
|
||||
positions_normal = report_dict.get("positions")
|
||||
self.recorder.save_objects(**{"report_normal.pkl": report_normal}, artifact_path=PortAnaRecord.get_path())
|
||||
self.recorder.save_objects(**{"positions_normal.pkl": positions_normal}, artifact_path=PortAnaRecord.get_path())
|
||||
order_normal = report_dict.get("order_list")
|
||||
if order_normal:
|
||||
self.recorder.save_objects(**{"order_normal.pkl": order_normal}, artifact_path=PortAnaRecord.get_path())
|
||||
|
||||
# analysis
|
||||
analysis = dict()
|
||||
|
||||
Reference in New Issue
Block a user