1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 17:34:35 +08:00
This commit is contained in:
bxdd
2021-04-30 22:56:21 +08:00
parent a109df3f46
commit d297a493b8
5 changed files with 102 additions and 70 deletions

View File

@@ -7,8 +7,7 @@ from pathlib import Path
import qlib import qlib
import pandas as pd import pandas as pd
from qlib.config import REG_CN from qlib.config import REG_CN
from qlib.contrib.strategy import TopkDropoutStrategy
from qlib.contrib.backtest import backtest
from qlib.utils import exists_qlib_data, init_instance_by_config, flatten_dict from qlib.utils import exists_qlib_data, init_instance_by_config, flatten_dict
from qlib.workflow import R from qlib.workflow import R
from qlib.workflow.record_temp import PortAnaRecord from qlib.workflow.record_temp import PortAnaRecord
@@ -130,20 +129,9 @@ if __name__ == "__main__":
"min_cost": 5, "min_cost": 5,
}, },
} }
#report_dict = backtest(
# start_time=trade_start_time,
# end_time=trade_end_time,
# **backtest_config,
# account=1e8,
# benchmark=benchmark,
# deal_price="$close",
# verbose=False,
#)
with R.start(experiment_name="highfreq_backtest"): with R.start(experiment_name="highfreq_backtest"):
# backtest. If users want to use backtest based on their own prediction, # backtest. If users want to use backtest based on their own prediction,
# please refer to https://qlib.readthedocs.io/en/latest/component/recorder.html#record-template. # please refer to https://qlib.readthedocs.io/en/latest/component/recorder.html#record-template.
recorder = R.get_recorder() recorder = R.get_recorder()
par = PortAnaRecord(recorder, port_analysis_config, 1) par = PortAnaRecord(recorder, port_analysis_config, "day")
par.generate() par.generate()

View File

@@ -6,7 +6,7 @@ import pathlib
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from ...data.data import Cal from ...data.data import Cal
from ...utils import get_sample_freq_calendar from ...utils import get_sample_freq_calendar, parse_freq
from .position import Position from .position import Position
from .report import Report from .report import Report
from .order import Order from .order import Order
@@ -151,16 +151,25 @@ class SplitEnv(BaseEnv):
trade_state, trade_info = self.sub_env.execute(order_list=_order_list) trade_state, trade_info = self.sub_env.execute(order_list=_order_list)
self.trade_account.update_bar_end( self.trade_account.update_bar_end(
trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange, update_report=self.generate_report trade_start_time=trade_start_time,
trade_end_time=trade_end_time,
trade_exchange=self.trade_exchange,
update_report=self.generate_report,
) )
_obs = {"current": self.trade_account.current} _obs = {"current": self.trade_account.current}
_info = {} _info = {}
return _obs, _info return _obs, _info
def get_report(self): def get_report(self):
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None sub_env_report_dict = self.sub_env.get_report()
_positions = self.trade_account.get_positions() if self.generate_report else None if self.generate_report:
return [(_report, _positions), *self.sub_env.get_report()] _report = self.trade_account.report.generate_report_dataframe()
_positions = self.trade_account.get_positions()
_count, _freq = parse_freq(self.step_bar)
sub_env_report_dict.update({f"{_count}{_freq}": (_report, _positions)})
return sub_env_report_dict
else:
return sub_env_report_dict
class SimulatorEnv(BaseEnv): class SimulatorEnv(BaseEnv):
@@ -235,13 +244,20 @@ class SimulatorEnv(BaseEnv):
# do nothing # do nothing
pass pass
self.trade_account.update_bar_end( self.trade_account.update_bar_end(
trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange, update_report=self.generate_report trade_start_time=trade_start_time,
trade_end_time=trade_end_time,
trade_exchange=self.trade_exchange,
update_report=self.generate_report,
) )
_obs = {"current": self.trade_account.current} _obs = {"current": self.trade_account.current}
_info = {"trade_info": trade_info} _info = {"trade_info": trade_info}
return _obs, _info return _obs, _info
def get_report(self): def get_report(self):
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None if self.generate_report:
_positions = self.trade_account.get_positions() if self.generate_report else None _report = self.trade_account.report.generate_report_dataframe()
return [(_report, _positions)] _positions = self.trade_account.get_positions()
_count, _freq = parse_freq(self.step_bar)
return {f"{_count}{_freq}": (_report, _positions)}
else:
return {}

