Skip to content

Commit 780f999

Browse files
evanzdyou-n-g
andauthored
support optimization based strategy (microsoft#754)
* support optimization based strategy * fix riskdata not found & update doc * refactor signal_strategy * add portfolio example * Update examples/portfolio/prepare_riskdata.py Co-authored-by: you-n-g <you-n-g@users.noreply.github.com> * fix typo Co-authored-by: you-n-g <you-n-g@users.noreply.github.com> * fix typo Co-authored-by: you-n-g <you-n-g@users.noreply.github.com> * update doc * fix riskmodel doc Co-authored-by: you-n-g <you-n-g@users.noreply.github.com> Co-authored-by: you-n-g <you-n-g@users.noreply.github.com>
1 parent d5a4d42 commit 780f999

File tree

14 files changed

+667
-261
lines changed

14 files changed

+667
-261
lines changed

docs/component/strategy.rst

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Portfolio Strategy: Portfolio Management
88
Introduction
99
===================
1010

11-
``Portfolio Strategy`` is designed to adopt different portfolio strategies, which means that users can adopt different algorithms to generate investment portfolios based on the prediction scores of the ``Forecast Model``. Users can use the ``Portfolio Strategy`` in an automatic workflow by ``Workflow`` module, please refer to `Workflow: Workflow Management <workflow.html>`_.
11+
``Portfolio Strategy`` is designed to adopt different portfolio strategies, which means that users can adopt different algorithms to generate investment portfolios based on the prediction scores of the ``Forecast Model``. Users can use the ``Portfolio Strategy`` in an automatic workflow by ``Workflow`` module, please refer to `Workflow: Workflow Management <workflow.html>`_.
1212

1313
Because the components in ``Qlib`` are designed in a loosely-coupled way, ``Portfolio Strategy`` can be used as an independent module also.
1414

@@ -28,14 +28,14 @@ Qlib provides a base class ``qlib.contrib.strategy.BaseStrategy``. All strategy
2828
Return the proportion of your total value you will use in investment. Dynamically risk_degree will result in Market timing.
2929

3030
- `generate_order_list`
31-
Return the order list.
31+
Return the order list.
3232

3333
Users can inherit `BaseStrategy` to customize their strategy class.
3434

3535
WeightStrategyBase
3636
--------------------
3737

38-
Qlib also provides a class ``qlib.contrib.strategy.WeightStrategyBase`` that is a subclass of `BaseStrategy`.
38+
Qlib also provides a class ``qlib.contrib.strategy.WeightStrategyBase`` that is a subclass of `BaseStrategy`.
3939

4040
`WeightStrategyBase` only focuses on the target positions, and automatically generates an order list based on positions. It provides the `generate_target_weight_position` interface.
4141

@@ -71,17 +71,27 @@ TopkDropoutStrategy
7171

7272
- `Topk`: The number of stocks held
7373
- `Drop`: The number of stocks sold on each trading day
74-
74+
7575
Currently, the number of held stocks is `Topk`.
7676
On each trading day, the `Drop` number of held stocks with the worst `prediction score` will be sold, and the same number of unheld stocks with the best `prediction score` will be bought.
77-
77+
7878
.. image:: ../_static/img/topk_drop.png
7979
:alt: Topk-Drop
8080

8181
``TopkDrop`` algorithm sells `Drop` stocks every trading day, which guarantees a fixed turnover rate.
82-
82+
8383
- Generate the order list from the target amount
8484

85+
EnhancedIndexingStrategy
86+
------------------------
87+
`EnhancedIndexingStrategy` Enhanced indexing combines the arts of active management and passive management,
88+
with the aim of outperforming a benchmark index (e.g., S&P 500) in terms of portfolio return while controlling
89+
the risk exposure (a.k.a. tracking error).
90+
91+
For more information, please refer to `qlib.contrib.strategy.signal_strategy.EnhancedIndexingStrategy`
92+
and `qlib.contrib.strategy.optimizer.enhanced_indexing.EnhancedIndexingOptimizer`.
93+
94+
8595
Usage & Example
8696
====================
8797

examples/portfolio/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Portfolio Optimization Strategy
2+
3+
## Introduction
4+
5+
In `qlib/examples/benchmarks` we have various **alpha** models that predict
6+
the stock returns. We also use a simple rule based `TopkDropoutStrategy` to
7+
evaluate the investing performance of these models. However, such a strategy
8+
is too simple to control the portfolio risk like correlation and volatility.
9+
10+
To this end, an optimization based strategy should be used to for the
11+
trade-off between return and risk. In this doc, we will show how to use
12+
`EnhancedIndexingStrategy` to maximize portfolio return while minimizing
13+
tracking error relative to a benchmark.
14+
15+
16+
## Preparation
17+
18+
We use China stock market data for our example.
19+
20+
1. Prepare CSI300 weight:
21+
22+
```bash
23+
wget http://fintech.msra.cn/stock_data/downloads/csi300_weight.zip
24+
unzip -d ~/.qlib/qlib_data/cn_data csi300_weight.zip
25+
rm -f csi300_weight.zip
26+
```
27+
28+
2. Prepare risk model data:
29+
30+
```bash
31+
python prepare_riskdata.py
32+
```
33+
34+
Here we use a **Statistical Risk Model** implemented in `qlib.model.riskmodel`.
35+
However users are strongly recommended to use other risk models for better quality:
36+
* **Fundamental Risk Model** like MSCI BARRA
37+
* [Deep Risk Model](https://arxiv.org/abs/2107.05201)
38+
39+
40+
## End-to-End Workflow
41+
42+
You can finish workflow with `EnhancedIndexingStrategy` by running
43+
`qrun config_enhanced_indexing.yaml`.
44+
45+
In this config, we mainly changed the strategy section compared to
46+
`qlib/examples/benchmarks/workflow_config_lightgbm_Alpha158.yaml`.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
qlib_init:
2+
provider_uri: "~/.qlib/qlib_data/cn_data"
3+
region: cn
4+
market: &market csi300
5+
benchmark: &benchmark SH000300
6+
data_handler_config: &data_handler_config
7+
start_time: 2008-01-01
8+
end_time: 2020-08-01
9+
fit_start_time: 2008-01-01
10+
fit_end_time: 2014-12-31
11+
instruments: *market
12+
port_analysis_config: &port_analysis_config
13+
strategy:
14+
class: EnhancedIndexingStrategy
15+
module_path: qlib.contrib.strategy
16+
kwargs:
17+
model: <MODEL>
18+
dataset: <DATASET>
19+
riskmodel_root: ./riskdata
20+
backtest:
21+
start_time: 2017-01-01
22+
end_time: 2020-08-01
23+
account: 100000000
24+
benchmark: *benchmark
25+
exchange_kwargs:
26+
limit_threshold: 0.095
27+
deal_price: close
28+
open_cost: 0.0005
29+
close_cost: 0.0015
30+
min_cost: 5
31+
task:
32+
model:
33+
class: LGBModel
34+
module_path: qlib.contrib.model.gbdt
35+
kwargs:
36+
loss: mse
37+
colsample_bytree: 0.8879
38+
learning_rate: 0.2
39+
subsample: 0.8789
40+
lambda_l1: 205.6999
41+
lambda_l2: 580.9768
42+
max_depth: 8
43+
num_leaves: 210
44+
num_threads: 20
45+
dataset:
46+
class: DatasetH
47+
module_path: qlib.data.dataset
48+
kwargs:
49+
handler:
50+
class: Alpha158
51+
module_path: qlib.contrib.data.handler
52+
kwargs: *data_handler_config
53+
segments:
54+
train: [2008-01-01, 2014-12-31]
55+
valid: [2015-01-01, 2016-12-31]
56+
test: [2017-01-01, 2020-08-01]
57+
record:
58+
- class: SignalRecord
59+
module_path: qlib.workflow.record_temp
60+
kwargs:
61+
model: <MODEL>
62+
dataset: <DATASET>
63+
- class: SigAnaRecord
64+
module_path: qlib.workflow.record_temp
65+
kwargs:
66+
ana_long_short: False
67+
ann_scaler: 252
68+
- class: PortAnaRecord
69+
module_path: qlib.workflow.record_temp
70+
kwargs:
71+
config: *port_analysis_config
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
import os
4+
import numpy as np
5+
import pandas as pd
6+
7+
from qlib.data import D
8+
from qlib.model.riskmodel import StructuredCovEstimator
9+
10+
11+
def prepare_data(riskdata_root="./riskdata", T=240, start_time="2016-01-01"):
12+
13+
universe = D.features(D.instruments("csi300"), ["$close"], start_time=start_time).swaplevel().sort_index()
14+
15+
price_all = (
16+
D.features(D.instruments("all"), ["$close"], start_time=start_time).squeeze().unstack(level="instrument")
17+
)
18+
19+
# StructuredCovEstimator is a statistical risk model
20+
riskmodel = StructuredCovEstimator()
21+
22+
for i in range(T - 1, len(price_all)):
23+
24+
date = price_all.index[i]
25+
ref_date = price_all.index[i - T + 1]
26+
27+
print(date)
28+
29+
codes = universe.loc[date].index
30+
price = price_all.loc[ref_date:date, codes]
31+
32+
# calculate return and remove extreme return
33+
ret = price.pct_change()
34+
ret.clip(ret.quantile(0.025), ret.quantile(0.975), axis=1, inplace=True)
35+
36+
# run risk model
37+
F, cov_b, var_u = riskmodel.predict(ret, is_price=False, return_decomposed_components=True)
38+
39+
# save risk data
40+
root = riskdata_root + "/" + date.strftime("%Y%m%d")
41+
os.makedirs(root, exist_ok=True)
42+
43+
pd.DataFrame(F, index=codes).to_pickle(root + "/factor_exp.pkl")
44+
pd.DataFrame(cov_b).to_pickle(root + "/factor_cov.pkl")
45+
# for specific_risk we follow the convention to save volatility
46+
pd.Series(np.sqrt(var_u), index=codes).to_pickle(root + "/specific_risk.pkl")
47+
48+
49+
if __name__ == "__main__":
50+
51+
import qlib
52+
53+
qlib.init(provider_uri="~/.qlib/qlib_data/cn_data")
54+
55+
prepare_data()

qlib/contrib/strategy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .signal_strategy import (
66
TopkDropoutStrategy,
77
WeightStrategyBase,
8+
EnhancedIndexingStrategy,
89
)
910

1011
from .rule_strategy import (
File renamed without changes.

0 commit comments

Comments
 (0)