|
1 | 1 | # Copyright (c) Microsoft Corporation. |
2 | 2 | # Licensed under the MIT License. |
| 3 | +""" |
| 4 | +The expect result of `backtest` is following in current version |
3 | 5 |
|
| 6 | +'The following are analysis results of benchmark return(1day).' |
| 7 | + risk |
| 8 | +mean 0.000651 |
| 9 | +std 0.012472 |
| 10 | +annualized_return 0.154967 |
| 11 | +information_ratio 0.805422 |
| 12 | +max_drawdown -0.160445 |
| 13 | +'The following are analysis results of the excess return without cost(1day).' |
| 14 | + risk |
| 15 | +mean 0.001258 |
| 16 | +std 0.007575 |
| 17 | +annualized_return 0.299303 |
| 18 | +information_ratio 2.561219 |
| 19 | +max_drawdown -0.068386 |
| 20 | +'The following are analysis results of the excess return with cost(1day).' |
| 21 | + risk |
| 22 | +mean 0.001110 |
| 23 | +std 0.007575 |
| 24 | +annualized_return 0.264280 |
| 25 | +information_ratio 2.261392 |
| 26 | +max_drawdown -0.071842 |
| 27 | +[1706497:MainThread](2021-12-07 14:08:30,263) INFO - qlib.workflow - [record_temp.py:441] - Portfolio analysis record 'port_analysis_30minute. |
| 28 | +pkl' has been saved as the artifact of the Experiment 2 |
| 29 | +'The following are analysis results of benchmark return(30minute).' |
| 30 | + risk |
| 31 | +mean 0.000078 |
| 32 | +std 0.003646 |
| 33 | +annualized_return 0.148787 |
| 34 | +information_ratio 0.935252 |
| 35 | +max_drawdown -0.142830 |
| 36 | +('The following are analysis results of the excess return without ' |
| 37 | + 'cost(30minute).') |
| 38 | + risk |
| 39 | +mean 0.000174 |
| 40 | +std 0.003343 |
| 41 | +annualized_return 0.331867 |
| 42 | +information_ratio 2.275019 |
| 43 | +max_drawdown -0.074752 |
| 44 | +'The following are analysis results of the excess return with cost(30minute).' |
| 45 | + risk |
| 46 | +mean 0.000155 |
| 47 | +std 0.003343 |
| 48 | +annualized_return 0.294536 |
| 49 | +information_ratio 2.018860 |
| 50 | +max_drawdown -0.075579 |
| 51 | +[1706497:MainThread](2021-12-07 14:08:30,277) INFO - qlib.workflow - [record_temp.py:441] - Portfolio analysis record 'port_analysis_5minute.p |
| 52 | +kl' has been saved as the artifact of the Experiment 2 |
| 53 | +'The following are analysis results of benchmark return(5minute).' |
| 54 | + risk |
| 55 | +mean 0.000015 |
| 56 | +std 0.001460 |
| 57 | +annualized_return 0.172170 |
| 58 | +information_ratio 1.103439 |
| 59 | +max_drawdown -0.144807 |
| 60 | +'The following are analysis results of the excess return without cost(5minute).' |
| 61 | + risk |
| 62 | +mean 0.000028 |
| 63 | +std 0.001412 |
| 64 | +annualized_return 0.319771 |
| 65 | +information_ratio 2.119563 |
| 66 | +max_drawdown -0.077426 |
| 67 | +'The following are analysis results of the excess return with cost(5minute).' |
| 68 | + risk |
| 69 | +mean 0.000025 |
| 70 | +std 0.001412 |
| 71 | +annualized_return 0.281536 |
| 72 | +information_ratio 1.866091 |
| 73 | +max_drawdown -0.078194 |
| 74 | +[1706497:MainThread](2021-12-07 14:08:30,287) INFO - qlib.workflow - [record_temp.py:466] - Indicator analysis record 'indicator_analysis_1day |
| 75 | +.pkl' has been saved as the artifact of the Experiment 2 |
| 76 | +'The following are analysis results of indicators(1day).' |
| 77 | + value |
| 78 | +ffr 0.945821 |
| 79 | +pa 0.000324 |
| 80 | +pos 0.542882 |
| 81 | +[1706497:MainThread](2021-12-07 14:08:30,293) INFO - qlib.workflow - [record_temp.py:466] - Indicator analysis record 'indicator_analysis_30mi |
| 82 | +nute.pkl' has been saved as the artifact of the Experiment 2 |
| 83 | +'The following are analysis results of indicators(30minute).' |
| 84 | + value |
| 85 | +ffr 0.982910 |
| 86 | +pa 0.000037 |
| 87 | +pos 0.500806 |
| 88 | +[1706497:MainThread](2021-12-07 14:08:30,302) INFO - qlib.workflow - [record_temp.py:466] - Indicator analysis record 'indicator_analysis_5min |
| 89 | +ute.pkl' has been saved as the artifact of the Experiment 2 |
| 90 | +'The following are analysis results of indicators(5minute).' |
| 91 | + value |
| 92 | +ffr 0.991017 |
| 93 | +pa 0.000000 |
| 94 | +pos 0.000000 |
| 95 | +[1706497:MainThread](2021-12-07 14:08:30,627) INFO - qlib.timer - [log.py:113] - Time cost: 0.014s | waiting `async_log` Done |
| 96 | +""" |
4 | 97 |
|
| 98 | + |
| 99 | +from copy import deepcopy |
5 | 100 | import qlib |
6 | 101 | import fire |
| 102 | +import pandas as pd |
7 | 103 | from qlib.config import REG_CN, HIGH_FREQ_CONFIG |
8 | 104 | from qlib.data import D |
9 | 105 | from qlib.utils import exists_qlib_data, init_instance_by_config, flatten_dict |
|
14 | 110 |
|
15 | 111 |
|
16 | 112 | class NestedDecisionExecutionWorkflow: |
| 113 | + # TODO: add test for nested workflow. |
| 114 | + # 1) comparing same backtest |
| 115 | + # - Basic test idea: the shared accumulated value are equal in multiple levels |
| 116 | + # - Aligning the profit calculation between multiple levels and single levels. |
| 117 | + # 2) comparing different backtest |
| 118 | + # - Basic test idea: |
| 119 | + # - the daily backtest will be similar as multi-level(the data quality makes this gap samller) |
17 | 120 |
|
18 | 121 | market = "csi300" |
19 | 122 | benchmark = "SH000300" |
@@ -167,8 +270,6 @@ def backtest(self): |
167 | 270 | par = PortAnaRecord( |
168 | 271 | recorder, |
169 | 272 | self.port_analysis_config, |
170 | | - risk_analysis_freq=["day", "30min", "5min"], |
171 | | - indicator_analysis_freq=["day", "30min", "5min"], |
172 | 273 | indicator_analysis_method="value_weighted", |
173 | 274 | ) |
174 | 275 | par.generate() |
@@ -199,6 +300,93 @@ def collect_data(self): |
199 | 300 | for trade_decision in data_generator: |
200 | 301 | print(trade_decision) |
201 | 302 |
|
| 303 | + # the code below are for checking, users don't have to care about it |
| 304 | + def check_diff_freq(self): |
| 305 | + self._init_qlib() |
| 306 | + exp = R.get_exp(experiment_name="backtest") |
| 307 | + rec = next(iter(exp.list_recorders().values())) # assuming this will get the latest recorder |
| 308 | + for check_key in "account", "total_turnover", "total_cost": |
| 309 | + check_key = "total_cost" |
| 310 | + |
| 311 | + acc_dict = {} |
| 312 | + for freq in ["30minute", "5minute", "1day"]: |
| 313 | + acc_dict[freq] = rec.load_object(f"portfolio_analysis/report_normal_{freq}.pkl")[check_key] |
| 314 | + acc_df = pd.DataFrame(acc_dict) |
| 315 | + acc_resam = acc_df.resample("1d").last().dropna() |
| 316 | + assert (acc_resam["30minute"] == acc_resam["1day"]).all() |
| 317 | + |
| 318 | + def backtest_only_daily(self): |
| 319 | + """ |
| 320 | + This backtest is used for comparing the nested execution and single layer execution |
| 321 | + Due to the low quality daily-level and miniute-level data, they are hardly comparable. |
| 322 | + So it is used for detecting serious bugs which make the results different greatly. |
| 323 | +
|
| 324 | + .. code-block:: shell |
| 325 | +
|
| 326 | + [1724971:MainThread](2021-12-07 16:24:31,156) INFO - qlib.workflow - [record_temp.py:441] - Portfolio analysis record 'port_analysis_1day.pkl' |
| 327 | + has been saved as the artifact of the Experiment 2 |
| 328 | + 'The following are analysis results of benchmark return(1day).' |
| 329 | + risk |
| 330 | + mean 0.000651 |
| 331 | + std 0.012472 |
| 332 | + annualized_return 0.154967 |
| 333 | + information_ratio 0.805422 |
| 334 | + max_drawdown -0.160445 |
| 335 | + 'The following are analysis results of the excess return without cost(1day).' |
| 336 | + risk |
| 337 | + mean 0.001375 |
| 338 | + std 0.006103 |
| 339 | + annualized_return 0.327204 |
| 340 | + information_ratio 3.475016 |
| 341 | + max_drawdown -0.024927 |
| 342 | + 'The following are analysis results of the excess return with cost(1day).' |
| 343 | + risk |
| 344 | + mean 0.001184 |
| 345 | + std 0.006091 |
| 346 | + annualized_return 0.281801 |
| 347 | + information_ratio 2.998749 |
| 348 | + max_drawdown -0.029568 |
| 349 | + [1724971:MainThread](2021-12-07 16:24:31,170) INFO - qlib.workflow - [record_temp.py:466] - Indicator analysis record 'indicator_analysis_1day. |
| 350 | + pkl' has been saved as the artifact of the Experiment 2 |
| 351 | + 'The following are analysis results of indicators(1day).' |
| 352 | + value |
| 353 | + ffr 1.0 |
| 354 | + pa 0.0 |
| 355 | + pos 0.0 |
| 356 | + [1724971:MainThread](2021-12-07 16:24:31,188) INFO - qlib.timer - [log.py:113] - Time cost: 0.007s | waiting `async_log` Done |
| 357 | +
|
| 358 | + """ |
| 359 | + self._init_qlib() |
| 360 | + model = init_instance_by_config(self.task["model"]) |
| 361 | + dataset = init_instance_by_config(self.task["dataset"]) |
| 362 | + self._train_model(model, dataset) |
| 363 | + strategy_config = { |
| 364 | + "class": "TopkDropoutStrategy", |
| 365 | + "module_path": "qlib.contrib.strategy.signal_strategy", |
| 366 | + "kwargs": { |
| 367 | + "signal": (model, dataset), |
| 368 | + "topk": 50, |
| 369 | + "n_drop": 5, |
| 370 | + }, |
| 371 | + } |
| 372 | + pa_conf = deepcopy(self.port_analysis_config) |
| 373 | + pa_conf["strategy"] = strategy_config |
| 374 | + pa_conf["executor"] = { |
| 375 | + "class": "SimulatorExecutor", |
| 376 | + "module_path": "qlib.backtest.executor", |
| 377 | + "kwargs": { |
| 378 | + "time_per_step": "day", |
| 379 | + "generate_portfolio_metrics": True, |
| 380 | + "verbose": True, |
| 381 | + }, |
| 382 | + } |
| 383 | + pa_conf["backtest"]["benchmark"] = self.benchmark |
| 384 | + |
| 385 | + with R.start(experiment_name="backtest"): |
| 386 | + recorder = R.get_recorder() |
| 387 | + par = PortAnaRecord(recorder, pa_conf) |
| 388 | + par.generate() |
| 389 | + |
202 | 390 |
|
203 | 391 | if __name__ == "__main__": |
204 | 392 | fire.Fire(NestedDecisionExecutionWorkflow) |
0 commit comments