View File

@@ -163,7 +163,7 @@ class TopkDropoutStrategy(ModelStrategy):
# Get the stock list we really want to buy # Get the stock list we really want to buy
buy = today[: len(sell) + self.topk - len(last)] buy = today[: len(sell) + self.topk - len(last)]
#print("flag", len(sell), len(buy), self.topk, len(last)) # print("flag", len(sell), len(buy), self.topk, len(last))
for code in current_stock_list: for code in current_stock_list:
if not self.trade_exchange.is_stock_tradable( if not self.trade_exchange.is_stock_tradable(
stock_id=code, start_time=trade_start_time, end_time=trade_end_time stock_id=code, start_time=trade_start_time, end_time=trade_end_time

View File

@@ -13,7 +13,7 @@ from ..data.dataset import DatasetH
from ..data.dataset.handler import DataHandlerLP from ..data.dataset.handler import DataHandlerLP
from ..utils import init_instance_by_config, get_module_by_module_path from ..utils import init_instance_by_config, get_module_by_module_path
from ..log import get_module_logger from ..log import get_module_logger
from ..utils import flatten_dict from ..utils import flatten_dict, parse_freq
from ..strategy.base import BaseStrategy from ..strategy.base import BaseStrategy
from ..contrib.eva.alpha import calc_ic, calc_long_short_return from ..contrib.eva.alpha import calc_ic, calc_long_short_return
@@ -225,7 +225,7 @@ class PortAnaRecord(RecordTemp):
artifact_path = "portfolio_analysis" artifact_path = "portfolio_analysis"
def __init__(self, recorder, config, risk_analysis_dep, **kwargs): def __init__(self, recorder, config, risk_analysis_freq, **kwargs):
""" """
config["strategy"] : dict config["strategy"] : dict
define the strategy class as well as the kwargs. define the strategy class as well as the kwargs.
@@ -233,59 +233,87 @@ class PortAnaRecord(RecordTemp):
define the env class as well as the kwargs. define the env class as well as the kwargs.
config["backtest"] : dict config["backtest"] : dict
define the backtest kwargs. define the backtest kwargs.
risk_analysis_dep : int risk_analysis_freq : int
risk analyze the dep'th env report risk analysis freq of report
""" """
super().__init__(recorder=recorder, **kwargs) super().__init__(recorder=recorder, **kwargs)
self.strategy_config = config["strategy"] self.strategy_config = config["strategy"]
self.env_config = config["env"] self.env_config = config["env"]
self.backtest_config = config["backtest"] self.backtest_config = config["backtest"]
self.risk_analysis_dep = risk_analysis_dep _count, _freq = parse_freq(risk_analysis_freq)
self.risk_analysis_freq = f"{_count}{_freq}"
self.report_freq = self._get_report_freq(self.env_config)
def _get_report_freq(self, env_config):
ret_freq = []
if env_config["kwargs"].get("generate_report", False):
_count, _freq = parse_freq(env_config["kwargs"]["step_bar"])
ret_freq.append(f"{_count}{_freq}")
if "sub_env" in env_config["kwargs"]:
ret_freq.extend(self._get_report_freq(env_config["kwargs"]["sub_env"]))
return ret_freq
def _cal_risk_analysis_scaler(self, freq):
_count, _freq = parse_freq(freq)
_freq_scaler = {
"minute": 240 * 250,
"day": 250,
"week": 50,
"month": 12,
}
return _count * _freq_scaler[_freq]
def generate(self, **kwargs): def generate(self, **kwargs):
# custom strategy and get backtest # custom strategy and get backtest
report_list = normal_backtest(env=self.env_config, strategy=self.strategy_config, **self.backtest_config) report_dict = normal_backtest(env=self.env_config, strategy=self.strategy_config, **self.backtest_config)
for report_dep, (report_normal, positions_normal) in enumerate(report_list): for report_freq, (report_normal, positions_normal) in report_dict.items():
if report_normal is None: self.recorder.save_objects(
if self.risk_analysis_dep == report_dep: **{f"report_normal_{report_freq}.pkl": report_normal}, artifact_path=PortAnaRecord.get_path()
warnings.warn( )
f"the report in dep {risk_analysis_dep} is None, please set the corresponding env with `generate_report==True`" self.recorder.save_objects(
) **{f"positions_normal_{report_freq}.pkl": positions_normal}, artifact_path=PortAnaRecord.get_path()
continue )
self.recorder.save_objects( if self.risk_analysis_freq not in report_dict:
**{f"report_normal_{report_dep}.pkl": report_normal}, artifact_path=PortAnaRecord.get_path() warnings.warn(
f"the freq {self.risk_analysis_freq} report is not found, please set the corresponding env with `generate_report==True`"
) )
self.recorder.save_objects( else:
**{f"positions_norma_{report_dep}l.pkl": positions_normal}, artifact_path=PortAnaRecord.get_path() report_normal, _ = report_dict.get(self.risk_analysis_freq)
analysis = dict()
risk_analysis_scaler = self._cal_risk_analysis_scaler(self.risk_analysis_freq)
analysis["excess_return_without_cost"] = risk_analysis(
report_normal["return"] - report_normal["bench"], risk_analysis_scaler
) )
# analysis analysis["excess_return_with_cost"] = risk_analysis(
if self.risk_analysis_dep == report_dep: report_normal["return"] - report_normal["bench"] - report_normal["cost"], risk_analysis_scaler
analysis = dict() )
analysis["excess_return_without_cost"] = risk_analysis(report_normal["return"] - report_normal["bench"]) analysis_df = pd.concat(analysis) # type: pd.DataFrame
analysis["excess_return_with_cost"] = risk_analysis( # log metrics
report_normal["return"] - report_normal["bench"] - report_normal["cost"] self.recorder.log_metrics(**flatten_dict(analysis_df["risk"].unstack().T.to_dict()))
) # save results
analysis_df = pd.concat(analysis) # type: pd.DataFrame self.recorder.save_objects(
# log metrics **{f"port_analysis_{report_freq}.pkl": analysis_df}, artifact_path=PortAnaRecord.get_path()
self.recorder.log_metrics(**flatten_dict(analysis_df["risk"].unstack().T.to_dict())) )
# save results logger.info(
self.recorder.save_objects( f"Portfolio analysis record 'port_analysis_{report_freq}.pkl' has been saved as the artifact of the Experiment {self.recorder.experiment_id}"
**{f"port_analysis.pkl_{report_dep}": analysis_df}, artifact_path=PortAnaRecord.get_path() )
) # print out results
logger.info( pprint("The following are analysis results of the excess return without cost.")
f"Portfolio analysis record 'port_analysis_{report_dep}.pkl' has been saved as the artifact of the Experiment {self.recorder.experiment_id}" pprint(analysis["excess_return_without_cost"])
) pprint("The following are analysis results of the excess return with cost.")
# print out results pprint(analysis["excess_return_with_cost"])
pprint("The following are analysis results of the excess return without cost.")
pprint(analysis["excess_return_without_cost"])
pprint("The following are analysis results of the excess return with cost.")
pprint(analysis["excess_return_with_cost"])
def list(self): def list(self):
return [ list_path = []
PortAnaRecord.get_path("report_normal.pkl"), for _freq in self.report_freq:
PortAnaRecord.get_path("positions_normal.pkl"), list_path.extend(
PortAnaRecord.get_path("port_analysis.pkl"), [
] PortAnaRecord.get_path(f"report_normal_{_freq}.pkl"),
PortAnaRecord.get_path(f"positions_normal_{_freq}.pkl"),
]
)
if _freq == self.risk_analysis_freq:
list_path.append(PortAnaRecord.get_path(f"port_analysis_{_freq}.pkl"))
return list_path