├── .idea
├── .gitignore
├── misc.xml
├── encodings.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
├── vcs.xml
└── option_future_research.iml
├── research
├── daily_models
│ ├── P_12_0.2_0.2.jpg
│ ├── P_26_0.2_0.2.jpg
│ ├── P_5_0.2_0.2.jpg
│ ├── rb_5_0.2_0.2.jpg
│ ├── rb_12_0.2_0.2.jpg
│ ├── rb_26_0.2_0.2.jpg
│ ├── test.py
│ ├── __init__.py
│ ├── back_test_result.csv
│ ├── com_analysis.py
│ ├── basis_index.py
│ └── com_daily.py
├── __init__.py
├── ml_models
│ ├── __init__.py
│ ├── ts_transformer.py
│ ├── xgb_demo.py
│ ├── reg_demos.py
│ └── select_features.py
├── tick_models
│ ├── __init__.py
│ ├── sta_reference.py
│ └── FactorProcess.py
└── data_process
│ ├── __init__.py
│ └── DataProcess.py
├── __init__.py
├── backtester
├── Event.py
├── __init__.py
├── Account.py
├── Position.py
├── Factor.py
├── backtest.py
└── BackTester.py
├── conf
├── __init__.py
├── test.editorconfig
└── strategy.ini
├── utils
├── __init__.py
├── utils.py
└── define.py
├── strategy
├── __init__.py
├── Signal.py
├── T0Signal.py
└── RegSignal.py
├── corr_2021-07-09.csv
├── corr_2021-07-12.csv
├── corr_2021-07-02.csv
├── corr_2021-07-05.csv
├── corr_2021-07-06.csv
├── corr_2021-07-08.csv
├── corr_2021-07-07.csv
├── README.md
├── .gitignore
├── main.py
└── corr.csv
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/research/daily_models/P_12_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/P_12_0.2_0.2.jpg
--------------------------------------------------------------------------------
/research/daily_models/P_26_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/P_26_0.2_0.2.jpg
--------------------------------------------------------------------------------
/research/daily_models/P_5_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/P_5_0.2_0.2.jpg
--------------------------------------------------------------------------------
/research/daily_models/rb_5_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/rb_5_0.2_0.2.jpg
--------------------------------------------------------------------------------
/research/daily_models/rb_12_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/rb_12_0.2_0.2.jpg
--------------------------------------------------------------------------------
/research/daily_models/rb_26_0.2_0.2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqiyu/option_future_research/HEAD/research/daily_models/rb_26_0.2_0.2.jpg
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/3 9:53
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/backtester/Event.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/10/25 13:11
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : Event.py
7 |
--------------------------------------------------------------------------------
/conf/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:10
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:12
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/backtester/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:10
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/3 9:53
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/daily_models/test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/4/26 22:47
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : test.py
7 |
--------------------------------------------------------------------------------
/strategy/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:10
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/ml_models/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:09
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/tick_models/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:09
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/daily_models/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:08
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/research/data_process/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 18:13
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : __init__.py.py
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/corr_2021-07-09.csv:
--------------------------------------------------------------------------------
1 | ,log_return,cos,label_1
2 | log_return,1.0,0.6220641113253699,0.34446323257182804
3 | cos,0.6220641113253699,1.0,0.12010698745745302
4 | label_1,0.34446323257182804,0.12010698745745302,1.0
5 |
--------------------------------------------------------------------------------
/corr_2021-07-12.csv:
--------------------------------------------------------------------------------
1 | ,log_return,cos,label_1
2 | log_return,1.0,0.595690342982082,0.33677370179085736
3 | cos,0.595690342982082,1.0,0.11572490496155427
4 | label_1,0.33677370179085736,0.11572490496155427,1.0
5 |
--------------------------------------------------------------------------------
/research/ml_models/ts_transformer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/6/13 13:24
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : ts_transformer.py
7 |
--------------------------------------------------------------------------------
/research/tick_models/sta_reference.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/5/27 14:19
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : sta_reference.py
7 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/corr_2021-07-02.csv:
--------------------------------------------------------------------------------
1 | ,log_return,log_return_0,wap_log_return,label_1
2 | log_return,1.0,0.4313170766562893,-0.6303306281863491,0.3650985121997103
3 | log_return_0,0.4313170766562893,1.0,-0.5175661326767479,0.08960881877394036
4 | wap_log_return,-0.6303306281863491,-0.5175661326767479,1.0,0.015029392587053821
5 | label_1,0.3650985121997103,0.08960881877394036,0.015029392587053821,1.0
6 |
--------------------------------------------------------------------------------
/corr_2021-07-05.csv:
--------------------------------------------------------------------------------
1 | ,log_return,log_return_0,wap_log_return,label_1
2 | log_return,1.0,0.3953102674119525,-0.5778133642341495,0.31712850967467987
3 | log_return_0,0.3953102674119525,1.0,-0.4319660868247206,0.06694354783989923
4 | wap_log_return,-0.5778133642341495,-0.4319660868247206,1.0,0.06141961438248771
5 | label_1,0.31712850967467987,0.06694354783989923,0.06141961438248771,1.0
6 |
--------------------------------------------------------------------------------
/corr_2021-07-06.csv:
--------------------------------------------------------------------------------
1 | ,log_return,log_return_0,wap_log_return,label_1
2 | log_return,1.0,0.44155529940037,-0.6398386290268137,0.3648210115950664
3 | log_return_0,0.44155529940037,1.0,-0.5472088304586968,0.06895602055364117
4 | wap_log_return,-0.6398386290268137,-0.5472088304586968,1.0,0.029864960338698857
5 | label_1,0.3648210115950664,0.06895602055364117,0.029864960338698857,1.0
6 |
--------------------------------------------------------------------------------
/corr_2021-07-08.csv:
--------------------------------------------------------------------------------
1 | ,log_return,log_return_0,wap_log_return,label_1
2 | log_return,1.0,0.43150704887559765,-0.620525720744645,0.35911705943478667
3 | log_return_0,0.43150704887559765,1.0,-0.4997037944820132,0.0575499468906213
4 | wap_log_return,-0.620525720744645,-0.4997037944820132,1.0,0.03517371116977774
5 | label_1,0.35911705943478667,0.0575499468906213,0.03517371116977774,1.0
6 |
--------------------------------------------------------------------------------
/corr_2021-07-07.csv:
--------------------------------------------------------------------------------
1 | ,log_return,log_return_0,wap_log_return,label_1
2 | log_return,1.0,0.40772032791484353,-0.6065216902300024,0.3236860540770087
3 | log_return_0,0.40772032791484353,1.0,-0.45879668178550886,0.07407664102120662
4 | wap_log_return,-0.6065216902300024,-0.45879668178550886,1.0,0.03232056831367304
5 | label_1,0.3236860540770087,0.07407664102120662,0.03232056831367304,1.0
6 |
--------------------------------------------------------------------------------
/conf/test.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | volitility = 18
5 | k1 = 0.2
6 | k2 = 0.2
7 | stop_profit = 3
8 | stop_loss = 10
9 | fee = 1.51
10 | open_fee = 1.51
11 | close_t0_fee = 0.0
12 | start_tick = 2
13 | long_lots_limit = 1
14 | short_lots_limit = 1
15 | slope_upper = 1.5
16 | slope_lower = -1.5
17 | start_timestamp = 21:00:30
18 | end_timestamp = 14:59:30
19 | delay_sec = 3
20 | uqer_token = aaa
21 |
--------------------------------------------------------------------------------
/.idea/option_future_research.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/conf/strategy.ini:
--------------------------------------------------------------------------------
1 | [strategy]
2 | stop_profit = 2.0
3 | stop_loss = 5.0
4 | close_type = 0
5 | vol_limit = 2
6 | init_cash = 1000000
7 | risk_ratio = 0.3
8 | order_duration = 20
9 | signal_delay = 5
10 | risk_duration = 10
11 | cancel_order_delay = 30
12 | up_limit_price = 5000
13 | down_limit_price = 4900
14 | reg_long_threshold = 0.0008
15 | reg_short_threshold = 0.0008
16 |
17 | [models]
18 | log_return = 0.9368
19 | log_return_0 = 0.0174
20 | wap_log_return = 0.8194
21 | intercept = 000.00000119
22 | a = 3
23 | b = 2
24 |
25 |
--------------------------------------------------------------------------------
/research/ml_models/xgb_demo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/10/25 22:14
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : xgb_demo.py
7 |
8 | import xgboost as xgb
9 |
10 | # read in data
11 | dtrain = xgb.DMatrix('demo/data/agaricus.txt.train')
12 | dtest = xgb.DMatrix('demo/data/agaricus.txt.test')
13 | # specify parameters via map
14 | param = {'max_depth': 2, 'eta': 1, 'objective': 'binary:logistic'}
15 | num_round = 2
16 | bst = xgb.train(param, dtrain, num_round)
17 | # make prediction
18 | preds = bst.predict(dtest)
19 |
--------------------------------------------------------------------------------
/research/ml_models/reg_demos.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/10/26 10:41
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : reg_demos.py
7 |
8 | from sklearn.datasets import load_boston
9 | from sklearn.model_selection import cross_val_score
10 | from sklearn.ensemble import RandomForestRegressor
11 |
12 | boston = load_boston()
13 | re_model = RandomForestRegressor(n_estimators=100, n_jobs=1, random_state=0)
14 | ret = cross_val_score(re_model, boston.data, boston.target, cv=10, scoring='neg_mean_squared_error')
15 | print(ret)
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # option_future_research
2 | This repo will focus on hf factor research, clf/reg model research, and backtester for T0 strategy.
3 | Structures:
4 | - conf: conf file
5 | - global: global define across the projects
6 | - cache: some historical tick mkt data for testing purpose
7 | - research: factor and model research, mainly focus on tick model for short-terms prediction,e.g. 5s,10s
8 | - results: model evaluation results and backtesting results for strategy
9 | - strategy: strategies are kept here. To create a new strategy, you need to define a new signal class which inherit
10 | from base class Signal
--------------------------------------------------------------------------------
/research/daily_models/back_test_result.csv:
--------------------------------------------------------------------------------
1 | acc_return,std,sharp_ratio,max_drawdown,max_risk_ratio,long_sig,short_sig,long_holding,short_holding,open_trans,close_trans,bc_return,bc_sharp_ratio,product_id,start_date,end_date,strategy_name
2 | -0.23787499999999995,0.16767478666046562,-1.418668869289733,0.4603076049741889,0.3767597220529075,8,10,18,35,13,13,0.656737998843262,3.3987740584734762,P,20210403,20220419,P_5_0.2_0.2
3 | 0.11937500000000001,0.0513078005944206,2.326644264945968,0.17483882285864655,0.30732824427480915,3,2,11,6,5,4,0.5417115177610334,3.0321702441026046,P,20210403,20220419,P_12_0.2_0.2
4 | 0.06637500000000007,0.030318123265707042,2.189284587911057,0.03615,0.2401431758053639,1,1,8,4,2,2,0.40451090953665103,2.477749378945712,P,20210403,20220419,P_26_0.2_0.2
5 | 0.017077899999999646,0.0922639596050876,0.18509827751916622,0.21842811871510265,0.32375218159944424,4,4,25,33,6,5,-0.10954253037884198,-1.4519837164434737,rb,20210403,20220419,rb_5_0.2_0.2
6 | -0.17420240000000053,0.08520115789967338,-2.044601321089186,0.17959126154724436,0.23843601136932108,1,0,11,0,1,1,-0.10954253037884198,-1.4519837164434737,rb,20210403,20220419,rb_12_0.2_0.2
7 | 0.0,0.0,,0.0,0.0,0,0,0,0,0,0,-0.10954253037884198,-1.4519837164434737,rb,20210403,20220419,rb_26_0.2_0.2
8 |
--------------------------------------------------------------------------------
/strategy/Signal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 16:28
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : Signal.py
7 |
8 | import abc
9 | import utils.define as define
10 |
11 |
12 | class SignalField(object):
13 | def __init__(self):
14 | self._signal_type = define.NO_SIGNAL
15 | self._vol = 0
16 | self._price = 0
17 | self._direction = define.LONG
18 |
19 | @property
20 | def signal_type(self):
21 | return self._signal_type
22 |
23 | @signal_type.setter
24 | def signal_type(self, val):
25 | self._signal_type = val
26 |
27 | @property
28 | def vol(self):
29 | return self._vol
30 |
31 | @vol.setter
32 | def vol(self, val):
33 | self._vol = val
34 |
35 | @property
36 | def price(self):
37 | return self._price
38 |
39 | @price.setter
40 | def price(self, val):
41 | self._price = val
42 |
43 | @property
44 | def direction(self):
45 | return self._direction
46 |
47 | @direction.setter
48 | def direction(self, val):
49 | self._direction = val
50 |
51 |
52 | class Signal(object):
53 | def __init__(self, factor, position):
54 | self.factor = factor
55 | self.position = position
56 |
57 | @abc.abstractmethod
58 | def __call__(self, *args, **kwargs):
59 | pass
60 |
--------------------------------------------------------------------------------
/research/ml_models/select_features.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/14 10:05
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : select_features.py
7 |
8 | from sklearn.feature_selection import SelectKBest
9 | from sklearn.feature_selection import chi2
10 | from sklearn.feature_selection import mutual_info_classif
11 | from sklearn.datasets import load_iris
12 | import pandas as pd
13 | import numpy as np
14 | from scipy.stats import chi2_contingency
15 | from sklearn.decomposition import PCA
16 |
17 |
18 | def iris_feature_test():
19 | iris = load_iris()
20 | # mutual_info_classif, chi2
21 | model1 = SelectKBest(mutual_info_classif, k=3)
22 | ret = model1.fit_transform(iris.data, iris.target)
23 |
24 | print(model1.scores_)
25 | print(model1.pvalues_)
26 | # chi2test1(iris.target, iris.data[:,0])
27 | print(ret.shape)
28 |
29 |
30 | def chi2test1(y, x):
31 | con_table = pd.crosstab(y, x)
32 | chi2, p, df, ex = chi2_contingency(con_table)
33 | print(chi2, p, df)
34 |
35 |
36 | def pca_demo():
37 | X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
38 | pca = PCA(n_components=1, svd_solver='arpack')
39 | pca.fit(X)
40 | print(pca.explained_variance_ratio_)
41 | print(pca.singular_values_)
42 | print(pca.transform([[2, 3]]))
43 |
44 |
45 | if __name__ == '__main__':
46 | # iris_feature_test()
47 | # iris = load_iris()
48 | # (iris.target, iris.data[:, 0])
49 | pca_demo()
50 |
--------------------------------------------------------------------------------
/backtester/Account.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 14:02
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : Account.py
7 |
8 | class Account(object):
9 | def __init__(self, init_cash=100000):
10 | self.transaction = list()
11 | self.fee = 0.0
12 | self.risk_ratio = [0.0]
13 | self.occupied_margin = list()
14 | self.market_value = init_cash
15 | self.available_margin = list()
16 | self.trade_market_values = [init_cash]
17 | self.settle_market_values = [init_cash]
18 |
19 | def add_transaction(self, val=[]):
20 | self.transaction.append(val)
21 |
22 | def cache_transaction(self):
23 | pass
24 |
25 | def update_fee(self, val):
26 | self.fee += val
27 |
28 | def update_risk_ratio(self, val):
29 | self.risk_ratio.append(val)
30 |
31 | def update_occupied_margin(self, val):
32 | self.occupied_margin.append(val)
33 |
34 | def update_market_value(self, trade_val, settle_val, fee):
35 | _trade_val = self.trade_market_values[-1]
36 | # print("old mkt val", _trade_val)
37 | self.trade_market_values.append(_trade_val - fee + trade_val)
38 | # print("new mkt val", self.trade_market_values[-1])
39 | _settle_val = self.settle_market_values[-1]
40 | # print("old mkt val 1", _settle_val)
41 | self.settle_market_values.append(_settle_val - fee + settle_val)
42 | # print("new mkt val 1", self.settle_market_values[-1])
43 |
44 | def update_available_margin(self, val):
45 | self.available_margin.append(val)
46 |
47 | def get_trade_market_values(self):
48 | return self.trade_market_values
49 |
50 | def get_settle_market_values(self):
51 | return self.settle_market_values
52 |
--------------------------------------------------------------------------------
/research/daily_models/com_analysis.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/3/15 13:33
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : com_analysis.py
7 |
8 | import matplotlib.pyplot as plt
9 | import uqer
10 | import pprint
11 | import numpy as np
12 | from uqer import DataAPI
13 |
14 | uqer_client = uqer.Client(token="6aa0df8d4eec296e0c25fac407b332449112aad6c717b1ada315560e9aa0a311")
15 |
16 |
17 | # 返回相应品种主力合约阶段的日频率 价格波动,收盘价涨跌幅波动,结算价涨跌幅波动,分钟收盘价波动均值
18 | def get_daily_vol(start_date='', end_date='', product_id='RB'):
19 | df = DataAPI.MktFutdGet(secID=u"", ticker=u"", tradeDate=u"", beginDate=start_date, endDate=end_date, exchangeCD="",
20 | field=u"", pandas="1")
21 | df = df[df.mainCon == 1]
22 | # df_m = df[df.ticker == 'M2205']
23 | df_rb = df[df.contractObject == product_id]
24 | df_hc = df[df.contractObject == 'HC']
25 | # _flag = [item.startswith('m') for item in df['ticker']]
26 | # df_m = df[_flag]
27 | # print(df_hc.head().T)
28 | # print(df_hc['CHG1'].mean(), df_hc['CHG1'].std())
29 | # print(df_hc['closePrice'].mean(), df_hc['closePrice'].std())
30 | # plt.plot(df_rb['CHG'])
31 | # plt.show()
32 | # plt.plot(df_rb['closePrice'])
33 | # # df_hc['ClosePrice']
34 | # plt.show()
35 | diff = df_rb['closePrice'] - df_hc['closePrice']
36 | diff = [item - list(df_hc['closePrice'])[idx] for idx, item in enumerate(df_rb['closePrice'])]
37 | plt.plot(diff)
38 | plt.show()
39 | min_std = []
40 | for d in df_rb['tradeDate']:
41 | df_min = DataAPI.FutBarHistOneDay2Get(instrumentID=u"rb2205", date=d, unit=u"1", field=u"", pandas="1")
42 | # print(d, df_min['closePrice'].std())
43 | min_std.append(df_min['closePrice'].std())
44 | return df_rb['closePrice'].std(), df_rb['CHG'].std(), df_rb['CHG1'].std(), sum(min_std) / len(min_std)
45 |
46 |
47 | if __name__ =="__main__":
48 | daily_close_std, daily_chg_std, daily_chg1_std, min_std = get_daily_vol("20220208", "20220314", 'RB')
49 | print('daily close std:',daily_close_std,'daily close chg std:', daily_chg_std,'daily clear chg std:', daily_chg1_std, 'min_std:', min_std)
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 | cache/
131 | results/
132 | strategy/
133 | conf/.editorconfig
134 | logs/
135 |
--------------------------------------------------------------------------------
/research/data_process/DataProcess.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/29 10:33
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : DataProcess.py
7 |
8 | from ...utils.define import *
9 | import os
10 | import pandas as pd
11 | from datetime import datetime
12 | import shutil
13 |
14 |
15 | def cache_depth_mkt():
16 | lst = os.listdir('C:\projects\pycharm\option_future_research\cache\mkt_pycache')
17 | for item in lst:
18 | _tmp = item.split('.')[0].split('_')
19 | try:
20 | os.system('mkdir C:\projects\pycharm\option_future_research\cache\\{0}'.format(_tmp[-1]))
21 | shutil.copy('C:\projects\pycharm\option_future_research\cache\mkt_pycache\{0}'.format(item),
22 | 'C:\projects\pycharm\option_future_research\cache\{0}\{1}.csv'.format(_tmp[-1], _tmp[0]))
23 | except Exception as ex:
24 | print(ex)
25 | # os.system('rm -r C:\projects\pycharm\option_future_research\cache\\mkt_pycache')
26 |
27 |
28 | def prepare_test_mkt():
29 | with open('../../utils/trade_dates.txt', 'r') as f:
30 | lines = f.readlines()
31 | dates = [item.strip() for item in lines]
32 | # create folders
33 | for item in dates:
34 | try:
35 | os.system('mkdir C:\projects\pycharm\option_future_research\cache\\{0}'.format(item))
36 | except Exception as ex:
37 | print("folder exist", item)
38 |
39 |
40 | def transaction_analysis():
41 | df = pd.read_csv('results/trans_m2105_20210104.csv')
42 | long_open_ts = list(df[df.direction == 0]['timestamp'])
43 | long_close_ts = list(df[df.direction == 1]['timestamp'])
44 | short_open_ts = list(df[df.direction == 10]['timestamp'])
45 | short_close_ts = list(df[df.direction == 11]['timestamp'])
46 |
47 | assert len(long_open_ts) == len(long_close_ts)
48 | assert len(short_open_ts) == len(short_close_ts)
49 |
50 | long_tran_time = []
51 | for idx, item in enumerate(long_open_ts):
52 | _sec = datetime.strptime(long_close_ts[idx], '%H:%M:%S') - datetime.strptime(item, '%H:%M:%S')
53 | long_tran_time.append(_sec.seconds)
54 | short_tran_time = []
55 | for idx, item in enumerate(short_open_ts):
56 | _sec = datetime.strptime(short_close_ts[idx], '%H:%M:%S') - datetime.strptime(item, '%H:%M:%S')
57 | print(idx, _sec.seconds, item, short_close_ts[idx])
58 | short_tran_time.append(_sec.seconds)
59 | # print(long_tran_time)
60 | # print(short_tran_time)
61 | # print(max(long_tran_time)/60, min(long_tran_time))
62 | # print(max(short_tran_time)/60, min(short_tran_time))
63 |
64 |
65 | if __name__ == '__main__':
66 | cache_depth_mkt()
67 |
--------------------------------------------------------------------------------
/backtester/Position.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 14:03
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : Position.py
7 | import copyreg
8 | from collections import defaultdict
9 | import utils.define as define
10 | import copy
11 |
12 |
13 | class Position(object):
14 | def __init__(self):
15 | self.position = defaultdict(list)
16 |
17 | def open_position(self, instrument_id, long_short, price, timestamp, vol):
18 | self.position[instrument_id].append([long_short, price, timestamp, vol])
19 |
20 | def close_position(self, instrument_id, long_short, price, timestamp):
21 | _lst = self.position.get(instrument_id) or []
22 | for item in _lst:
23 | if item[0] == long_short:
24 | _lst.remove(item)
25 |
26 | def update_position(self, instrument_id, long_short, price, timestamp, vol, order_type):
27 | print('update position:long_short=>', long_short, 'price=>', price, 'ts=>', timestamp, 'vol=>', vol,
28 | 'order type=>', order_type)
29 | _lst = self.position.get(instrument_id) or []
30 | update_lst = []
31 |
32 | print("before update position:", self.position[instrument_id])
33 | if not _lst:
34 | print('pos not exist,add possition')
35 | # self.open_position(instrument_id, long_short, price, timestamp, vol)
36 | update_lst.append([long_short, price, timestamp, vol])
37 | else:
38 | for item in _lst:
39 | print("before update position:", item, instrument_id, long_short, price, vol, order_type)
40 | _direction, _price, _ts, _vol = item
41 | if _direction == define.LONG and order_type == define.LONG_OPEN:
42 | item[0] = define.LONG
43 | item[1] = (_price * _vol + price * vol) / (_vol + vol)
44 | item[2] = timestamp
45 | item[3] = _vol + vol
46 | update_lst.append(item)
47 | elif _direction == define.LONG and order_type == define.LONG_CLOSE:
48 | assert _vol >= vol
49 | if _vol == vol:
50 | continue
51 | item[1] = 0.0 if _vol == vol else (_price * _vol - price * vol) / (_vol - vol)
52 | item[2] = timestamp
53 | item[3] = _vol - vol
54 | elif _direction == define.SHORT and order_type == define.SHORT_OPEN:
55 | item[0] = define.SHORT
56 | item[1] = (_price * _vol + price * vol) / (_vol + vol)
57 | item[2] = timestamp
58 | item[3] = _vol + vol
59 | update_lst.append(item)
60 | elif _direction == define.SHORT and order_type == define.SHORT_CLOSE:
61 | assert _vol >= vol
62 | if _vol == vol:
63 | continue
64 | item[1] = 0.0 if _vol == vol else (_price * _vol - price * vol) / (_vol - vol)
65 | item[2] = timestamp
66 | item[3] = _vol - vol
67 | else:
68 | update_lst.append(item)
69 | self.position[instrument_id] = update_lst
70 | print("after update position:", self.position[instrument_id])
71 |
72 | def get_position(self, instrument_id):
73 | _lst = self.position.get(instrument_id) or []
74 | ret = []
75 | for item in _lst:
76 | if item[-1] > 0:
77 | ret.append(item)
78 | return ret
79 |
80 | def get_position_side(self, instrument_id, side):
81 | _pos_lst = self.position.get(instrument_id)
82 | if _pos_lst:
83 | for item in _pos_lst:
84 | if item[0] == side:
85 | return item
86 | return
87 |
--------------------------------------------------------------------------------
/strategy/T0Signal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 16:32
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : T0Signal.py
7 |
8 | from strategy.Signal import Signal
9 | import utils.define as define
10 | from backtester.Factor import Factor
11 | from backtester.Position import Position
12 |
13 |
14 | class TOSignal(Signal):
15 | def __init__(self, factor, position):
16 | super().__init__(factor, position)
17 | self.is_available = True
18 |
19 | def __call__(self, *args, **kwargs):
20 | assert isinstance(self.factor, Factor)
21 | assert isinstance(self.position, Position)
22 | params = kwargs.get('params')
23 | volitility = float(params.get('volitility')) or 20.0
24 | k1 = float(params.get('k1')) or 1.0
25 | k2 = float(params.get('k2')) or 1.0
26 | instrument_id = params.get('instrument_id')
27 | # adjust by multiplier
28 | stop_profit = float(params.get('stop_profit')) or 5.0
29 | stop_loss = float(params.get('stop_loss')) or 20.0
30 | multiplier = int(params.get('multiplier')) or 10
31 |
32 | open_fee = float(params.get('open_fee')) or 1.51
33 | close_to_fee = float(params.get('close_t0_fee')) or 0.0
34 | fee = (open_fee + close_to_fee) / multiplier
35 | start_tick = int(params.get('start_tick')) or 2
36 | long_lots_limit = int(params.get('long_lots_limit')) or 1
37 | short_lots_limit = int(params.get('short_lots_limit')) or 1
38 | slope_upper = float(params.get('slope_upper')) or 1.0
39 | slope_lower = float(params.get('slope_lower')) or -1.0
40 | _position = self.position.get_position(instrument_id)
41 |
42 | if params.get('tick')[2].split()[1]<'21:00:00.000':
43 | print('check')
44 |
45 | if len(self.factor.last_price) < start_tick:
46 | return define.NO_SIGNAL
47 | _long, _short, long_price, short_price = 0, 0, 0.0, 0.0
48 |
49 | if _position:
50 | for item in _position:
51 | if item[0] == define.LONG:
52 | long_price = item[1]
53 | _long += 1
54 | elif item[0] == define.SHORT:
55 | short_price = item[1]
56 | _short += 1
57 | # if factor.last_price[-1] > factor.last_price[-2] and factor.last_price[-1] > factor.vwap[
58 | # -1] + k1 * volitility and not _long:
59 | if (self.factor.slope[-1] > self.factor.slope[-2]) and (self.factor.slope[-2] < self.factor.slope[-3]) and abs(
60 | self.factor.slope[-1]) > slope_upper and _short < short_lots_limit:
61 | # if (factor.slope[-1] > factor.slope[-2]) and abs(
62 | # factor.slope[-1]) > 0.8 and not _long:
63 | # print(factor.last_price, factor.slope)
64 | # print(_short, short_lots_limit)
65 | return define.SHORT_OPEN
66 | # if factor.last_price[-1] < factor.last_price[-2] and factor.last_price[-1] < factor.vwap[
67 | # -1] + k2 * volitility and not _short:
68 | if (self.factor.slope[-1] < self.factor.slope[-2]) and (self.factor.slope[-2] > self.factor.slope[-3]) and abs(
69 | self.factor.slope[-1]) > slope_upper and _long < long_lots_limit:
70 | # if (factor.slope[-1] < factor.slope[-2]) and abs(
71 | # factor.slope[-1]) > 0.8 and not _short:
72 |
73 | # print(factor.last_price,factor.slope)
74 | return define.LONG_OPEN
75 | # if factor.last_price[-1] < factor.last_price[-2] and _long and factor.last_price[-1] > long_price + stop_profit:
76 | if _long and (self.factor.last_price[-1] > long_price + stop_profit + fee or
77 | self.factor.last_price[-1] < long_price - stop_loss - fee):
78 | return define.LONG_CLOSE
79 | # if factor.last_price[-1] > factor.last_price[-2] and _short and short_price > factor.last_price[-1] + stop_profit:
80 | if _short and (
81 | short_price > self.factor.last_price[-1] + stop_profit + fee or self.factor.last_price[
82 | -1] > short_price + stop_loss + fee):
83 | return define.SHORT_CLOSE
84 | return define.NO_SIGNAL
85 |
--------------------------------------------------------------------------------
/utils/utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/29 15:50
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : utils.py
7 |
8 | import time
9 | import json
10 | import math
11 | import uqer
12 | import pprint
13 | import numpy as np
14 | from uqer import DataAPI
15 | import matplotlib.pyplot as plt
16 | from sklearn.linear_model import LinearRegression
17 | from sklearn import metrics
18 | import utils.define as define
19 | from sklearn.feature_selection import SelectKBest
20 | from sklearn.feature_selection import mutual_info_classif
21 | from sklearn.linear_model import LogisticRegression
22 | from scipy.stats import rankdata
23 | import talib as ta
24 | import pandas as pd
25 | import statsmodels.api as sm
26 | from editorconfig import get_properties, EditorConfigError
27 | import logging
28 | import os
29 | import gc
30 |
31 | try:
32 | print("current path in utils:", os.getcwd())
33 | # _conf_file = os.path.join(os.path.abspath(os.pardir), define.CONF_DIR,
34 | # define.CONF_FILE_NAME)
35 | _conf_file = os.path.join(os.path.abspath(os.getcwd()), define.CONF_DIR, define.CONF_FILE_NAME)
36 | options = get_properties(_conf_file)
37 | except EditorConfigError:
38 | logging.warning("Error getting EditorConfig propterties", exc_info=True)
39 | else:
40 | for key, value in options.items():
41 | # _config = '{0},{1}:{2}'.format(_config, key, value)
42 | print("{0}:{1}".format(key, value))
43 | print(options.get('uqer_token'))
44 | uqer_client = uqer.Client(token=options.get('uqer_token'))
45 |
46 |
47 | def get_instrument_ids(start_date='20210901', end_date='20210930', product_id='RB'):
48 | df = DataAPI.MktFutdGet(endDate=end_date, beginDate=start_date, pandas="1")
49 | df = df[df.mainCon == 1]
50 | df = df[df.contractObject == product_id.upper()][['ticker', 'tradeDate', 'exchangeCD']]
51 | return df
52 |
53 |
54 | def get_trade_dates(start_date='20110920', end_date='20210921'):
55 | df = DataAPI.TradeCalGet(exchangeCD=u"XSHG,XSHE", beginDate=start_date, endDate=end_date, isOpen=u"1",
56 | field=u"",
57 | pandas="1")
58 | df = df[df.isOpen == 1]
59 | return df
60 |
61 |
62 | def timeit(func):
63 | def timed(*args, **kwargs):
64 | ts = time.time()
65 | result = func(*args, **kwargs)
66 | te = time.time()
67 | # logger.info('%r (%r, %r) %2.2f sec' % (func.__name__, args, kwargs, te - ts))
68 | print('%r (%r, %r) %2.2f sec' % (func.__name__, args, kwargs, te - ts))
69 | return result
70 |
71 | return
72 |
73 |
74 | def test_time():
75 | for item in range(10):
76 | time.sleep(1)
77 |
78 |
79 | def is_trade(start_timestamp=None, end_timestamp=None, update_time=None):
80 | update_time = update_time.split()[1].split('.')[0]
81 | if update_time > start_timestamp and update_time < '22:59:30':
82 | return True
83 | elif update_time > '09:00:30' and update_time < end_timestamp:
84 | return True
85 | else:
86 | return False
87 | return False
88 |
89 |
90 | def get_path(ref_path_lst=[]):
91 | _path = os.path.join(os.path.abspath(os.getcwd()))
92 | # print(_path)
93 | for p in ref_path_lst:
94 | _path = os.path.join(_path, p)
95 | return _path
96 |
97 |
98 | def get_contract(instrument_id=''):
99 | df = DataAPI.FutuGet(secID=u"", ticker=instrument_id, exchangeCD=u"", contractStatus="", contractObject=u"",
100 | prodID="",
101 | field=u"", pandas="1")
102 | return df
103 |
104 |
105 | def get_mul_num(instrument_id=''):
106 | df = get_contract(instrument_id=instrument_id)
107 | return df['contMultNum'].values[0]
108 |
109 |
110 | def write_json_file(file_path='', data=None):
111 | # if data == None:
112 | # return
113 | with open(file_path, 'w') as outfile:
114 | j_data = json.dumps(data)
115 | outfile.write(j_data)
116 |
117 |
118 | def load_json_file(filepath=''):
119 | with open(filepath) as infile:
120 | contents = infile.read()
121 | return json.loads(contents)
122 |
123 |
124 | def get_trade_dates(start_date='20110920', end_date='20210921'):
125 | df = DataAPI.TradeCalGet(exchangeCD=u"XSHG,XSHE", beginDate=start_date, endDate=end_date, isOpen=u"1", field=u"",
126 | pandas="1")
127 | df = df[df.isOpen == 1]
128 | return df
129 |
130 |
131 | def _is_trading_time(time_str=''):
132 | if time_str > '09:00:00.000' and time_str <= '15:00:00.000' or time_str > '21:00:00.000' and time_str < '23:00:01.000':
133 | return True
134 | else:
135 | return False
136 |
137 |
138 | if __name__ == "__main__":
139 | start_ts = time.time()
140 | test_time()
141 | end_ts = time.time()
142 | print(end_ts - start_ts)
143 | # df = get_instrument_ids(start_date='20210901', end_date='20210930', product_id='RB')
144 | # print(df)
145 | df = get_contract(instrument_id='rb2201', exchangeCD=u"XSGE")
146 | print(df)
147 |
--------------------------------------------------------------------------------
/strategy/RegSignal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/28 16:15
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : RegSignal.py
7 |
8 | import numpy as np
9 | import pandas as pd
10 | from strategy.Signal import Signal
11 | import utils.define as define
12 | import utils.utils as utils
13 | from backtester.Factor import Factor
14 | from backtester.Position import Position
15 | import os
16 |
17 |
18 | class RegSignal(Signal):
19 | def __init__(self, factor, position, instrument_id=None, trade_date=None):
20 | super().__init__(factor, position)
21 | self.is_available = True
22 | trade_date_df = utils.get_trade_dates(start_date='20210101', end_date=trade_date)
23 | trade_date_df = trade_date_df[trade_date_df.exchangeCD == 'XSHE']
24 | trade_dates = list(trade_date_df['calendarDate'])
25 | prev_date = trade_dates[-2]
26 | _evalute_path = utils.get_path([define.RESULT_DIR, define.TICK_MODEL_DIR,
27 | 'model_evaluate.json'])
28 | _model_evaluate = utils.load_json_file(_evalute_path)
29 | _ret_lst = _model_evaluate.get('{0}_{1}'.format(instrument_id, trade_date.replace('-', ''))) or []
30 | if not _ret_lst:
31 | self.is_available = False
32 | return
33 | _ret_lst.sort(key=lambda x: x[1], reverse=True)
34 | mse, r2, date, predict_window, lag_window = _ret_lst[0]
35 | _path = utils.get_path(
36 | [define.RESULT_DIR, define.TICK_MODEL_DIR,
37 | 'pred_{0}_{1}_{2}_{3}.csv'.format(instrument_id, date.replace('-', ''), predict_window, lag_window)])
38 | df = pd.read_csv(_path)
39 | _pred_lst = sorted(list(df['pred']))
40 | self.map_dict = dict(zip([item.split()[1].split('.')[0] for item in df['UpdateTime']], df['pred']))
41 | _prev_path = utils.get_path(
42 | [define.RESULT_DIR, define.TICK_MODEL_DIR,
43 | 'pred_{0}_{1}_{2}_{3}.csv'.format(instrument_id, prev_date.replace('-', ''), predict_window, lag_window)])
44 | self.df_prev = pd.read_csv(_prev_path)
45 | _label_lst = sorted(list(self.df_prev['pred']))
46 | # self._ret_up = self.df_prev['label'].quantile(1 - 0.0001)
47 | # self._ret_down = self.df_prev['label'].quantile(0.002)
48 | # TODO CHECK HARDCODE
49 | self._ret_up = _label_lst[-50]
50 | self._ret_down = _label_lst[20]
51 | self._ret_up = 0.0005
52 | self._ret_down = -0.0005
53 | print('up ret:{0}, down ret:{1},up pred:{2}, down pred:{3}'.format(self._ret_up, _pred_lst[-50], self._ret_down,
54 | _pred_lst[50]))
55 |
56 | def __call__(self, *args, **kwargs):
57 | if not self.is_available:
58 | return define.NO_SIGNAL
59 |
60 | params = kwargs.get('params')
61 | _k = params.get('tick')[2].split()[1].split('.')[0]
62 | _v = self.map_dict.get(_k) or 0.0
63 | _up_ratio = float(params.get('ret_up_ratio')) or 0.0015
64 | _down_ratio = float(params.get('ret_down_ratio')) or 0.001
65 |
66 | # _ret_down = float(params.get('ret_down_ratio')) or 0.001
67 | _long, _short, long_price, short_price = 0, 0, 0.0, 0.0
68 | instrument_id = params.get('instrument_id')
69 | start_tick = int(params.get('start_tick')) or 2
70 | stop_profit = float(params.get('stop_profit')) or 5.0
71 | stop_loss = float(params.get('stop_loss')) or 20.0
72 | multiplier = int(params.get('multiplier')) or 10
73 | long_lots_limit = int(params.get('long_lots_limit')) or 1
74 | short_lots_limit = int(params.get('short_lots_limit')) or 1
75 |
76 | open_fee = float(params.get('open_fee')) or 1.51
77 | close_to_fee = float(params.get('close_t0_fee')) or 0.0
78 | fee = (open_fee + close_to_fee) / multiplier
79 | _position = self.position.get_position(instrument_id)
80 | if _position:
81 | for item in _position:
82 | if item[0] == define.LONG:
83 | long_price = item[1]
84 | _long += 1
85 | elif item[0] == define.SHORT:
86 | short_price = item[1]
87 | _short += 1
88 | if _v >= self._ret_up and _long < long_lots_limit and _v > 0:
89 | return define.LONG_OPEN
90 | elif _v <= self._ret_down and _short < short_lots_limit and _v < 0:
91 | return define.SHORT_OPEN
92 |
93 | if len(self.factor.last_price) < start_tick:
94 | return define.NO_SIGNAL
95 |
96 | if _long and (self.factor.last_price[-1] > long_price + stop_profit + fee or
97 | self.factor.last_price[-1] < long_price - stop_loss - fee):
98 | return define.LONG_CLOSE
99 | if _short and (
100 | short_price > self.factor.last_price[-1] + stop_profit + fee or self.factor.last_price[
101 | -1] > short_price + stop_loss + fee):
102 | return define.SHORT_CLOSE
103 | return define.NO_SIGNAL
104 |
--------------------------------------------------------------------------------
/utils/define.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 14:04
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : define.py
7 |
8 |
9 | NO_SIGNAL = -1
10 | LONG_OPEN = 0
11 | LONG_CLOSE = 1
12 | SHORT_OPEN = 2
13 | SHORT_CLOSE = 3
14 | LONG = 4
15 | SHORT = 5
16 | STOP = 6
17 | PLT_START = 3
18 | PLT_END = -10
19 | TICK_SIZE = 41400
20 | TICK = 1
21 |
22 | cols = ["InstrumentID", "LastPrice", "OpenPrice", "HighestPrice", "LowestPrice", "Volume", "Turnover", "OpenInterest",
23 | "UpperLimitPrice", "LowerLimitPrice", "UpdateTime",
24 | "UpdateMillisec",
25 | "BidPrice1", "BidVolume1", "AskPrice1", "AskVolume1", "BidPrice2", "BidVolume2", "AskPrice2", "AskVolume2",
26 | "BidPrice3", "BidVolume3",
27 | "AskPrice3", "AskVolume3", "BidPrice4", "BidVolume4", "AskPrice4", "AskVolume4", "BidPrice5", "BidVolume5",
28 | "AskPrice5", "AskVolume5"]
29 |
30 | selected_cols = ['InstrumentID', 'UpdateTime', 'Turnover', 'Volume', 'LastPrice', 'AskPrice1', 'AskVolume1',
31 | 'BidPrice1', 'BidVolume1']
32 |
33 | tb_cols = ["Exchange", "InstrumentID", "UpdateTime", "LastPrice", "OpenInterest", "InterestDiff", "Turnover",
34 | "Volume", "OpenVolume", "CloseVolume", "TransactionType", "Direction", "BidPrice1", "AskPrice1",
35 | "BidVolume1",
36 | "AskVolume1"]
37 |
38 | # skip_raw_cols = ['Exchange', 'InstrumentID', 'LastPrice', 'OpenInterest', 'InterestDiff',
39 | # 'Turnover', 'Volume', 'OpenVolume', 'CloseVolume', 'TransactionType', 'Direction',
40 | # 'BidPrice1', 'AskPrice1', 'BidVolume1', 'AskVolume1', 'vwap', 'vol_short', 'vol_long',
41 | # 'turnover_short', 'turnover_long', 'vwap_short', 'vwap_long', 'bs_vol', 'bs_vol_long',
42 | # 'bs_vol_short', 'bs_vol_diff', 'bs_tag', 'wap',
43 | # 'trenddiff', 'trend_long', 'dif', 'dea', 'turnover_ls_ratio',
44 | # 'vwap_ls_ratio', 'aoi', 'oi']
45 | # remove from VIF, to be added with other calculation
46 |
47 |
48 | # skip_raw_cols = ['Exchange', 'InstrumentID']
49 |
50 | # skip_raw_cols_normalized = ['Exchange', 'InstrumentID', 'OpenInterest', 'InterestDiff',
51 | # 'Turnover', 'Volume', 'OpenVolume', 'CloseVolume', 'TransactionType', 'Direction',
52 | # 'BidPrice1', 'AskPrice1', 'BidVolume1', 'AskVolume1', 'vwap', 'vol_short', 'vol_long',
53 | # 'turnover_short', 'turnover_long', 'vwap_short', 'vwap_long', 'bs_vol', 'bs_vol_long',
54 | # 'bs_vol_short', 'bs_vol_diff', 'bs_tag']
55 |
56 | skip_raw_cols = ['Exchange', 'InstrumentID', 'TransactionType', 'Direction', 'BidPrice1',
57 | 'bs_tag',
58 | 'LastPrice', 'InterestDiff', 'OpenInterest', 'Turnover', 'Volume', 'OpenVolume', 'CloseVolume',
59 | 'AskPrice1',
60 | # norm value
61 | 'AskVolume1', 'BidVolume1', 'vwap', 'wap', 'volume_ls_diff', 'turnover_ls_diff', 'bs_vol_ls_diff',
62 | 'norm_turnover',
63 | # norm value
64 | 'norm_openvolume', 'norm_closevolume', 'norm_turnover_ls_diff', ] # norm value
65 |
66 | # normalized_cols = [('LastPrice', 'LastPrice'), ('wap', 'LastPrice'), ('OpenInterest', 'OpenInterest'),
67 | # ('vwap', 'LastPrice'), ('Volume', 'Volume'), ('BidPrice1', 'LastPrice'), ('AskPrice1', 'LastPrice')]
68 |
69 |
70 | BASE_DIR = 'option_future_research'
71 | RESULT_DIR = 'results'
72 | CONF_DIR = 'conf'
73 | FACTOR_DIR = 'factors'
74 | CONF_FILE_NAME = '.editorconfig'
75 | TICK_MODEL_DIR = 'tickmodels'
76 | TICK_MKT_DIR = 'C:\projects\l2mkt\FutAC_TickKZ_PanKou_Daily_202107'
77 | FACTOR_DIR = 'factors'
78 | CACHE_DIR = 'cache'
79 | BT_DIR = 't0backtest'
80 | daily_cache_name = 'cache/future_20210101_20210804.csv'
81 |
82 | MKT_MISSING_SKIP = 0.3
83 |
84 | exchange_map = {'XZCE': 'zc', 'XSGE': 'sc', 'XSIE': 'ine', 'XDCE': 'dc'}
85 |
86 | normalized_vals = {'LastPrice': 0, 'Volume': 0, 'OpenInterest': 0}
87 | normalized_refs = [('wap', 'LastPrice')]
88 | # normalized_refs = [('LastPrice', 'LastPrice'), ('OpenInterest', 'OpenInterest'), ('InterestDiff', 'Volume'),
89 | # ('Turnover', 'Turnover'), ('Volume', 'Volume'), ('OpenVolume', 'Volume'), ('CloseVolume', 'Volume'),
90 | # ('BidVolume1', 'Volume'), ('AskVolume1', 'Volume'), ('vwap', 'LastPrice'), ('wap', 'LastPrice'),
91 | # ('bs_tag', 'Volume'),
92 | # ('volume_ls_diff', 'Volume'), ('turnover_ls_diff', 'Turnover')]
93 |
94 | CLF_LABEL_NAME = 'label_clf_1'
95 | REG_LABEL_NAME = 'label_1'
96 |
97 | # train_cols = ['open_close_ratio', 'oi', 'oir', 'aoi', 'slope', 'cos', 'macd', 'dif', 'dea', 'bsvol_volume_0',
98 | # 'trend_1', 'bsvol_volume_1',
99 | # 'trend_ls_diff', 'trend_ls_ratio', 'volume_ls_ratio', 'turnover_ls_ratio', 'bs_vol_ls_ratio',
100 | # 'bsvol_volume_ls_ratio']
101 |
102 | # train_cols = ['slope']
103 | # train_cols = ['cos', 'macd', 'aoi', 'trend_0', 'log_return_0', 'bsvol_volume_0', 'trend_1',
104 | # 'bsvol_volume_1', 'trend_ls_ratio', 'volume_ls_ratio', 'log_return']
105 | # train_cols = ['log_return_0', 'log_return_1', 'slope']
106 | # train_cols = ['cos']
107 | train_cols = ['log_return', 'log_return_0', 'wap_log_return']
108 |
--------------------------------------------------------------------------------
/research/daily_models/basis_index.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/9/3 9:53
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : daily_models.py
7 |
8 | import math
9 | import uqer
10 | import datetime
11 | import pprint
12 | import pandas as pd
13 | import numpy as np
14 | from uqer import DataAPI
15 | import matplotlib.pyplot as plt
16 | from sklearn.model_selection import train_test_split
17 | from sklearn.linear_model import LinearRegression
18 | from sklearn import metrics
19 |
20 | uqer_client = uqer.Client(token="")
21 |
22 |
23 | # TODO line correlation
24 |
25 |
26 | def fetch_data(start_date='', end_date=''):
27 | DataAPI.TradeCalGet(exchangeCD=u"XSHG,XSHE", beginDate=start_date, endDate=end_date, isOpen=u"1",
28 | field=u"", pandas="1")
29 | df = DataAPI.MktMFutdGet(mainCon=u"1", contractMark=u"", contractObject=u"", tradeDate=u"", startDate=start_date,
30 | endDate=end_date, field=u"", pandas="1")
31 | df = df[df.exchangeCD == 'CCFX']
32 | df = df[df.mainCon == 1]
33 | ic_df = df[df.contractObject == 'IC'][['tradeDate', 'ticker']]
34 | if_df = df[df.contractObject == 'IF'][['tradeDate', 'ticker']]
35 | ih_df = df[df.contractObject == 'IH'][['tradeDate', 'ticker']]
36 |
37 | df_basis = DataAPI.MktFutIdxBasisGet(secID=u"", ticker=u"", beginDate=start_date, endDate=end_date, field=u"",
38 | pandas="1")
39 | ic_mkt = ic_df.set_index(['tradeDate', 'ticker']).join(df_basis.set_index(['tradeDate', 'ticker']),
40 | how='left').reset_index()
41 | if_mkt = if_df.set_index(['tradeDate', 'ticker']).join(df_basis.set_index(['tradeDate', 'ticker']),
42 | how='left').reset_index()
43 | ih_mkt = ih_df.set_index(['tradeDate', 'ticker']).join(df_basis.set_index(['tradeDate', 'ticker']),
44 | how='left').reset_index()
45 |
46 | icf_mkt = ic_mkt.set_index('tradeDate').join(other=if_mkt.set_index('tradeDate'), on='tradeDate', lsuffix='_ic',
47 | rsuffix='_if')
48 | df_mkt = icf_mkt.join(ih_mkt.set_index('tradeDate'))
49 | df_mkt = df_mkt.sort_values(by='tradeDate', ascending=True)
50 | df_mkt.reset_index().to_csv('fut_index_mkt_{0}_{1}.csv'.format(start_date, end_date), index=False)
51 |
52 |
53 | def factor_calculate(predict_windows=20, lag_windows=20):
54 | df_factor = pd.read_csv('fut_index_mkt.csv')
55 | df_factor['basis_ic_mean'] = df_factor[['basis_ic']].rolling(lag_windows).mean()
56 | df_factor['basis_if_mean'] = df_factor[['basis_if']].rolling(lag_windows).mean()
57 | df_factor['basis_ih_mean'] = df_factor[['basis']].rolling(lag_windows).mean()
58 | df_factor['basis_ic_pct_annual'] = (df_factor['basis_ic_mean'] / df_factor['closeIndex_ic']) * 12
59 | df_factor['basis_if_pct_annual'] = (df_factor['basis_if_mean'] / df_factor['closeIndex_if']) * 12
60 | df_factor['basis_ih_pct_annual'] = (df_factor['basis_ih_mean'] / df_factor['closeIndex']) * 12
61 |
62 | df_factor['index_return_ic'] = df_factor['closeIndex_ic'].rolling(2).apply(
63 | lambda x: math.log(list(x)[-1] / list(x)[0]))
64 | df_factor['index_return_if'] = df_factor['closeIndex_if'].rolling(2).apply(
65 | lambda x: math.log(list(x)[-1] / list(x)[0]))
66 | df_factor['index_return'] = df_factor['closeIndex'].rolling(2).apply(lambda x: math.log(list(x)[-1] / list(x)[0]))
67 | df_factor['index_return_ic_win'] = df_factor['index_return_ic'].rolling(predict_windows).sum().shift(
68 | 1 - predict_windows)
69 | df_factor['index_return_if_win'] = df_factor['index_return_if'].rolling(predict_windows).sum().shift(
70 | 1 - predict_windows)
71 | df_factor['index_return_win'] = df_factor['index_return'].rolling(predict_windows).sum().shift(1 - predict_windows)
72 | df_factor['icf_index_return_diff_win'] = df_factor['index_return_ic_win'] - df_factor['index_return_if_win']
73 | df_factor['icf_basis_annual_diff'] = df_factor['basis_ic_pct_annual'] - df_factor['basis_if_pct_annual']
74 | lst_rolling_corr = list(
75 | df_factor[['basis_ic_pct_annual', 'basis_if_pct_annual']].rolling(lag_windows).corr().reset_index()[
76 | 'basis_ic_pct_annual'])
77 |
78 | df_factor['icf_corr_win'] = lst_rolling_corr[1::2]
79 | df_factor[['basis_ic_pct_annual', 'basis_if_pct_annual', 'icf_corr_win']].rolling(lag_windows).corr()
80 | print(df_factor[['icf_index_return_diff_win', 'icf_basis_annual_diff', 'icf_corr_win']].corr())
81 | factor_cols = ['tradeDate', 'basis_ic_mean', 'basis_if_mean', 'basis_ic_pct_annual', 'basis_if_pct_annual',
82 | 'index_return_ic',
83 | 'index_return_if', 'index_return_ic_win',
84 | 'index_return_if_win', 'icf_index_return_diff_win', 'icf_basis_annual_diff', 'icf_corr_win']
85 | df_factor[factor_cols].to_csv('fut_index_factor_{0}_{1}.csv'.format(predict_windows, lag_windows), index=False)
86 |
87 |
88 | def model_process(predict_windows=40, lag_windows=20):
89 | df_factor = pd.read_csv('fut_index_factor_{0}_{1}.csv'.format(predict_windows, lag_windows))
90 | df_factor = df_factor.dropna(axis=0)
91 | cols = list(df_factor.columns)
92 | trade_dates = list(df_factor['tradeDate'])
93 | cols.remove('index_return_ic_win')
94 | cols.remove('index_return_if_win')
95 | cols.remove('tradeDate')
96 | df_corr = df_factor[cols].corr()
97 | df_corr.index = list(df_corr.columns)
98 | df_corr.to_csv('corr_{0}_{1}.csv'.format(predict_windows, lag_windows))
99 | cols.remove('icf_index_return_diff_win')
100 | X = df_factor[cols]
101 | y = df_factor['icf_index_return_diff_win']
102 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=None)
103 |
104 | with open('model_evaluation.txt', 'a') as f:
105 | print('Test results for pred_win:{0}, lag_win:{1}'.format(predict_windows, lag_windows))
106 | f.write('Test results for pred_win:{0}, lag_win:{1}\n'.format(predict_windows, lag_windows))
107 | lin_reg_model = LinearRegression()
108 | lin_reg_model.fit(X_train, y_train)
109 | print('intercept:', lin_reg_model.intercept_)
110 | f.write('coef_:{0}\n'.format(list(zip(cols, lin_reg_model.coef_))))
111 | print('coef_:{0}', list(zip(cols, lin_reg_model.coef_)))
112 | f.write('coef_:{0}\n'.format(list(zip(cols, lin_reg_model.coef_))))
113 | y_pred = lin_reg_model.predict(X_test)
114 | print('rmse:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)), 'r2:', metrics.r2_score(y_test, y_pred))
115 | f.write('rmse:{0}\n r2:{1}\n'.format(np.sqrt(metrics.mean_squared_error(y_test, y_pred)),
116 | metrics.r2_score(y_test, y_pred)))
117 | fig = plt.figure()
118 | ax1 = fig.add_subplot(111)
119 | ax1.plot(df_factor['icf_index_return_diff_win'],
120 | label="IC IF index log return diff in predict windows {0}".format(predict_windows))
121 | ax1.legend()
122 |
123 | ax2 = ax1.twinx()
124 | ax2.plot(df_factor['icf_basis_annual_diff'], 'r',
125 | label='IC IF annual basis pct diff in lag windows {0}'.format(lag_windows))
126 | ax2.legend()
127 | _idx_lst = list(range(df_factor.shape[0]))
128 | ax1.set_xticks(_idx_lst[::120])
129 | trade_date_str = [item.replace('-', '') for item in trade_dates]
130 | ax1.set_xticklabels(trade_date_str[::120], rotation=30)
131 | plt.savefig('icf return diff.jpg')
132 |
133 |
134 | if __name__ == '__main__':
135 | # fetch_data('20150103', '20210902')
136 | predict_windows_lst = [60]
137 | lag_windows_lst = [120]
138 | for pred_win in predict_windows_lst:
139 | for lag_win in lag_windows_lst:
140 | factor_calculate(predict_windows=pred_win, lag_windows=lag_win)
141 | model_process(predict_windows=pred_win, lag_windows=lag_win)
142 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/6/22 21:00
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : main.py
7 |
8 |
9 | from backtester.BackTester import backtesting
10 | from editorconfig import get_properties, EditorConfigError
11 | import utils.define as define
12 | import utils.utils as utils
13 | from research.tick_models.ModelProcess import *
14 | import logging
15 | import hashlib
16 | import time
17 | import gc
18 | import os
19 | import pandas as pd
20 | import matplotlib.pyplot as plt
21 | import pprint
22 |
23 | logging.basicConfig(filename='logs/{0}.txt'.format(os.path.split(__file__)[-1].split('.')[0]), level=logging.DEBUG)
24 | logger = logging.getLogger()
25 |
26 |
27 | def data_visialize(n_records=100):
28 | df = pd.read_csv('C:\\projects\\pycharm\\option_future_research\\cache\\factors\\factor_rb2110_20210706.csv')
29 | print(df.shape)
30 | # df[['oir', 'label_1']].plot()
31 | # plt.show()
32 | # df.hist(bins=80, figsize=(9, 6))
33 | # df[['oir','oi','cos','label_0']].hist(bins=80, figsize=(9, 6))
34 | cols = list(df.columns)
35 | cols.remove('norm_bs_tag')
36 |
37 |
38 | # df[cols].hist()
39 |
40 | # sns.pairplot(df_final_features.iloc[:,2:], height=1.5)
41 |
42 | from random import sample
43 | # df0 = df[df.label_clf_1 == 0]
44 | # df1 = df[df.label_clf_1 == 1]
45 | #
46 | # num0 = df0.shape[0]
47 | # sample_0 = sample(list(range(num0)), int(num0 * 0.1))
48 | # df = df1.append(df0.iloc[sample_0])
49 | cols = df.columns
50 | for c in cols:
51 | df.plot.scatter(c, 'label_1')
52 | plt.show()
53 | # df.plot.scatter('realized_vol', 'future_vol')
54 | # df.plot.scatter('aratio', 'bratio')
55 | # df = df[df.label_clf_1 != 0]
56 | # df.plot.scatter('oir', 'label_1')
57 | # plt.xlim(-50,50)
58 | plt.show()
59 |
60 | # x_idx = list(range(n_records))
61 | #
62 | # fig = plt.figure()
63 | # ax = fig.add_subplot(111)
64 | # ax.plot(x_idx, list(df['cos'])[:n_records], '-', label='oir')
65 | # # ax.plot(time, Rn, '-', label='Rn')
66 | # ax2 = ax.twinx()
67 | # ax2.plot(x_idx, list(df['price_chg_1'])[:n_records], '-r', label='label')
68 | # ax.legend(loc=0)
69 | # ax.grid()
70 | # # ax.set_xlabel("Time (h)")
71 | # # ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
72 | # # ax2.set_ylabel(r"Temperature ($^\circ$C)")
73 | # ax2.set_ylim(-15, 15)
74 | # ax.set_ylim(-2, 2)
75 | # ax2.legend(loc=0)
76 | # # plt.savefig('0.png')
77 | # plt.show()
78 |
79 |
80 | def train_models(start_date: str = '20210701', end_date: str = '20210730', product_id: str = 'rb'):
81 | """
82 |
83 | :param start_date:
84 | :param end_date:
85 | :param product_id:
86 | :return:
87 | """
88 | trade_date_df = utils.get_trade_dates(start_date=start_date, end_date=end_date)
89 | trade_date_df = trade_date_df[trade_date_df.exchangeCD == 'XSHE']
90 | trade_dates = list(trade_date_df['calendarDate'])
91 | train_days = 4
92 | num_date = len(trade_dates)
93 | idx = 0
94 | predict_window_lst = [10,20] # 10s, 30s,1min,5min,10min-- 10s
95 | lag_window_lst = [10, 60] # 10s, 30s,1min,5min,
96 |
97 | print(
98 | 'train for predict windows:{0} and lag_windows:{1}'.format(predict_window_lst, lag_window_lst))
99 | lst_score = []
100 | while idx < num_date - train_days:
101 | print(trade_dates[idx], trade_dates[idx + train_days])
102 | # train_model_reg_without_feature_preselect(predict_windows=predict_window_lst,
103 | # lag_windows=lag_window_lst,
104 | # top_k_features=20,
105 | # start_date=trade_dates[idx],
106 | # end_date=trade_dates[idx + train_days],
107 | # train_days=train_days, product_id=product_id.upper())
108 |
109 | ret_scores = train_model_reg(predict_windows=predict_window_lst, lag_windows=lag_window_lst,
110 | start_date=trade_dates[idx],
111 | end_date=trade_dates[idx + train_days],
112 | top_k_features=-10, train_days=train_days, product_id=product_id.upper())
113 | # ret_scores = train_model_clf(predict_windows=predict_window_lst, lag_windows=lag_window_lst,
114 | # start_date=trade_dates[idx],
115 | # end_date=trade_dates[idx + train_days],
116 | # top_k_features=-10, train_days=train_days, product_id=product_id.upper())
117 | pprint.pprint(ret_scores)
118 | lst_score.append(ret_scores)
119 | idx += 1
120 | gc.collect()
121 | # df_score = pd.DataFrame(lst_score, columns=list(lst_score[0].keys()))
122 | # _train_results_file = os.path.join(os.path.abspath(os.pardir), define.BASE_DIR, define.RESULT_DIR,
123 | # define.TICK_MODEL_DIR,
124 | # 'model_evaluation_{0}.csv'.format(product_id))
125 | # df_score.to_csv(_train_results_file, index=False)
126 |
127 |
128 | def backtest(start_date: str = '20210707', end_date: str = '20210709', product_id: str = 'rb',
129 | strategy_name: str = 'T0Signal'):
130 | """
131 | :param start_date:
132 | :param end_date:
133 | :param product_id:
134 | :return:
135 | """
136 | trade_date_df = utils.get_trade_dates(start_date=start_date, end_date=end_date)
137 | trade_date_df = trade_date_df[trade_date_df.exchangeCD == 'XSHE']
138 | trade_dates = list(trade_date_df['calendarDate'])
139 |
140 | total_return, total_fee, precision = 0.0, 0.0, []
141 | backtesting_config = ''
142 | multiplier = 10
143 | margin_rate = 0.1
144 | try:
145 | _conf_file = utils.get_path([define.CONF_DIR, define.CONF_FILE_NAME])
146 | print("conf file in main:", _conf_file)
147 | options = get_properties(_conf_file)
148 | except EditorConfigError:
149 | logging.warning("Error getting EditorConfig propterties", exc_info=True)
150 | else:
151 | for key, value in options.items():
152 | backtesting_config = '{0},{1}:{2}'.format(backtesting_config, key, value)
153 | _ret_path = utils.get_path([define.RESULT_DIR, define.BT_DIR, 'results_{0}.txt'.format(product_id)])
154 | f = open(_ret_path, "a")
155 | result_fname_digest = hashlib.sha256(bytes(backtesting_config, encoding='utf-8')).hexdigest()
156 | print('digest', result_fname_digest)
157 | f.write("{0}:{1}\n".format(backtesting_config, result_fname_digest))
158 | dates = ['20210708']
159 | trans_lst = []
160 | for idx, trade_date in enumerate(trade_dates):
161 | print('start back test for date:{0}'.format(trade_date))
162 | start_ts = time.time()
163 | ret = backtesting(product_id=product_id, trade_date=trade_date.strip().replace('-', ''),
164 | signal_name=strategy_name, result_fname_digest=result_fname_digest, options=options)
165 | end_ts = time.time()
166 | print("back testing time:", end_ts - start_ts)
167 | if ret:
168 | _total_return, _total_fee, _precision, _transaction = ret
169 | total_return += _total_return
170 | total_fee += _total_fee
171 | [item.append(trade_date) for item in _transaction]
172 | trans_lst.extend(_transaction)
173 |
174 | trans_df = pd.DataFrame(trans_lst,
175 | columns=['idx', 'instrument_id', 'direction', 'last_price', 'filled_price', 'filled_vol',
176 | 'open_price',
177 | 'fee', 'open_ts',
178 | 'close_ts', 'holding_time',
179 | 'return', 'trade_date'])
180 |
181 | trans_df['open_cost'] = trans_df['filled_vol'] * trans_df['open_price'] * multiplier * margin_rate
182 | trans_df['return_ratio'] = (trans_df['return'] / trans_df['open_cost']) * 10000
183 | trans_df['return_cut'] = pd.cut(trans_df['return_ratio'], 5)
184 | group_cnt = trans_df[trans_df.holding_time > 0].groupby('return_cut')[['idx']].count()
185 | total_trans_num = len(group_cnt)
186 | group_ratio = [item / sum(group_cnt['idx']) * 100 for item in group_cnt['idx']]
187 | # trans_df['return_count'] = list(group_cnt['idx'])
188 | _idx_lst = list(range(len(group_cnt)))
189 | xtick_labels = group_cnt.index
190 | fig = plt.figure()
191 | ax1 = fig.add_subplot(111)
192 | ax1.bar(_idx_lst, group_ratio)
193 | ax1.set_xticks(_idx_lst[::1])
194 | plt.xticks(rotation=75)
195 | plt.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.3)
196 | min_lst = []
197 | ax1.set_xticklabels(xtick_labels[::1])
198 | _bt_evaluation_path = utils.get_path(
199 | [define.RESULT_DIR, define.BT_DIR, '{0}_{1}_{2}.jpg'.format(product_id, start_date, end_date)])
200 | plt.savefig(_bt_evaluation_path)
201 | _trans_path = utils.get_path(
202 | [define.RESULT_DIR, define.BT_DIR, 'trans_{0}_{1}_{2}.csv'.format(product_id, start_date, end_date)])
203 | trans_df.to_csv(_trans_path, index=False)
204 | print(total_return, total_fee, precision)
205 |
206 |
207 | if __name__ == "__main__":
208 | # data_visialize()
209 | start_date = '20210701'
210 | bt_start_date = '20210707'
211 | end_date = '20210709'
212 | product_id = 'm'
213 | # train_models(start_date=start_date, end_date=end_date, product_id=product_id)
214 | backtest(start_date=bt_start_date, end_date=end_date, product_id=product_id, strategy_name='ClfSignal')
215 | # data_visialize()
216 |
--------------------------------------------------------------------------------
/research/tick_models/FactorProcess.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/10/25 14:11
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : FactorProcess.py
7 |
8 | import math
9 | import numpy as np
10 | import talib as ta
11 | import pandas as pd
12 | import logging
13 | import os
14 | import matplotlib.pyplot as plt
15 |
16 | import utils.define as define
17 | import utils.utils as utils
18 |
19 | logging.basicConfig(filename='logs/{0}.txt'.format(os.path.split(__file__)[-1].split('.')[0]), level=logging.DEBUG)
20 | logger = logging.getLogger()
21 |
22 |
23 | def _normalized_by_base(x: list, normalized_dict: dict, key: str) -> list:
24 | """
25 |
26 | :param x:
27 | :param normalized_dict:
28 | :param key:
29 | :return:
30 | """
31 | _normalized_base = normalized_dict.get(key)
32 | if not _normalized_base:
33 | return []
34 | else:
35 | try:
36 | return [float(item) / _normalized_base for item in x]
37 | except Exception as ex:
38 | print('error for norm cal for col:{0} with error:{1}'.format(key, ex))
39 | return []
40 |
41 |
42 | def _cal_turning_item(x: list) -> tuple:
43 | if not x:
44 | return 0, np.nan
45 | if len(x) == 1:
46 | return 0, np.nan
47 | if len(x) == 2:
48 | return -1, x[0]
49 | try:
50 | if (x[-1] - x[-2]) * (x[-2] - x[-3]) > 0:
51 | return -2, x[0]
52 | else:
53 | return -1, x[1]
54 | except Exception as ex:
55 | raise ValueError("Error to cal turning with error:{0}".format(ex))
56 |
57 |
58 | def log_return(x):
59 | return np.log(x).diff()
60 |
61 |
62 | def realized_volatility(x):
63 | return np.sqrt(np.sum(x ** 2))
64 |
65 |
66 | def count_unique(x):
67 | return len(np.unique(x))
68 |
69 |
70 | def cal_turning(x: list) -> tuple:
71 | idx_lst = []
72 | val_lst = []
73 | if len(x) == 1:
74 | idx_lst.append(0)
75 | val_lst.append(np.nan)
76 | elif len(x) == 2:
77 | _idx, _val = _cal_turning_item(x)
78 | return [0], [_val]
79 | else:
80 | _len = len(x)
81 | idx_lst.append(0)
82 | val_lst.append(np.nan)
83 | idx_lst.append(0)
84 | val_lst.append(x[0])
85 | _idx, _val = _cal_turning_item(x)
86 | idx_lst.append(2 + _idx)
87 | val_lst.append(_val)
88 | for idx in range(3, _len, 1):
89 | _idx, _val = _cal_turning_item(x[idx - 3:idx])
90 | idx_lst.append(idx + _idx)
91 | val_lst.append(_val)
92 | return idx_lst, val_lst
93 |
94 |
95 | def cal_slope(x: list, turn_idx: list, turn_val: list) -> list:
96 | assert len(x) == len(turn_idx)
97 | assert len(x) == len(turn_val)
98 | ret = []
99 | for idx, item in enumerate(x):
100 | if idx == 0:
101 | ret.append(np.nan)
102 | continue
103 | ret.append((x[idx] - turn_val[idx]) / (idx - turn_idx[idx]))
104 | return ret
105 |
106 |
107 | def cal_cos(x: list, turn_idx: list, turn_val: list) -> list:
108 | assert len(x) == len(turn_idx)
109 | assert len(x) == len(turn_val)
110 | ret = []
111 | for idx, item in enumerate(x):
112 | if idx == 0:
113 | ret.append(0)
114 | continue
115 | a = np.array([idx - turn_idx[idx], item - turn_val[idx]])
116 | b = np.array([0, 1])
117 | ret.append(a.dot(b) / (np.linalg.norm(a) * np.linalg.norm(b)))
118 | return ret
119 |
120 |
121 | def cal_oir(bid_price: list, bid_vol: list, ask_price: list, ask_vol: list, n_rows: int) -> tuple:
122 | """
123 | calculate order imbalance factors
124 | :param bid_price:
125 | :param bid_vol:
126 | :param ask_price:
127 | :param ask_vol:
128 | :param n_rows:
129 | :return:
130 | """
131 | v_b = [0]
132 | v_a = [0]
133 |
134 | for i in range(1, n_rows):
135 | v_b.append(
136 | 0 if bid_price[i] < bid_price[i - 1] else bid_vol[i] - bid_vol[i - 1] if bid_price[i] == bid_price[
137 | i - 1] else bid_vol[i])
138 |
139 | v_a.append(
140 | 0 if ask_price[i] < ask_price[i - 1] else ask_vol[i] - ask_vol[i - 1] if ask_price[i] == ask_price[
141 | i - 1] else ask_vol[i])
142 | lst_oi = []
143 | lst_oir = []
144 | lst_aoi = []
145 | for idx, item in enumerate(v_a):
146 | lst_oi.append(v_b[idx] - item)
147 | lst_oir.append((v_b[idx] - item) / (v_b[idx] + item) if v_b[idx] + item != 0 else 0.0)
148 | lst_aoi.append((v_b[idx] - item) / (ask_price[idx] - bid_price[idx]))
149 | return lst_oi, lst_oir, lst_aoi
150 |
151 |
152 | def plot_factor(x=[], y=[], labels=[], tick_step=120):
153 | fig = plt.figure()
154 | ax1 = fig.add_subplot(111)
155 | ax1.plot(x, label="factor")
156 | ax1.legend()
157 |
158 | ax2 = ax1.twinx()
159 | ax2.plot(y, 'r', label='log return')
160 | ax2.legend()
161 | if labels:
162 | _idx_lst = list(range(len(x)))
163 | ax1.set_xticks(_idx_lst[::120])
164 | trade_date_str = [item.replace('-', '') for item in labels]
165 | ax1.set_xticklabels(trade_date_str[::tick_step], rotation=30)
166 | plt.show()
167 |
168 |
169 | def get_factor(trade_date: str = "20210701", predict_windows: list = [1200], lag_windows: list = [10, 600],
170 | instrument_id: str = '', exchange_cd: str = '') -> pd.DataFrame:
171 | """
172 |
173 | :param trade_date:
174 | :param predict_windows:
175 | :param lag_long:
176 | :param lag_short:
177 | :param stop_profit:
178 | :param stop_loss:
179 | :param instrument_id:
180 | :param exchange_cd:
181 | :return:
182 | """
183 | _mul_num = utils.get_mul_num(instrument_id) or 1
184 | print(instrument_id, trade_date, define.exchange_map, exchange_cd, define.TICK_MODEL_DIR)
185 | _tick_mkt_path = os.path.join(define.TICK_MKT_DIR, define.exchange_map.get(exchange_cd),
186 | '{0}_{1}.csv'.format(instrument_id, trade_date.replace('-', '')))
187 | tick_mkt = pd.read_csv(_tick_mkt_path, encoding='gbk')
188 | tick_mkt.columns = define.tb_cols
189 |
190 | # filter trading timestamp
191 | _flag = [utils._is_trading_time(item.split()[-1]) for item in list(tick_mkt['UpdateTime'])]
192 | tick_mkt = tick_mkt[_flag]
193 |
194 | # for price/volume, if not define the normorlized base or defined as 0, we will ret it as the first tick
195 | open_dict = tick_mkt.head(1).to_dict('record')[0]
196 |
197 | tick_mkt['vwap'] = (tick_mkt['Turnover'] / tick_mkt['Volume']) / _mul_num
198 | tick_mkt['wap'] = (tick_mkt['BidPrice1'] * tick_mkt['AskVolume1'] + tick_mkt['AskPrice1'] * tick_mkt[
199 | 'BidVolume1']) / (tick_mkt['AskVolume1'] + tick_mkt['BidVolume1'])
200 | # tick_mkt['log_return'] = tick_mkt['LastPrice'].rolling(2).apply(lambda x: math.log(list(x)[-1] / list(x)[0]))
201 | tick_mkt['log_return'] = np.log(tick_mkt['LastPrice']).diff()
202 | tick_mkt['wap_log_return'] = np.log(tick_mkt['wap']) - np.log(tick_mkt['LastPrice'])
203 |
204 | # tick_mkt['log_return'] = tick_mkt['wap'].rolling(2).apply(lambda x: math.log(list(x)[-1] / list(x)[0]))
205 |
206 | # tick_mkt['label_reg'] = tick_mkt['log_return'].rolling(predict_windows).sum().shift(1 - predict_windows)
207 | # lst_ret_max = list(tick_mkt['label_reg'].rolling(predict_windows).max())
208 | # lst_ret_min = list(tick_mkt['label_reg'].rolling(predict_windows).min())
209 | # 1: profit for long;2: profit for short; 0: not profit for the predict_windows, based on stop_profit threshold
210 | # tick_mkt['label_clf'] = [1 if item > math.log(1 + stop_profit) else 2 if item < math.log(1 - stop_profit) else 0 for
211 | # item in
212 | # tick_mkt['label_reg']]
213 | sum_max = []
214 | sum_min = []
215 |
216 | _x, _y = tick_mkt.shape
217 | _diff = list(tick_mkt['InterestDiff'])
218 | _vol = list(tick_mkt['Volume'])
219 |
220 | open_close_ratio = []
221 | for idx, item in enumerate(_diff):
222 | try:
223 | open_close_ratio.append(item / (_vol[idx] - item))
224 | except Exception as ex:
225 | open_close_ratio.append(open_close_ratio[-1])
226 |
227 | # cal ori factor(paper factor)
228 | lst_oi, lst_oir, lst_aoi = cal_oir(list(tick_mkt['BidPrice1']), list(tick_mkt['BidVolume1']),
229 | list(tick_mkt['AskPrice1']), list(tick_mkt['AskVolume1']), n_rows=_x)
230 |
231 | # cal cos factor(market factor)
232 | _lst_last_price = list(tick_mkt['LastPrice'])
233 | _lst_turn_idx, _lst_turn_val = cal_turning(_lst_last_price)
234 | _lst_slope = cal_slope(_lst_last_price, _lst_turn_idx, _lst_turn_val)
235 | _lst_cos = cal_cos(_lst_last_price, _lst_turn_idx, _lst_turn_val)
236 |
237 | dif, dea, macd = ta.MACD(tick_mkt['LastPrice'], fastperiod=12, slowperiod=26, signalperiod=9)
238 |
239 | tick_mkt['open_close_ratio'] = open_close_ratio
240 | tick_mkt['price_spread'] = (tick_mkt['BidPrice1'] - tick_mkt['AskPrice1']) / (
241 | tick_mkt['BidPrice1'] + tick_mkt['AskPrice1'] / 2)
242 | tick_mkt['buy_sell_spread'] = abs(tick_mkt['BidPrice1'] - tick_mkt['AskPrice1'])
243 | tick_mkt['oi'] = lst_oi
244 | tick_mkt['oir'] = lst_oir
245 | tick_mkt['aoi'] = lst_aoi
246 | tick_mkt['slope'] = _lst_slope
247 | tick_mkt['cos'] = _lst_cos
248 | tick_mkt['macd'] = macd
249 | tick_mkt['dif'] = dif
250 | tick_mkt['dea'] = dea
251 | tick_mkt['bs_tag'] = tick_mkt['LastPrice'].rolling(2).apply(lambda x: 1 if list(x)[-1] > list(x)[0] else -1)
252 | tick_mkt['bs_vol'] = tick_mkt['bs_tag'] * tick_mkt['Volume']
253 |
254 | def _cal_clf_label(x):
255 | # FIXME remove clf bc hardcode, log(1.001)= 0.0009988(0.1%), log(0.999)=-0.001(0.1%)
256 | # return [1 if item > 0.001 else 2 if item < -0.001 else 0 for item in x]
257 | # return [1 if item > 2 else 2 if item < -2 else 0 for item in x]
258 | # ret = []
259 | # for item in x:
260 | # if item > 5:
261 | # ret.append(2)
262 | # elif item < 5 and item >= 2:
263 | # ret.append(1)
264 | # elif item < 2 and item > -2:
265 | # ret.append(0)
266 | # elif item <= -2 and item > -5:
267 | # ret.append(-1)
268 | # else:
269 | # ret.append(-2)
270 | # return ret
271 | return [1 if item > 2 else -1 if item < -2 else 0 for item in x]
272 |
273 | # agg by windows,agg_dict={factor_name:(executor, new_factor_name)}, if None for new_factor_name, then use the
274 | # raw name by default
275 | windows_agg_dict = {
276 | 'LastPrice': [(lambda x: (list(x)[-1] - list(x)[0]) / len(x), 'trend')],
277 | 'Volume': [(np.sum, 'volume')],
278 | 'Turnover': [(np.sum, 'turnover')],
279 | 'bs_vol': [(np.sum, None)],
280 | 'log_return': [(np.sum, None)],
281 | }
282 | for idx, _lag in enumerate(lag_windows):
283 | for key, val in windows_agg_dict.items():
284 | for _func, rename in val:
285 | factor_name = rename or key
286 | tick_mkt['{0}_{1}'.format(factor_name, idx)] = list(tick_mkt[key].rolling(_lag).apply(_func))
287 |
288 | tick_mkt['bsvol_volume_{0}'.format(idx)] = tick_mkt['bs_vol_{0}'.format(idx)] / tick_mkt[
289 | 'volume_{0}'.format(idx)]
290 |
291 | for idx, _pred in enumerate(predict_windows):
292 | tick_mkt['label_{0}'.format(idx)] = tick_mkt['log_return'].rolling(_pred).sum().shift(1 - _pred)
293 | tick_mkt['rv_{0}'.format(idx)] = tick_mkt['log_return'].rolling(_pred).apply(realized_volatility).shift(
294 | 1 - _pred)
295 | # tick_mkt['label_clf_{0}'.format(idx)] = _cal_clf_label(tick_mkt['label_{0}'.format(idx)])
296 |
297 | tick_mkt['price_chg_{0}'.format(idx)] = tick_mkt['LastPrice'].rolling(_pred).apply(
298 | lambda x: list(x)[-1] - list(x)[0]).shift(1 - _pred)
299 | tick_mkt['vwap_chg_{0}'.format(idx)] = tick_mkt['LastPrice'].rolling(_pred).apply(
300 | lambda x: x.mean() - list(x)[0]).shift(1 - _pred)
301 | tick_mkt['label_clf_{0}'.format(idx)] = _cal_clf_label(tick_mkt['vwap_chg_{0}'.format(idx)])
302 |
303 | # tick_mkt['log_return_short'] = tick_mkt['log_return'].rolling(lag_short).sum()
304 | # tick_mkt['log_return_long'] = tick_mkt['log_return'].rolling(lag_long).sum()
305 | # tick_mkt['trend_short'] = tick_mkt['LastPrice'].rolling(lag_short).apply(
306 | # lambda x: (list(x)[-1] - list(x)[0]) / lag_short)
307 | # tick_mkt['trend_long'] = tick_mkt['LastPrice'].rolling(lag_long).apply(
308 | # lambda x: (list(x)[-1] - list(x)[0]) / lag_long)
309 | # tick_mkt['vol_short'] = tick_mkt['Volume'].rolling(lag_short).sum()
310 | # tick_mkt['vol_long'] = tick_mkt['Volume'].rolling(lag_long).sum()
311 | # tick_mkt['turnover_short'] = tick_mkt['Turnover'].rolling(lag_short).sum()
312 | # tick_mkt['turnover_long'] = tick_mkt['Turnover'].rolling(lag_long).sum()
313 | # tick_mkt['bs_vol_long'] = tick_mkt['bs_vol'].rolling(lag_long).sum()
314 | # tick_mkt['bs_vol_short'] = tick_mkt['bs_vol'].rolling(lag_short).sum()
315 |
316 | # agg by factor,remove vwap
317 | factor_agg_dict = {'trend': ['minus', 'divide'], 'volume': ['minus', 'divide'], 'turnover': ['minus', 'divide'],
318 | 'bs_vol': ['minus', 'divide'], 'bsvol_volume': ['divide']}
319 | lag_win_size = len(lag_windows)
320 | if lag_win_size >= 2:
321 | for key, val in factor_agg_dict.items():
322 | for _func_name in val:
323 | if _func_name == 'minus':
324 | tick_mkt['{0}_ls_diff'.format(key)] = tick_mkt['{0}_{1}'.format(key, 0)] - tick_mkt[
325 | '{0}_{1}'.format(key, lag_win_size - 1)]
326 | if _func_name == 'divide':
327 | tick_mkt['{0}_ls_ratio'.format(key)] = tick_mkt['{0}_{1}'.format(key, 0)] / tick_mkt[
328 | '{0}_{1}'.format(key, lag_win_size - 1)]
329 |
330 | # tick_mkt['trenddiff'] = tick_mkt['trend_short'] - tick_mkt['trend_long']
331 | # tick_mkt['trend_ls_ratio'] = tick_mkt['trend_short'] / tick_mkt['trend_long']
332 | # tick_mkt['vol_ls_ratio'] = tick_mkt['vol_short'] / tick_mkt['vol_long']
333 | # tick_mkt['turnover_ls_ratio'] = tick_mkt['turnover_short'] / tick_mkt['turnover_long']
334 | # tick_mkt['vwap_short'] = tick_mkt['turnover_short'] / tick_mkt['vol_short']
335 | # tick_mkt['vwap_long'] = tick_mkt['turnover_long'] / tick_mkt['vol_long']
336 | # tick_mkt['vwap_ls_ratio'] = tick_mkt['vwap_short'] / tick_mkt['vwap_long']
337 | # tick_mkt['bs_vol_diff'] = tick_mkt['bs_vol_short'] - tick_mkt['bs_vol_long']
338 | # tick_mkt['bs_vol_ls_ratio'] = tick_mkt['bs_vol_short'] / tick_mkt['bs_vol_long']
339 |
340 | # TODO ADD norm factor
341 | # norm factor processing, calculate the norm here or in model processing. norm with the value of the current date
342 | # or summary of the prev date
343 | normalized = define.normalized_vals
344 | # TODO open_dict is the open value of the current date, this could be normalized by other ref,e.g. the prev date
345 | for k, v in normalized.items():
346 | if not v:
347 | normalized.update({k: (open_dict.get(k) or 1.0)})
348 | for _base_col, _ref_col in define.normalized_refs:
349 | _arr = _normalized_by_base(tick_mkt[_base_col], normalized, _ref_col)
350 | if not _arr: # FIXME
351 | continue
352 | tick_mkt['norm_{0}'.format(_base_col.lower())] = _arr
353 |
354 | _factor_lst = list(tick_mkt.columns)
355 | print(_factor_lst)
356 |
357 | for col in define.skip_raw_cols:
358 | try:
359 | _factor_lst.remove(col)
360 | except Exception as ex:
361 | print('col:{0} not exist with error:{1}'.format(col, ex))
362 |
363 | # FIXME replace this with better solution
364 | for idx, _lag in enumerate(lag_windows):
365 | _factor_lst.remove('volume_{0}'.format(idx))
366 | _factor_lst.remove('turnover_{0}'.format(idx))
367 | _factor_lst.remove('bs_vol_{0}'.format(idx))
368 |
369 | # handle exception values
370 | tick_mkt = tick_mkt.replace(np.inf, np.nan)
371 | tick_mkt = tick_mkt.replace(-np.inf, np.nan)
372 | tick_mkt = tick_mkt.dropna()
373 | _factor_path = utils.get_path([define.CACHE_DIR, define.FACTOR_DIR,
374 | 'factor_{0}_{1}.csv'.format(instrument_id, trade_date.replace('-', ''))])
375 | tick_mkt[_factor_lst].to_csv(_factor_path, index=False)
376 | return tick_mkt[_factor_lst]
377 |
--------------------------------------------------------------------------------
/backtester/Factor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 14:03
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : Factor.py
7 |
8 | import numpy as np
9 | import pandas
10 | import math
11 | import utils.utils as utils
12 | import utils.define as define
13 | import collections
14 | import copy
15 |
16 |
17 | class Factor(object):
18 | def __init__(self, product_id='m', instrument_id='', trade_date='', long_windows=600, short_windows=120):
19 | # same variables in live
20 | self.last_price = collections.deque(long_windows) # xxx_circular_ptr
21 | self.mid_price = collections.deque(long_windows)
22 | self.spread = collections.deque(long_windows)
23 | self.buy_vol = collections.deque(long_windows)
24 | self.sell_vol = collections.deque(long_windows)
25 | self.turnover = collections.deque(long_windows)
26 | self.vol = collections.deque(long_windows)
27 | self.long_windows = long_windows
28 | self.short_windows = short_windows
29 | self.curr_factor = []
30 | self.last_factor = []
31 | self.last_mkt = []
32 |
33 | self.product_id = product_id
34 | self.instrument_id = instrument_id
35 | self.trade_date = trade_date
36 |
37 | self.vwap = []
38 | self.upper_bound = []
39 | self.lower_bound = []
40 | self.open_turnover = -np.inf
41 | self.open_vol = -np.inf
42 | self.product_id = product_id
43 | self.update_time = []
44 | self.turning = []
45 | self.turning_idx = []
46 | self.slope = []
47 | # this only calculate by time stamp
48 |
49 | self.b2s_vol = []
50 | self.b2s_turnover = []
51 | self.turnover = []
52 | self.price_highest = []
53 | self.price_lowest = []
54 | self.ma10 = []
55 | self.ma20 = []
56 | self.ma60 = []
57 | self.ma120 = []
58 | self.mid_price = []
59 | self.trend_ls_ratio = []
60 | self.cos = []
61 | self.trend_long = []
62 | self.trend_short = []
63 | self.log_return = []
64 | self.log_return10 = []
65 | self.log_return20 = []
66 | self.log_return40 = []
67 | self.log_return60 = []
68 | self.mid_log_return = []
69 | self.vwap_ls_diff = []
70 |
71 | def update_factor(self, tick=[], *args, **kwa):
72 | '''
73 |
74 | :param tick: live的depth market
75 | :param args:
76 | :param kwa:
77 | :return:
78 | '''
79 | last_price = tick[3]
80 | vol = tick[7] # 当前成交量,和live 不同
81 | turnover = tick[6] # 当前成交额,和live不同
82 | _update_time = tick[2]
83 | bid_price1, ask_price1, bid_vol1, ask_vol1 = tick[-4:]
84 |
85 | # same var with live
86 | _curr_last = tick[3]
87 | _curr_vol = tick[7] # 当前成交量,和CTP live不同
88 | _curr_interest = tick[4] # 当前持仓,和CTP live 不同
89 | _curr_turnover = tick[6] # 当前成交额,和CTP live不同
90 | _prev_max = _curr_last
91 | _prev_min = _curr_last
92 | _prev_vwap = _curr_last
93 | _log_return = 0.0
94 | _mid_log_return = 0.0
95 | _log_return_long = 0.0
96 | _log_return_short = 0.0
97 | _mid_log_return_short = 0.0
98 | _mid_log_return_long = 0.0
99 | _ma_short = 0.0
100 | _ma_long = 0.0
101 | _ma_ls_diff_last = 0.0
102 | _ma_ls_diff_curr = 0.0
103 | # already included in the hist data
104 | tick_type = tick[10]
105 | tick_direction = tick[11]
106 | _vol_buy = 0.0
107 | _vol_sell = 0.0
108 | _interest_open = 0.0
109 | _interest_close = 0.0
110 | _turnover_long = 0.0
111 | _turnover_short = 0.0
112 | _vol_long = 0.0
113 | _vol_short = 0.0
114 |
115 | _curr_spread = ask_price1 - bid_price1
116 | _curr_vwap = turnover / vol # FIXME check the calculation with live
117 | _curr_mid = (ask_price1 * bid_vol1 + bid_price1 * ask_vol1) / (ask_vol1 + bid_vol1)
118 |
119 | # update from last factor, start from the 2nd tick
120 | if len(self.last_factor) > 0:
121 | _prev_max = self.last_factor[1]
122 | _prev_min = self.last_factor[2]
123 | _prev_vwap = self.last_factor[5]
124 | _log_return = math.log(_curr_last) - math.log(self.last_factor[0])
125 | _mid_log_return = math.log(_curr_mid) - math.log(self.last_factor[4])
126 |
127 | cir_size = len(self.last_price)
128 | _curr_max = max(_prev_max, _curr_last)
129 | _curr_min = min(_prev_min, _curr_last)
130 |
131 | if len(self.last_mkt) > 0:
132 | pass
133 |
134 | if cir_size >= self.long_windows:
135 | _log_return_long = self.last_factor[8] + _log_return - math.log(self.last_price[1]) - math.log(
136 | self.last_price[1])
137 | _mid_log_return_long = self.last_factor[10] + _mid_log_return - math.log(self.mid_price[1]) - math.log(
138 | self.mid_price[1])
139 | _log_return_short = self.last_factor[7] + _log_return - math.log(self.last_price[1]) - math.log(
140 | self.last_price[0])
141 | _mid_log_return_short = self.last_factor[9] + _mid_log_return - math.log(self.mid_price[1]) - math.log(
142 | self.mid_price[0])
143 | _turnover_long = sum(self.turnover[-self.long_windows:]) # TODO different from live
144 | _turnover_short = sum(self.turnover[-self.short_windows:]) # TODO
145 | _vol_long = sum(self.vol[-self.long_windows:])
146 | _vol_short = sum(self.vol[-self.short_windows:])
147 | _ma_long = _turnover_long / _vol_long
148 | _ma_short = _turnover_short / _vol_short
149 | _ma_ls_diff_curr = _ma_short - _ma_long
150 | _ma_ls_diff_last = self.last_factor[11] - self.last_factor[12]
151 | elif cir_size >= self.short_windows:
152 | _ma_long = _curr_vwap
153 | _log_return_short = self.last_factor[7] + _log_return - math.log(self.last_price[1]) - math.log(
154 | self.last_price[0])
155 |
156 | _mid_log_return_short = self.last_factor[9] + _mid_log_return - math.log(self.mid_price[1]) - math.log(
157 | self.mid_price[0])
158 | _ma_short = _curr_vwap
159 | else:
160 | _ma_long = _curr_vwap
161 | _ma_short = _curr_vwap
162 |
163 | # time series cached factor
164 | self.last_price.append(_curr_last)
165 | self.spread.append(_curr_spread)
166 | self.mid_price.append(_curr_mid)
167 | self.turnover.append(_curr_turnover)
168 | self.vol.append(_curr_vol)
169 |
170 | ret_factor = copy.deepcopy(self.curr_factor)
171 | # currentfactorupdate
172 | self.curr_factor.clear()
173 | self.curr_factor.append(_curr_last) # factor vector:0
174 | self.curr_factor.append(_curr_max) # vector: 1;factor: 5
175 | self.curr_factor.append(_curr_min) # vector: 2;factor: 6
176 | self.curr_factor.append(_curr_spread) # vector: 3;factor: 7
177 | self.curr_factor.append(_curr_mid) # vector: 4;factor: 8
178 | self.curr_factor.append(_curr_vwap) # vector: 5;factor: 9
179 | self.curr_factor.append(_log_return) # vector: 6;factor: 10
180 | self.curr_factor.append(_log_return_short) # vector: 7;factor: 11
181 | self.curr_factor.append(_log_return_long) # vector: 8;factor: 12
182 | self.curr_factor.append(_mid_log_return_short) # vector: 9;factor: 13
183 | self.curr_factor.append(_mid_log_return_long) # vector: 10;factor: 14
184 | self.curr_factor.append(_ma_short) # vector: 11; factor: 15
185 | self.curr_factor.append(_ma_long) # // vector: 12; factor: 16
186 | self.curr_factor.append(_curr_vol) # vector: 13;factor: 17
187 | self.curr_factor.append(_curr_interest) # vector14; factor: 18
188 |
189 | self.last_factor.clear()
190 | for item in self.curr_factor:
191 | self.last_factor.append(item)
192 | self.last_mkt.clear()
193 | self.last_mkt.append(ask_price1) # 0: askprice 1
194 | self.last_mkt.append(bid_price1) # 1:bidprice 1
195 | self.last_mkt.append(_curr_vol) # 2 vol TODO VOL is different
196 | self.last_mkt.append(_curr_interest) # 3, TODO interest is diff
197 | self.last_mkt.append(_vol_buy)
198 | self.last_mkt.append(_vol_sell)
199 | self.last_mkt.append(_curr_interest) # TODO interest is diff
200 | return ret_factor
201 |
202 | # @timeit
203 | def update_factor_bt(self, tick=[], *args, **kwargs):
204 | # values from tick
205 | last_price = tick[3]
206 | vol = tick[7]
207 | turnover = tick[6]
208 | _update_time = tick[2]
209 | bid_price1, ask_price1, bid_vol1, ask_vol1 = tick[-4:]
210 |
211 | _mid = (bid_price1 * ask_vol1 + ask_price1 * bid_vol1) / (ask_vol1 + bid_vol1)
212 | self.mid_price.append(_mid)
213 | _range = kwargs.get('range') or 20
214 | k1 = kwargs.get('k1') or 0.2
215 | k2 = kwargs.get('k2') or 0.2
216 | idx = kwargs.get('idx') or 0
217 | lag_long = kwargs.get('lag_long') or 60
218 | lag_short = kwargs.get('lag_short') or 20
219 | _mul_num = kwargs.get('multiplier') or 1.0
220 | self.open_turnover = turnover if self.open_turnover < 0 else self.open_turnover
221 | self.open_vol = vol if self.open_vol < 0 else self.open_vol
222 | self.last_price.append(last_price)
223 | self.update_time.append(_update_time)
224 | # "BidPrice1", "BidVolume1", "AskPrice1", "AskVolume1",
225 |
226 | self.spread.append(ask_price1 - bid_price1)
227 | self.b2s_vol.append(bid_vol1 / ask_vol1 if ask_vol1 > 0 else 1.0)
228 | self.b2s_turnover.append(
229 | ((bid_vol1 * bid_price1) / (ask_vol1 * ask_price1) if ask_vol1 * ask_price1 > 0 else 1.0))
230 | # self.price_highest.append(max(self.last_price))
231 | # self.price_lowest.append(min(self.last_price))
232 | _vwap_val = None
233 | try:
234 | # if _update_time >= '21:00:00':
235 | _tmp_vol = vol
236 | _tmp_turnover = turnover
237 | self.vol.append(_tmp_vol)
238 | self.turnover.append(_tmp_turnover)
239 | _vwap_val = (_tmp_turnover) / (_mul_num * (_tmp_vol))
240 |
241 | except Exception as ex:
242 | if self.vwap:
243 | _vwap_val = self.vwap[-1]
244 | else:
245 | _vwap_val = last_price
246 | self.vwap.append(_vwap_val)
247 | self.upper_bound.append(_vwap_val + k1 * _range)
248 | self.lower_bound.append(_vwap_val - k2 * _range)
249 |
250 | # ma for fixed length
251 | self.ma10.append(sum(self.last_price[-10:]) / len(self.last_price[-10:]))
252 | self.ma20.append(sum(self.last_price[-20:]) / len(self.last_price[-20:]))
253 | self.ma60.append(sum(self.last_price[-60:]) / len(self.last_price[-60:]))
254 | self.ma120.append(sum(self.last_price[-120:]) / len(self.last_price[-120:]))
255 |
256 | if len(self.turning) <= 1:
257 | self.turning.append(last_price)
258 | self.turning_idx.append(idx)
259 | else:
260 | _last_turn = self.turning[-1]
261 | _last_turn1 = self.turning[-2]
262 | _last_price = self.last_price[-2]
263 | _last_price1 = self.last_price[-3]
264 | if (last_price - _last_price) * (_last_price - _last_price1) <= 0: # new turning
265 | self.turning.append(_last_price)
266 | self.turning_idx.append(idx - 1)
267 | else: # last turning
268 | self.turning.append(_last_turn)
269 | self.turning_idx.append(self.turning_idx[-1])
270 | # print(self.turning)
271 | if not self.slope:
272 | self.slope.append(0.5)
273 | else:
274 | if idx == self.turning_idx[-1]:
275 | self.slope.append(self.slope[-1])
276 | else:
277 | self.slope.append((last_price - self.turning[-1]) / (idx - self.turning_idx[-1]))
278 |
279 | # factor for lag windows,
280 | try:
281 | if len(self.last_price) >= lag_long: # long windows
282 | _trend_long = (self.last_price[-1] - self.last_price[-lag_long]) / lag_long
283 | _trend_short = (self.last_price[-1] - self.last_price[-lag_short]) / lag_short
284 | self.trend_long.append(_trend_long)
285 | self.trend_short.append(_trend_short)
286 | self.trend_ls_ratio.append(
287 | self.trend_short[-1] / self.trend_long[-1] if self.trend_long[-1] != 0 else np.nan)
288 |
289 | _ls_diff = sum(self.turnover[lag_long:]) / sum(self.vol[lag_long:]) - sum(
290 | self.turnover[lag_short:]) / sum(
291 | self.vol[lag_short:])
292 | # _ls_diff = np.array(self.last_price[lag_long:]).mean() - np.array(self.last_price[lag_short:]).mean()
293 | self.vwap_ls_diff.append(_ls_diff)
294 |
295 |
296 | elif len(self.last_price) >= lag_short: # short windows
297 | _trend_short = (self.last_price[-1] - self.last_price[-lag_short]) / lag_short
298 | self.trend_short.append(_trend_short)
299 | self.trend_long.append(np.nan)
300 | self.trend_ls_ratio.append(np.nan)
301 | self.vwap_ls_diff.append(np.nan)
302 |
303 | else:
304 | self.trend_long.append(np.nan)
305 | self.trend_short.append(np.nan)
306 | self.trend_ls_ratio.append(np.nan)
307 | self.vwap_ls_diff.append(np.nan)
308 |
309 | except Exception as ex:
310 | print(ex)
311 |
312 | a = np.array([idx - self.turning_idx[-1], self.last_price[-1] - self.last_price[self.turning_idx[-1]]])
313 | b = np.array([0, 1])
314 | self.cos.append(a.dot(b) / (np.linalg.norm(a) * np.linalg.norm(b)))
315 | if len(self.last_price) >= 2:
316 | self.log_return.append(math.log(self.last_price[-1]) - math.log(self.last_price[-2]))
317 | else:
318 | self.log_return.append(np.nan)
319 | if len(self.mid_price) >= 2:
320 | self.mid_log_return.append(math.log(self.mid_price[-1]) - math.log(self.mid_price[-2]))
321 | else:
322 | self.mid_log_return.append(np.nan)
323 | if len(self.log_return) > 10:
324 | self.log_return10.append(sum(self.log_return[-10:]))
325 | else:
326 | self.log_return10.append(np.nan)
327 | if len(self.last_price) != len(self.trend_long):
328 | print('check')
329 |
330 | def load_factor(self):
331 | pass
332 |
333 | def get_factor(self):
334 | return {'slope': self.slope[-1],
335 | 'vwap': self.vwap[-1],
336 | 'spread': self.spread[-1],
337 | 'b2s_vol': self.b2s_vol[-1],
338 | 'cos': self.cos[-1],
339 | 'trend_ls_ratio': self.trend_ls_ratio[-1],
340 | 'log_return': self.log_return[-1],
341 | 'log_return_0': self.log_return10[-1],
342 | 'wap_log_return': self.mid_log_return[-1],
343 |
344 | }
345 |
346 | def cache_factor(self):
347 | # factor_df = pandas.DataFrame({'last_price': self.last_price, 'vwap': self.vwap, 'upper_bound': self.upper_bound,
348 | # 'lower_bound': self.lower_bound, 'update_time': self.update_time,
349 | # 'turning': self.turning,
350 | # 'slope': self.slope, 'turning_idx': self.turning_idx, 'spread': self.spread,
351 | # 'vol_ratio': self.vol_ratio, 'price_highest': self.price_highest,
352 | # 'price_lowest': self.price_lowest,
353 | # 'curr_vol': self.curr_vol, 'curr_turnover': self.curr_turnover})
354 | factor_df = pandas.DataFrame({'last_price': self.last_price, 'vwap': self.vwap, 'upper_bound': self.upper_bound,
355 | 'lower_bound': self.lower_bound, 'update_time': self.update_time,
356 | 'turning': self.turning,
357 | 'slope': self.slope, 'turning_idx': self.turning_idx, 'spread': self.spread,
358 | 'b2s_vol': self.b2s_vol,
359 | 'b2s_turnover': self.b2s_turnover, 'vol': self.vol,
360 | 'turnover': self.turnover,
361 | # 'price_high': self.price_highest,
362 | # 'price_low': self.price_lowest
363 | })
364 | _factor_path = utils.get_path([define.CACHE_DIR, define.FACTOR_DIR,
365 | 'factor_{0}_{1}.csv'.format(self.instrument_id, self.trade_date)])
366 | factor_df.to_csv(_factor_path, index=False)
367 |
--------------------------------------------------------------------------------
/backtester/backtest.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from collections import defaultdict, deque
3 | import os
4 | import pickle as pk
5 |
6 | from sqlalchemy import create_engine
7 | import numpy as np
8 | import pandas as pd
9 | from tqdm import tqdm
10 |
11 | import core
12 | import core.theo as T
13 | import core.fitting as F
14 |
15 |
16 | def get_daily_info(startdate, enddate):
17 | cache_file = os.path.join(os.sep.join(os.path.abspath(__file__).split(os.sep)[:-3]), 'data', 'stock_daily_info.csv')
18 | daily_info = pd.read_csv(cache_file, dtype={'date': str, 'symbol': str})
19 | daily_info['refPrice'] = round((daily_info['price_limit_up'] + daily_info['price_limit_down']) / 2 * 100 - 0.1)/ 100
20 | daily_info = daily_info.set_index(['symbol', 'date'])
21 | return daily_info
22 |
23 |
24 | def cross_edge_notional_multilevel(book_with_valuation, symbol, posNotionals, edges, daily_info, use_ratio=True, start_extime=93200000000):
25 | results = defaultdict(lambda: defaultdict(dict))
26 | for date, sub_df in sorted(book_with_valuation.groupby('date')):
27 | if (symbol, date) in daily_info.index:
28 | refPrice = daily_info.loc[(symbol, date), 'refPrice']
29 | else:
30 | print('%s: %s not in daily info. skip.' % (date, symbol))
31 | continue
32 |
33 | for posNotional in posNotionals:
34 | position_normal = int(posNotional / refPrice // 100 * 100)
35 | # print(date, '%d / %.2f = %d' % (posNotional, refPrice, position_normal))
36 | if position_normal == 0:
37 | # print('posNormal = 0. Skip')
38 | continue
39 |
40 | if sub_df.shape[0] - sub_df.dropna().shape[0] > 50 or sub_df.dropna().shape[0] < 50:
41 | pass
42 | else:
43 | print(date, sub_df.dropna().shape[0], sub_df.shape[0])
44 | book_events = sub_df.to_dict('records')
45 | for edge in edges:
46 | trades = []
47 | buyprice = defaultdict(int)
48 | sellprice = defaultdict(int)
49 | cur_position = 0
50 | for event in book_events:
51 | if event['ExchangeTime'] < start_extime:
52 | continue
53 | if not np.isnan(event['value']) and event['Bid1'] != 0 and event['Ask1'] != 0:
54 | midpt = (event['Bid1'] + event['Ask1']) / 2
55 | fv = midpt + event['value']
56 | for level in range(5):
57 | i = level + 1
58 | if (not use_ratio and (fv - event['Ask%d' % i]) > edge) or (use_ratio and (fv - event['Ask%d' % i]) / midpt > edge):
59 | new_buy_qty = min(max(0, event['Ask%dSize' % i] - buyprice[event['Ask%d' % i]]), position_normal - cur_position)
60 | if new_buy_qty > 0:
61 | trades.append({'dir': False, 'price': event['Ask%d' % i], 'qty': new_buy_qty, 'extime': event['ExchangeTime'], 'date': event['date'], 'TimeStamp': event['TimeStamp']})
62 | buyprice[event['Ask%d' % i]] += new_buy_qty
63 | cur_position += new_buy_qty
64 |
65 | if (not use_ratio and (event['Bid%d' % i] - fv)) > edge or (use_ratio and (event['Bid%d' % i] - fv) / midpt > edge):
66 | new_sell_qty = min(max(0, event['Bid%dSize' % i] - sellprice[event['Bid%d' % i]]), cur_position + position_normal)
67 | if new_sell_qty:
68 | trades.append({'dir': True, 'price': event['Bid%d' % i], 'qty': new_sell_qty, 'extime': event['ExchangeTime'], 'date': event['date'], 'TimeStamp': event['TimeStamp']})
69 | sellprice[event['Bid%d' % i]] += new_sell_qty
70 | cur_position -= new_sell_qty
71 |
72 | buyprice_new = defaultdict(int)
73 | sellprice_new = defaultdict(int)
74 | buyprice_new.update({k: v for k, v in buyprice.items() if k > event['Bid1']})
75 | sellprice_new.update({k: v for k, v in sellprice.items() if k < event['Ask1']})
76 | buyprice = buyprice_new
77 | sellprice = sellprice_new
78 | results[posNotional][edge][date] = trades
79 |
80 | return results
81 |
82 |
83 |
84 | def constraint_trades_time(trades, start_extime, end_extime):
85 | trades = trades.copy()
86 | for date in trades:
87 | tmp = trades[date]
88 | trades[date] = [v for v in tmp if v['extime'] >= start_extime and v['extime'] < end_extime]
89 |
90 | return trades
91 |
92 |
93 | def constraint_trades_maxvolume(trades_date, maxvolume):
94 | pos_total = 0
95 | neg_total = 0
96 | new_trades = []
97 | for t in trades_date:
98 | if t['dir'] and t['qty'] and neg_total < maxvolume:
99 | new_trades.append(t.copy())
100 | new_trades[-1]['qty'] = min(maxvolume - neg_total, new_trades[-1]['qty'])
101 | neg_total += new_trades[-1]['qty']
102 | if not t['dir'] and t['qty'] and pos_total < maxvolume:
103 | new_trades.append(t.copy())
104 | new_trades[-1]['qty'] = min(maxvolume - pos_total, new_trades[-1]['qty'])
105 | pos_total += new_trades[-1]['qty']
106 |
107 | return new_trades
108 |
109 |
110 | def handle_match(fills, t):
111 | matches = []
112 | t = t.copy()
113 | t['remain_qty'] = t['qty']
114 | if not fills or (fills[0]['dir'] and t['dir']) or (not fills[0]['dir'] and not t['dir']):
115 | fills.append(t)
116 | else:
117 | while fills:
118 | fill = fills[0]
119 | match_qty = min(fill['remain_qty'], t['remain_qty'])
120 | fill['remain_qty'] -= match_qty
121 | t['remain_qty'] -= match_qty
122 | if fill['remain_qty'] == 0:
123 | fills.popleft()
124 | holdtime = t['TimeStamp'] - fill['TimeStamp']
125 | # print(fill)
126 | # print(t)
127 | matches.append({'qty': match_qty,
128 | 'entry_dir': fill['dir'],
129 | 'entryprice': fill['price'],
130 | 'exitprice': t['price'],
131 | 'entrytime': datetime.fromtimestamp(fill['TimeStamp'] / 1e6),
132 | 'exittime': datetime.fromtimestamp(t['TimeStamp'] / 1e6)})
133 |
134 | if t['remain_qty'] == 0:
135 | break
136 |
137 | if t['remain_qty'] > 0:
138 | fills.append(t)
139 |
140 | return matches
141 |
142 |
143 | def match(trades_date):
144 | matches = []
145 | fills = deque()
146 | for t in trades_date:
147 | pos = t['qty'] * (1.0 if not t['dir'] else -1.0)
148 | if abs(pos) != 0:
149 | tmp_matches = handle_match(fills, t)
150 | matches.extend(tmp_matches)
151 |
152 | return matches, fills
153 |
154 |
155 | def get_open_pnl(lastbook_date, remain_fills):
156 | open_pnl = 0
157 | for f in remain_fills:
158 | open_pnl += (1.0 if f['dir'] else -1.0) * f['remain_qty'] * (f['price'] - lastbook_date['midpt'])
159 | return open_pnl
160 |
161 |
162 | def get_open_notional(remain_fills):
163 | open_notional = 0
164 | for f in remain_fills:
165 | open_notional += f['remain_qty'] * f['price'] * (1.0 if not f['dir'] else -1.0)
166 | return open_notional
167 |
168 |
169 | def get_remain_qty(remain_fills):
170 | remain_qty = 0
171 | for f in remain_fills:
172 | remain_qty += f['remain_qty'] * (1.0 if not f['dir'] else -1.0)
173 | return remain_qty
174 |
175 |
176 | def get_lastbook(book_with_valuation, end_extime):
177 | book_with_valuation = book_with_valuation[book_with_valuation['ExchangeTime'] >= end_extime]
178 | lastbook = {}
179 | for date, sub_df in book_with_valuation.groupby('date'):
180 | sub_df = sub_df.sort_values('ExchangeTime')
181 | lastbook[date] = sub_df.iloc[0]
182 | return lastbook
183 |
184 |
185 | def end_penalty_half_spread(last_book_date, remain_qty):
186 | if last_book_date['Ask1'] == 0 or last_book_date['Bid1'] == 0:
187 | return 0
188 | else:
189 | return abs(remain_qty) * 1 / 2 * (last_book_date['Ask1'] - last_book_date['Bid1'])
190 |
191 |
192 | def end_penalty_whole_book(last_book_date, remain_qty):
193 | if last_book_date['Ask1'] == 0 or last_book_date['Bid1'] == 0:
194 | return 0
195 | else:
196 | if remain_qty > 0:
197 | total_qty = 0
198 | total_notional = 0
199 | first_bid = last_book_date['Bid1']
200 | last_bid = 0
201 | remain_qty = abs(remain_qty)
202 | for level in range(1, 6):
203 | if last_book_date['Bid%d' % level] != 0:
204 | bid = last_book_date['Bid%d' % level]
205 | bidsize = last_book_date['Bid%dSize' % level]
206 | matchqty = min(bidsize, remain_qty - total_qty)
207 | total_qty += matchqty
208 | total_notional += matchqty * bid
209 | if total_qty == remain_qty:
210 | break
211 | last_bid = bid
212 | else:
213 | break
214 |
215 | if remain_qty > total_qty:
216 | total_notional += (remain_qty - total_qty) * last_bid
217 | total_qty = remain_qty
218 |
219 | return last_book_date['midpt'] * remain_qty - total_notional
220 |
221 | if remain_qty < 0:
222 | total_qty = 0
223 | total_notional = 0
224 | first_ask = last_book_date['Ask1']
225 | last_ask = 0
226 | remain_qty = abs(remain_qty)
227 | for level in range(1, 6):
228 | if last_book_date['Ask%d' % level] != 0:
229 | ask = last_book_date['Ask%d' % level]
230 | asksize = last_book_date['Ask%dSize' % level]
231 | matchqty = min(asksize, remain_qty - total_qty)
232 | total_qty += matchqty
233 | total_notional += matchqty * ask
234 | if total_qty == remain_qty:
235 | break
236 | last_ask = ask
237 | else:
238 | break
239 |
240 | if remain_qty > total_qty:
241 | total_notional += (remain_qty - total_qty) * last_ask
242 | total_qty = remain_qty
243 |
244 | return total_notional - last_book_date['midpt'] * remain_qty
245 |
246 |
247 | def get_holdtime(matches_date):
248 | total_qty = 0
249 | total_holdtime = 0
250 | for m in matches_date:
251 | total_qty += m['qty']
252 | holdtime = (m['exittime'] - m['entrytime']).total_seconds()
253 | if (m['entrytime'].hour + m['entrytime'].minute / 60) <= 11.7 and (m['exittime'].hour + m['exittime'].minute / 60) >= 12.8:
254 | holdtime = max(0, holdtime - 5400)
255 | total_holdtime += m['qty'] * holdtime
256 |
257 | return total_holdtime / total_qty if total_qty > 0 else 0.0, total_qty
258 |
259 |
260 | def get_holdtime2(matches_date, remain_fills, lastbook_date):
261 | total_holdtime, total_qty = get_holdtime(matches_date)
262 | total_qty2 = 0
263 | total_holdtime2 = 0
264 |
265 | exit_time = datetime.fromtimestamp(lastbook_date['TimeStamp'] / 1e6)
266 | for f in remain_fills:
267 | total_qty2 += f['remain_qty']
268 | entry_time = datetime.fromtimestamp(f['TimeStamp'] / 1e6)
269 | holdtime = (exit_time - entry_time).total_seconds()
270 | if (entry_time.hour + entry_time.minute / 60) <= 11.7 and (exit_time.hour + exit_time.minute / 60) >= 12.8:
271 | holdtime = max(0, holdtime - 5400)
272 |
273 | total_holdtime2 += f['remain_qty'] * holdtime
274 |
275 | combined_qty = total_qty + total_qty2
276 | combined_holdtime = total_holdtime * total_qty + total_holdtime2
277 | return combined_holdtime / combined_qty if combined_qty > 0 else 0.0, combined_qty
278 |
279 |
280 | def get_summary(daily_info, symbol, trades, book_with_valuation, maxNotional, start_extime, end_extime, fee_ratio=0.0014, startdate=None, enddate=None):
281 | summary = defaultdict(lambda: defaultdict(dict))
282 | lastbook = get_lastbook(book_with_valuation, end_extime)
283 | for posnotional in trades:
284 | trades_posnotional = trades[posnotional]
285 | for edge_ratio in trades_posnotional:
286 | trades_edge = trades_posnotional[edge_ratio]
287 | trades_time = constraint_trades_time(trades_edge, start_extime, end_extime)
288 | for date in trades_time:
289 | if (startdate is not None and startdate > date) or (enddate is not None and enddate < date):
290 | continue
291 | try:
292 | lastbook_date = lastbook[date]
293 | except Exception as e:
294 | print(symbol, 'lastbook error', e)
295 | continue
296 | maxvolume = maxNotional / daily_info.loc[(symbol, date), 'refPrice'] // 100 * 100
297 | trades_date = trades_time[date]
298 | trades_date_maxvolume = constraint_trades_maxvolume(trades_date, maxvolume)
299 | matches_date, remaining_fills = match(trades_date_maxvolume)
300 | holdtime, matchqty = get_holdtime(matches_date)
301 | holdtime2, matchqty2 = get_holdtime2(matches_date, remaining_fills, lastbook_date)
302 | try:
303 | if len(matches_date):
304 | df = pd.DataFrame(matches_date)
305 | # df['entryTimeBin'] = df['entrytime'].apply(lambda x: x.hour + x.minute // 30 * 0.5)
306 | df['pnl'] = df['qty'] * (df['entryprice'] - df['exitprice']) * df['entry_dir'].apply(lambda x: 1.0 if x else -1.0)
307 | df['notional'] = df['qty'] * (df['entryprice'] + df['exitprice']) / 2
308 | df['fee'] = df['notional'] * fee_ratio
309 | sum_ = df[['fee', 'pnl', 'notional']].sum()
310 | else:
311 | sum_ = {
312 | 'fee': 0,
313 | 'pnl': 0,
314 | 'notional': 0,
315 | }
316 | open_pnl = get_open_pnl(lastbook_date, remaining_fills)
317 | open_notional = get_open_notional(remaining_fills)
318 | remaining_qty = get_remain_qty(remaining_fills)
319 | date_perf = {
320 | 'closed_pnl': sum_['pnl'],
321 | 'closed_fee': sum_['fee'],
322 | 'maxNotional': maxNotional,
323 | 'closed_notional': sum_['notional'],
324 | 'open_qty': remaining_qty,
325 | 'open_pnl': open_pnl,
326 | 'open_notional': open_notional,
327 | 'open_fee': abs(open_notional) * fee_ratio,
328 | 'holdtime': holdtime,
329 | 'holdtime2': holdtime2,
330 | 'open_penalty_half_spread': end_penalty_half_spread(lastbook_date, remaining_qty),
331 | 'open_penalty_whole_book': end_penalty_whole_book(lastbook_date, remaining_qty),
332 | }
333 | summary[posnotional][edge_ratio][date] = date_perf
334 | except Exception as e:
335 | print('except', e)
336 | import pdb; pdb.set_trace()
337 | return summary
338 |
339 |
340 | def gen_valuation(symbols: list, make_theolist, weights, date_first: str, date_last: str) -> pd.DataFrame:
341 | """
342 | Returns:
343 | DataFrame: book and 'value' column
344 | """
345 | quote_src = core.quote.load_data(symbols, date_first=date_first, date_last=date_last)
346 | quotes = [quote_src.get_quote(symbol) for symbol in symbols]
347 | quotes[0].alias = 'self'
348 | markup = core.markup.gen_markup(quotes[0], alpha=0.5, lag=3)
349 |
350 | theofunc_list = [T.MakeTimeTheo(quotes[0])] + make_theolist(quotes)
351 |
352 | theomatrix = T.make_theomatrix(theofunc_list, quotes[0].sampler, quote_src.get_data_num())
353 |
354 | # Sampling
355 | sampling_param = {'sample_type': 'none'}
356 | sampler = F.FitSampling(sampling_param)
357 |
358 | contract = quote_src.dict_sym2contract[symbols[0]]
359 | book_with_val = []
360 |
361 | for idx in range(len(quote_src.dateStr)):
362 | day = quote_src.dateStr[idx]
363 | raw = quote_src.dfdict[contract][idx]
364 | thmatrix_samp, markup_samp = sampler(quotes[0], theomatrix, markup, 0, selectedDays=[idx])
365 | valuation = np.dot(weights, thmatrix_samp[1:,])
366 | df_val = pd.DataFrame({'TimeStamp': thmatrix_samp[0,:], 'value': valuation})
367 | book_with_val.append(raw.merge(df_val, on='TimeStamp'))
368 |
369 |
370 | return pd.concat(book_with_val)
371 |
372 |
373 | class BasicCrossSimulationParam:
374 | def __init__(self, date_first, date_last, position_notionals, edge_ratios):
375 | self.position_notionals = position_notionals
376 | self.edge_ratios = edge_ratios
377 | self.date_first = date_first
378 | self.date_last = date_last
379 |
380 |
381 | def run_basic_cross_strategy(book_with_valuation, symbol: str, param: BasicCrossSimulationParam, daily_info):
382 | book_with_valuation['midpt'] = (book_with_valuation['Bid1'] * (1 + (book_with_valuation['Ask1Size'] == 0)) + book_with_valuation['Ask1'] * (1 + (book_with_valuation['Bid1Size'] == 0))) / 2
383 | book_with_valuation['date'] = book_with_valuation['TimeStamp'].apply(lambda x: datetime.fromtimestamp(x / 1e6).strftime('%Y%m%d'))
384 | resultsMultiLevel = cross_edge_notional_multilevel(
385 | book_with_valuation, symbol, param.position_notionals, param.edge_ratios, daily_info)
386 | print(resultsMultiLevel)
387 |
388 | max_notional = 2000000
389 | start_extime = 93200000000
390 | end_extime = 145000000000
391 | summary = get_summary(daily_info, symbol, resultsMultiLevel, book_with_valuation, max_notional, start_extime, end_extime)
392 | return summary
393 |
394 |
395 | if __name__ == '__main__':
396 | # book_parquet_dir = '/home/mhyang/Data/Experiment/DynamicEdgeIntraDay/book_with_valuation_GBDT_SH'
397 | # trade_dir = '/home/mhyang/Data/Experiment/PythonSim/GBDT_SH_Trades'
398 | # output_dir = 'GBDT_SH_Summary_0932_1450'
399 |
400 | book_parquet_dir = 'example/valuation/'
401 | trade_dir = 'example/trades'
402 | output_dir = 'example/summary'
403 |
404 | symbols = sorted([_.split('.p')[0] for _ in os.listdir(trade_dir)])
405 | symbols = ['600519']
406 | os.makedirs(output_dir, exist_ok=True)
407 | max_notional = 2000000
408 | start_extime = 93200000000
409 | end_extime = 145000000000
410 |
411 | daily_info = get_daily_info('20210801', '20210831')
412 |
413 | def default_to_regular(d):
414 | if isinstance(d, defaultdict):
415 | d = {k: default_to_regular(v) for k, v in d.items()}
416 | return d
417 |
418 | def run(symbol):
419 | print(symbol)
420 | output_path = os.path.join(output_dir, '%s.p' % symbol)
421 | if not os.path.exists(output_path):
422 | try:
423 | book_fpath = os.path.join(book_parquet_dir, '%s.parquet.gzip' % symbol)
424 | book_with_valuation = pd.read_parquet(book_fpath)
425 | book_with_valuation['date'] = book_with_valuation['TimeStamp'].apply(lambda x: datetime.fromtimestamp(x / 1e6).strftime('%Y%m%d'))
426 | book_with_valuation['midpt'] = (book_with_valuation['Bid1'] * (1 + (book_with_valuation['Ask1Size'] == 0)) + book_with_valuation['Ask1'] * (1 + (book_with_valuation['Bid1Size'] == 0))) / 2
427 | trade_fpath = os.path.join(trade_dir, '%s.p' % symbol)
428 | trades = pk.load(open(trade_fpath, 'rb'))
429 | summary = get_summary(daily_info, symbol, trades, book_with_valuation, max_notional, start_extime, end_extime)
430 | pk.dump(default_to_regular(summary), open(output_path, 'wb'))
431 | except Exception as e:
432 | print(symbol, e)
433 | else:
434 | print('%s exists' % output_path)
435 |
436 | import multiprocessing
437 |
438 | n = 8
439 | pool = multiprocessing.Pool(n)
440 | pool.map(run, symbols)
441 |
--------------------------------------------------------------------------------
/backtester/BackTester.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2021/7/26 14:05
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : BackTester.py
7 | import time
8 | from .Factor import Factor
9 | from .Position import Position
10 | from backtester.Account import Account
11 | from strategy.T0Signal import TOSignal
12 | from strategy.ClfSignal import ClfSignal
13 | from strategy.RegSignal import RegSignal
14 | from editorconfig import get_properties, EditorConfigError
15 | import pandas as pd
16 | import logging
17 | import matplotlib.pyplot as plt
18 | import numpy as np
19 | import hashlib
20 | from datetime import datetime
21 | import os
22 | import utils.utils as utils
23 | import utils.define as define
24 | from copy import deepcopy
25 | import configparser
26 |
27 |
28 | def get_fill_ret(order=[], tick=1, mkt=[]):
29 | _order_type, _price, _lot = order
30 | bid_price1, ask_price1, bid_vol1, ask_vol1 = mkt[-4:]
31 | _last_price = mkt[3]
32 | if _order_type == define.LONG:
33 | if _lot <= ask_vol1 and (not _price or _price >= ask_price1):
34 | return [ask_price1, _lot]
35 | else:
36 | return [0, 0]
37 | if _order_type == define.SHORT:
38 | if _lot <= bid_vol1 and (not _price or _price <= bid_price1):
39 | return [bid_price1, _lot]
40 | else:
41 | return [0, 0]
42 | return [0, 0]
43 |
44 |
45 | def stop_profit_loss(risk_input: dict = {}, risk_conf: dict = {}) -> tuple:
46 | '''
47 | # the range in the past 1 min is laess than 2(N) ticks, stop profit/loss will be downgraded,then tend to close the position
48 | #easier; if not, then the oppisite side, try to earn more in the order, at the same time, it may lose more, so it should
49 | #improve the strategy/signal
50 | :param risk_input:
51 | :param risk_conf:
52 | :return:
53 | '''
54 | _last_price = risk_input.get('last_price')[-120:]
55 | _range = max(_last_price) - min(_last_price)
56 | _stop_lower = risk_conf.get('stop_lower')
57 | _stop_upper = risk_conf.get('stop_upper')
58 | if _range < 2:
59 | return _stop_lower, _stop_lower
60 | else:
61 | return _stop_upper, _stop_upper
62 |
63 |
64 | def backtesting(product_id: str = 'm', trade_date: str = '20210401', signal_name: str = 'RegSignal',
65 | result_fname_digest: str = '', options: dict = {},
66 | plot_mkt: bool = True) -> tuple:
67 | '''
68 |
69 | :param product_id: product id to be tested, lower case
70 | :param trade_date: backtest trade, 'yyyymmdd'
71 | :param signal_name: signal type
72 | :param result_fname_digest: file name to cache test result, which the digest of back test params
73 | :param options: other params to be included in the back test
74 | :param plot_mkt: whether to plot and save jpg of the mkt and trade signal
75 | :return: (total return, total fee, precision, list of transaction)
76 | '''
77 |
78 | # load back test config
79 | backtesting_config = ''
80 | if not options:
81 | try:
82 | _strategy_conf = utils.get_path([define.CONF_DIR,
83 | define.STRATEGY_CONF_NAME])
84 | config = configparser.ConfigParser()
85 | config.read(_strategy_conf)
86 | signal_lst = []
87 | for k, v in config['strategy'].items():
88 | if k == 'signal_names':
89 | options.update({k: v})
90 | signal_lst = v.split(',')
91 | elif k == 'start_timestamp' or k == 'end_timestamp':
92 | options.update({k: v})
93 | else:
94 | options.update({k: float(v)})
95 | for sig_name in signal_lst:
96 | options[sig_name] = {}
97 | for k1, v1 in config[sig_name].items():
98 | options[sig_name].update({k1: float(v1)})
99 | except Exception as ex:
100 | print(ex)
101 |
102 | for key, value in options.items():
103 | backtesting_config = '{0},{1}:{2}'.format(backtesting_config, key, value)
104 |
105 | # get contract and daily cache
106 | instrument_id_df = utils.get_instrument_ids(start_date=trade_date, end_date=trade_date, product_id=product_id)
107 | instrument_id, trade_date, exchange_cd = instrument_id_df.values[0]
108 | _mul_num = utils.get_mul_num(instrument_id=instrument_id)
109 |
110 | # Load depth markets
111 | _tick_mkt_path = os.path.join(define.TICK_MKT_DIR, define.exchange_map.get(exchange_cd),
112 | '{0}_{1}.csv'.format(instrument_id, trade_date.replace('-', '')))
113 | tick_mkt = pd.read_csv(_tick_mkt_path, encoding='gbk')
114 | tick_mkt.columns = define.tb_cols
115 | logging.info('trade_date:{0}, instrument_id:{1}, shape:{2}'.format(trade_date, instrument_id, tick_mkt.shape))
116 | values = tick_mkt.values
117 |
118 | # init factor, position account signal
119 | factor = Factor(product_id=product_id, instrument_id=instrument_id, trade_date=trade_date)
120 | position = Position()
121 | account = Account()
122 | signal = ClfSignal(factor=factor, position=position, instrument_id=instrument_id,
123 | trade_date=trade_date, product_id=product_id)
124 |
125 | # Init backtest params
126 | open_fee = float(options.get('open_fee')) or 3.0
127 | close_t0_fee = float(options.get('close_t0_fee')) or 0.0
128 | fee = open_fee + close_t0_fee
129 | start_timestamp = options.get('start_timestamp') or '09:05:00'
130 | start_datetime = '{0} {1}'.format(trade_date, start_timestamp)
131 | end_timestamp = options.get('end_timestamp') or '22:50:00'
132 | # end_datetime = '{0} {1}'.format(trade_date, end_timestamp)
133 | # delay_sec = int(options.get('delay_sec')) or 5
134 | total_return = 0.0
135 | close_price = 0.0 # not the true close price, to close the position
136 | _text_lst = ['long_open', 'long_close', 'short_open', 'short_close']
137 | update_factor_time = 0.0
138 | get_signal_time = 0.0
139 | signal_delay = 0
140 | conf_signal_delay = int(options.get('signal_delay'))
141 |
142 | # robust handling, skip dates with records missing more than threshold(e.g. 0.3 here)
143 | if len(values) < define.TICK_SIZE * define.MKT_MISSING_SKIP:
144 | logging.warning("miss mkt for trade_date:{0} with len:{1} and tick size:{2}".format(trade_date, len(values),
145 | define.TICK_SIZE
146 | ))
147 | return
148 | _size = len(values)
149 | print('size is:', _size)
150 |
151 | dt_last_trans_time = None
152 | try:
153 | dt_last_trans_time = datetime.strptime(start_datetime, '%H:%M:%S')
154 | except Exception as ex:
155 | dt_last_trans_time = datetime.strptime(start_datetime.split('.')[0], '%Y-%m-%d %H:%M:%S')
156 | else:
157 | pass
158 |
159 | signal_num = 0
160 | print("**************Start Ticks with size:{0} and trade_date:{1}****************".format(_size, trade_date))
161 | for idx, item in enumerate(values):
162 | _last = item[3]
163 | _update_time = item[2]
164 | start_ts = time.time()
165 | curr_factor =factor.update_factor(item, idx=idx, multiplier=_mul_num, lag_long=int(options.get('long_windows')),
166 | lag_short=int(options.get('short_windows')))
167 | end_ts = time.time()
168 | update_factor_time += (end_ts - start_ts)
169 |
170 | # FIXME double check
171 | if not utils.is_trade(start_timestamp, end_timestamp, _update_time):
172 | # print(_update_time, 'not trade time--------------')
173 | continue
174 |
175 | if signal_delay % conf_signal_delay != 0:
176 | signal_delay += 1
177 | continue
178 | close_price = _last
179 | close_timestamp = _update_time
180 | start_ts = time.time()
181 |
182 | options.update({'instrument_id': instrument_id})
183 | options.update({'multiplier': _mul_num})
184 | options.update({'fee': fee})
185 | options.update({'factor': curr_factor})
186 | options.update({'trade_date': trade_date})
187 | s1 = time.time()
188 | options.update({'stop_lower': 2.0})
189 | options.update({'stop_upper': 20.0})
190 | stop_profit, stop_loss = stop_profit_loss({'last_price': factor.last_price}, options)
191 | options.update({'stop_profit': stop_profit, 'stop_loss': stop_loss})
192 |
193 | # Get signal
194 | _signal = signal(params=options)
195 | if _signal.signal_type != define.NO_SIGNAL:
196 | signal_num += 1
197 | print('Get Signal=>', _signal.signal_type, 'vol=>', _signal.vol, 'price=>', _signal.price, 'direction=>',
198 | _signal.direction, 'update time=>', _update_time)
199 |
200 | e1 = time.time()
201 | end_ts = time.time()
202 | get_signal_time += (end_ts - start_ts)
203 | _pos = position.get_position(instrument_id)
204 | _last_trans_time = _pos[-1][2] if _pos else start_datetime
205 | dt_curr_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
206 | _trans_gap_time = (dt_curr_time - dt_last_trans_time).seconds
207 | s2 = time.time()
208 | # handle signal, update account and position
209 | if _signal.signal_type == define.LONG_OPEN:
210 | print("Long Open with update time:{0}------".format(_update_time))
211 | _fill_price, _fill_lot = get_fill_ret(order=[define.LONG, _signal.price, _signal.vol], tick=1, mkt=item)
212 | if _fill_lot > 0:
213 | dt_last_trans_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
214 | account.add_transaction(
215 | [idx, instrument_id, _text_lst[define.LONG_OPEN], _last, _fill_price, _fill_lot, _fill_price,
216 | open_fee * _fill_lot,
217 | _update_time.split(' ')[-1],
218 | _update_time.split(' ')[-1],
219 | 0.0, 0.0])
220 | position.update_position(instrument_id=instrument_id, long_short=define.LONG, price=_fill_price,
221 | timestamp=_update_time, vol=_fill_lot, order_type=define.LONG_OPEN)
222 | account.update_fee(open_fee * _fill_lot)
223 |
224 | elif _signal.signal_type == define.SHORT_OPEN:
225 | print("Short Open with update time:{0}------".format(_update_time))
226 | _fill_price, _fill_lot = get_fill_ret(order=[define.SHORT, _signal.price, _signal.vol], tick=1, mkt=item)
227 | if _fill_lot > 0:
228 | dt_last_trans_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
229 | account.add_transaction(
230 | [idx, instrument_id, _text_lst[define.SHORT_OPEN], _last, _fill_price, _fill_lot, _fill_price,
231 | open_fee * _fill_lot,
232 | _update_time.split(' ')[-1],
233 | _update_time.split(' ')[-1],
234 | 0.0, 0.0])
235 | position.update_position(instrument_id=instrument_id, long_short=define.SHORT, price=_fill_price,
236 | timestamp=_update_time,
237 | vol=_fill_lot, order_type=define.SHORT_OPEN)
238 | account.update_fee(open_fee * _fill_lot)
239 |
240 | elif _signal.signal_type == define.LONG_CLOSE:
241 | print("Long Close with update time:{0}------".format(_update_time))
242 | _pos = position.get_position_side(instrument_id, define.LONG)
243 | if _pos:
244 | dt_curr_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
245 | try:
246 | dt_last_trans_time = datetime.strptime(_pos[2], '%H:%M:%S')
247 | except Exception as ex:
248 | dt_last_trans_time = datetime.strptime(_pos[2].split('.')[0], '%Y-%m-%d %H:%M:%S')
249 | else:
250 | pass
251 | holding_time = (dt_curr_time - dt_last_trans_time).seconds
252 |
253 | _fill_price, _fill_lot = get_fill_ret(order=[define.SHORT, _signal.price, _signal.vol], tick=1,
254 | mkt=item)
255 | if _fill_lot > 0:
256 | dt_last_trans_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
257 | # print('transaction time=>', dt_last_trans_time)
258 | curr_return = (((_fill_price - _pos[1]) * _mul_num) - fee) * _fill_lot
259 | total_return += curr_return
260 |
261 | account.add_transaction(
262 | [idx, instrument_id, _text_lst[define.LONG_CLOSE], _last, _fill_price, _fill_lot, _pos[1],
263 | close_t0_fee * _fill_lot,
264 | _pos[2].split(' ')[-1],
265 | _update_time.split(' ')[-1],
266 | holding_time,
267 | curr_return])
268 | position.update_position(instrument_id=instrument_id, long_short=define.SHORT, price=_fill_price,
269 | timestamp=_update_time, vol=_fill_lot, order_type=define.LONG_CLOSE)
270 | account.update_fee(close_t0_fee * _fill_lot)
271 | elif _signal.signal_type == define.SHORT_CLOSE:
272 | print("Short Close with update time:{0}------".format(_update_time))
273 | _pos = position.get_position_side(instrument_id, define.SHORT)
274 | if _pos:
275 | dt_curr_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
276 | try:
277 | dt_last_trans_time = datetime.strptime(_pos[2], '%H:%M:%S')
278 | except Exception as ex:
279 | dt_last_trans_time = datetime.strptime(_pos[2].split('.')[0], '%Y-%m-%d %H:%M:%S')
280 | else:
281 | pass
282 | holding_time = (dt_curr_time - dt_last_trans_time).seconds
283 |
284 | _fill_price, _fill_lot = get_fill_ret(order=[define.LONG, _signal.price, _signal.vol], tick=1, mkt=item)
285 | if _fill_lot > 0:
286 | dt_last_trans_time = datetime.strptime(_update_time.split('.')[0], '%Y-%m-%d %H:%M:%S')
287 | curr_return = ((_pos[1] - _fill_price) * _mul_num - fee) * _fill_lot
288 | total_return += curr_return
289 | account.add_transaction(
290 | [idx, instrument_id, _text_lst[define.SHORT_CLOSE], _last, _fill_price, _fill_lot, _pos[1],
291 | close_t0_fee * _fill_lot,
292 | _pos[2].split(' ')[-1],
293 | _update_time,
294 | holding_time,
295 | curr_return])
296 | position.update_position(instrument_id=instrument_id, long_short=define.LONG, price=_fill_price,
297 | timestamp=_update_time, vol=_fill_lot, order_type=define.SHORT_CLOSE)
298 | account.update_fee(close_t0_fee * _fill_lot)
299 | else: # NO_SIGNAL
300 | pass
301 | e2 = time.time()
302 | # print('handle signal time:', idx, idx/_size, e2-s2)
303 | signal_delay += 1
304 | _pos = position.get_position(instrument_id)
305 | total_return_risk = total_return
306 | total_risk = 0.0
307 | print("***************complete tick with signal num:{0} *************************".format(signal_num))
308 | if _pos:
309 | _tmp_pos = deepcopy(_pos)
310 | for item in _tmp_pos:
311 | if item[0] == define.LONG:
312 | dt_curr_time = datetime.strptime(close_timestamp.split('.')[0], '%Y-%m-%d %H:%M:%S')
313 | try:
314 | dt_last_trans_time = datetime.strptime(item[2], '%H:%M:%S')
315 | except Exception as ex:
316 | dt_last_trans_time = datetime.strptime(item[2].split('.')[0], '%Y-%m-%d %H:%M:%S')
317 | else:
318 | pass
319 | holding_time = (dt_curr_time - dt_last_trans_time).seconds
320 | # TODO to apply fill ??now assume all fill with latest price with one tick down
321 | _return = ((close_price - define.TICK - item[1]) * item[-1] - fee) * _mul_num
322 | total_return += _return
323 | total_risk += item[1] * item[-1]
324 | print('final long close with return:{0},total return after:{1} for trade_date:{2}'.format(_return,
325 | total_return,
326 | trade_date))
327 |
328 | account.add_transaction(
329 | [idx, instrument_id, _text_lst[define.LONG_CLOSE], close_price, close_price - define.TICK, item[-1],
330 | item[1],
331 | close_t0_fee * item[-1],
332 | item[2],
333 | close_timestamp, holding_time, _return])
334 | account.update_fee(close_t0_fee * item[-1])
335 | position.update_position(instrument_id=instrument_id, long_short=define.SHORT, price=close_price,
336 | timestamp=close_timestamp,
337 | vol=item[-1], order_type=define.LONG_CLOSE)
338 | else:
339 |
340 | dt_curr_time = datetime.strptime(close_timestamp.split('.')[0], '%Y-%m-%d %H:%M:%S')
341 | try:
342 | dt_last_trans_time = datetime.strptime(item[2], '%H:%M:%S')
343 | except Exception as ex:
344 | dt_last_trans_time = datetime.strptime(item[2].split('.')[0], '%Y-%m-%d %H:%M:%S')
345 | else:
346 | pass
347 | holding_time = (dt_curr_time - dt_last_trans_time).seconds
348 | # TODO to apply fill ??now assume all fill with latest price with one tick up, tick hardcode
349 | _return = ((item[1] - close_price - define.TICK) * _mul_num - fee) * item[-1]
350 | total_risk += item[1] * item[-1]
351 | total_return += _return
352 | print('final short close with return:{0},total return after:{1}'.format(_return, total_return))
353 |
354 | account.add_transaction(
355 | [idx, instrument_id, _text_lst[define.SHORT_CLOSE], close_price,
356 | close_price + define.TICK, item[-1], item[1],
357 | close_t0_fee * item[-1], item[2],
358 | close_timestamp, holding_time, _return])
359 | account.update_fee(close_t0_fee * item[-1])
360 | position.update_position(instrument_id=instrument_id, long_short=define.LONG, price=close_price,
361 | timestamp=close_timestamp,
362 | vol=item[-1], order_type=define.SHORT_CLOSE)
363 |
364 | if options.get('cache_factor') == '1':
365 | logging.info('Start cache factor')
366 | factor.cache_factor()
367 | logging.info('Complete cache factor')
368 | else:
369 | logging.info('Stip cache factor')
370 | if plot_mkt:
371 | _idx_lst = list(range(len(factor.last_price)))
372 | fig = plt.figure()
373 | ax1 = fig.add_subplot(111)
374 | ax1.plot(_idx_lst[define.PLT_START:define.PLT_END], factor.last_price[define.PLT_START:define.PLT_END])
375 | ax1.plot(_idx_lst[define.PLT_START:define.PLT_END], factor.vwap[define.PLT_START:define.PLT_END])
376 | ax1.plot(_idx_lst[define.PLT_START:define.PLT_END], factor.turning[define.PLT_START:define.PLT_END])
377 | print(np.array(factor.last_price).std())
378 | ax1.grid(True)
379 | ax1.set_title('{0}_{1}'.format(instrument_id, trade_date))
380 | xtick_labels = [item[:-3] for item in factor.update_time]
381 | ax1.set_xticks(_idx_lst[::3600])
382 | min_lst = []
383 | ax1.set_xticklabels(xtick_labels[::3600])
384 | # ax1.set_xticks(factor.update_time[::3600])
385 | # ax1.set_xticks(x_idx, xtick_labels, rotation=60, FontSize=6)
386 | for item in account.transaction:
387 | _t_lst = ['lo', 'lc', 'so', 'sc']
388 | ax1.text(item[0], item[3], s='{0}'.format(item[2]))
389 |
390 | ax2 = ax1.twinx()
391 | ax2.plot(_idx_lst[define.PLT_START:define.PLT_END], factor.trend_short[define.PLT_START:define.PLT_END], 'r')
392 |
393 | _ret_path = utils.get_path([define.RESULT_DIR, define.BT_DIR, '{0}_{1}.jpg'.format(instrument_id, trade_date)])
394 | plt.savefig(_ret_path)
395 | long_open, short_open, correct_long_open, wrong_long_open, correct_short_open, wrong_short_open = 0, 0, 0, 0, 0, 0
396 | total_fee = 0.0
397 | total_holding_time = 0.0
398 | max_holding_time = -np.inf
399 | min_holding_time = np.inf
400 | for item in account.transaction:
401 | if item[2] == 'long_open':
402 | long_open += 1
403 | elif item[2] == 'short_open':
404 | short_open += 1
405 | elif item[2] == 'long_close':
406 | total_holding_time += item[-2]
407 | max_holding_time = max(max_holding_time, item[-2])
408 | min_holding_time = min(min_holding_time, item[-2])
409 | if item[-1] > 0:
410 | correct_long_open += 1
411 | else:
412 | wrong_long_open += 1
413 | else: # short close
414 | total_holding_time += item[-2]
415 | max_holding_time = max(max_holding_time, item[-2])
416 | min_holding_time = min(min_holding_time, item[-2])
417 | if item[-1] > 0:
418 | correct_short_open += 1
419 | else:
420 | wrong_short_open += 1
421 | average_holding_time = total_holding_time / (long_open + short_open) if long_open + short_open > 0 else 0.0
422 | print("******************back test results for date:{0}*********************".format(trade_date))
423 | print('trade date', trade_date)
424 | print(long_open, short_open, correct_long_open, wrong_long_open, correct_short_open, wrong_short_open)
425 | print('total return:', total_return)
426 | print('total fee:', account.fee)
427 | print('total risk:', total_risk)
428 | print('update factor time:', update_factor_time)
429 | print('get signal time:', get_signal_time)
430 | print("average_holding_time:", average_holding_time)
431 | print("max_holding_time:", max_holding_time)
432 | print("min_holding_time:", min_holding_time)
433 | print("******************back test results for date:{0}*********************".format(trade_date))
434 |
435 | precision = (correct_long_open + correct_short_open) / (
436 | long_open + short_open) if long_open + short_open > 0 else 0.0
437 | # f = open("results/results_{0}.txt".format(product_id), "a")
438 | # _key = '{0}_{1}'.format(trade_date, instrument_id)
439 | # result_fname_digest = hashlib.sha256(bytes(backtesting_config, encoding='utf-8')).hexdigest()
440 | # f.write("{0}:{1}\n".format(backtesting_config, result_fname_digest))
441 | _ret_path = utils.get_path([define.RESULT_DIR, define.BT_DIR, '{0}.csv'.format(result_fname_digest)])
442 | try:
443 | result_df = pd.read_csv(_ret_path)
444 | except Exception as ex:
445 | result_df = pd.DataFrame(
446 | {'trade_date': [], 'product_id': [], 'instrument_id': [], 'total_return_final': [],
447 | 'total_return_unclose': [],
448 | 'total_fee': [],
449 | 'unclosed_value': [], 'precision': [], 'long_open': [], 'short_open': [],
450 | 'correct_long_open': [], 'wrong_long_open': [], 'correct_short_open': [], 'wrong_short_open': [],
451 | 'average_holding_time': [], 'max_holding_time': [], 'min_holding_time': []
452 | })
453 |
454 | result_df = result_df.append(
455 | {'trade_date': trade_date, 'product_id': product_id, 'instrument_id': instrument_id,
456 | 'total_return_final': total_return, 'total_return_unclose': total_return_risk,
457 | 'total_fee': account.fee,
458 | 'unclosed_value': total_risk, 'precision': precision,
459 | 'long_open': long_open, 'short_open': short_open, 'correct_long_open': correct_long_open,
460 | 'wrong_long_open': wrong_long_open, 'correct_short_open': correct_short_open,
461 | 'wrong_short_open': wrong_short_open, 'average_holding_time': average_holding_time,
462 | 'max_holding_time': max_holding_time, 'min_holding_time': min_holding_time
463 | }, ignore_index=True)
464 |
465 | result_df.to_csv(_ret_path, index=False)
466 | ret = (total_return, account.fee, precision, account.transaction)
467 | return ret
468 |
--------------------------------------------------------------------------------
/research/daily_models/com_daily.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/4/14 14:19
4 | # @Author : rpyxqi@gmail.com
5 | # @Site :
6 | # @File : com_daily.py
7 |
8 | import math
9 | import uqer
10 | import datetime
11 | import pprint
12 | import pandas as pd
13 | import numpy as np
14 | from uqer import DataAPI
15 | import matplotlib.pyplot as plt
16 | import backtester.Account as Account
17 | import backtester.Position as Position
18 | from sklearn.model_selection import train_test_split
19 | from sklearn.linear_model import LinearRegression
20 | from sklearn import metrics
21 | import utils.define as define
22 |
23 | uqer_client = uqer.Client(token="6aa0df8d4eec296e0c25fac407b332449112aad6c717b1ada315560e9aa0a311")
24 |
25 | margin_overwrite = {'p': 11}
26 |
27 | ['P', 'rb', 'm', 'eg', 'fu']
28 |
29 | open_fee_overwrite = {'p': 2.5, 'rb': 5.08, 'm': 2.5}
30 | close_fee_overwrite = {'p': 2.5, 'rb': 5.08, 'm': 2.5} # rb is close today
31 |
32 |
33 | def select_product(product_lst: list = ['rb', 'm'], start_date: str = '', end_date: str = '',
34 | trade_date: str = '') -> pd.DataFrame:
35 | sum_lst = []
36 | for pid in product_lst:
37 | df = DataAPI.MktMFutdGet(mainCon=u"1", contractMark=u"", contractObject=pid, tradeDate=u"",
38 | startDate=start_date,
39 | endDate=end_date, field=u"", pandas="1")
40 | trade_dates = list(df['tradeDate'])[-5:]
41 | vol_std_lst = []
42 | instrument_id_lst = list(df['ticker'])[-5:]
43 |
44 | for idx, trade_date in enumerate(trade_dates):
45 | df_intraday = DataAPI.FutBarHistOneDay2Get(instrumentID=instrument_id_lst[idx], date=trade_date, unit=u"1",
46 | field=u"",
47 | pandas="1")
48 | # print(instrument_id_lst[idx], trade_date)
49 | vol_std_lst.append(df_intraday['closePrice'].std())
50 | # print(vol_std_lst)
51 | _liquid = df['turnoverVol'].mean()
52 | _vol = sum(vol_std_lst) / len(vol_std_lst)
53 | # print(pid, _liquid, _vol)
54 | _fee = (open_fee_overwrite.get(pid.lower()) or 0.0) + (close_fee_overwrite.get(pid.lower()) or 0.0)
55 | sum_lst.append([pid, _liquid, _vol, _fee])
56 | df_sum = pd.DataFrame(sum_lst, columns=['product_id', 'liquid', 'vol', 'fee'])
57 | df_sum['liquid_rank'] = df_sum['liquid'].rank()
58 | df_sum['vol_rank'] = df_sum['vol'].rank()
59 | df_sum['score'] = df_sum['liquid_rank'] + df_sum['vol_rank']
60 | df_sum = df_sum.sort_values(by='score', ascending=False)
61 | print(df_sum)
62 |
63 |
64 | def get_trend_signal(df_mkt: pd.DataFrame, k1: float = 0.2, k2: float = 0.2, std_weight=0.2, sod: bool = True):
65 | # ['secID', 'ticker', 'exchangeCD', 'secShortName', 'secShortNameEN',
66 | # 'tradeDate', 'contractObject', 'contractMark', 'preSettlePrice',
67 | # 'preClosePrice', 'openPrice', 'highestPrice', 'lowestPrice',
68 | # 'settlePrice', 'closePrice', 'turnoverVol', 'turnoverValue', 'openInt',
69 | # 'chg', 'chg1', 'chgPct', 'mainCon', 'smainCon']
70 | # df_mkt = df_mkt.dropna()
71 | sig = []
72 | upbound = []
73 | lowerbound = []
74 | ref_price = 'preClosePrice' if sod else 'closePrice' # FIXME should be open price
75 | for item in df_mkt[[ref_price, 'hh', 'lc', 'hc', 'll', 'accstd']].values:
76 | _range = max(item[1] - item[2], item[3] - item[4])
77 | upbound.append(item[0] + k1 * _range + std_weight * item[5])
78 | lowerbound.append(item[0] - k2 * _range - std_weight * item[5])
79 | if item[-1] > item[0] + k1 * _range:
80 | sig.append(define.LONG)
81 | elif item[-1] < item[0] - k2 * _range:
82 | sig.append(define.SHORT)
83 | else:
84 | sig.append(define.NO_SIGNAL)
85 | df_mkt['upbound'] = upbound
86 | df_mkt['lowbound'] = lowerbound
87 | df_mkt['signal'] = sig
88 | return df_mkt
89 |
90 |
91 | def get_revert_signal(df_mkt: pd.DataFrame, k1: float = 0.2, k2: float = 0.2, sod: bool = True):
92 | pass
93 |
94 |
95 | def get_signal(start_date: str = '', end_date: str = '', product_id: str = '', options: dict = {},
96 | sod: bool = True) -> pd.DataFrame:
97 | '''
98 |
99 | :param start_date:
100 | :param end_date:
101 | :param product_id:
102 | :param options:
103 | :param sod: True, the signal is available start of the day(for backtest mode);False:eod of the day(for live )
104 | :return:
105 | '''
106 | df = DataAPI.MktMFutdGet(mainCon=u"1", contractMark=u"", contractObject=product_id, tradeDate=u"",
107 | startDate=start_date,
108 | endDate=end_date, field=u"", pandas="1")
109 | # ['secID', 'ticker', 'exchangeCD', 'secShortName', 'secShortNameEN',
110 | # 'tradeDate', 'contractObject', 'contractMark', 'preSettlePrice',
111 | # 'preClosePrice', 'openPrice', 'highestPrice', 'lowestPrice',
112 | # 'settlePrice', 'closePrice', 'turnoverVol', 'turnoverValue', 'openInt',
113 | # 'chg', 'chg1', 'chgPct', 'mainCon', 'smainCon']
114 | lag_window = options.get('lag_window') or 5
115 | k1 = options.get('k1') or 0.2
116 | k2 = options.get('k2') or 0.2
117 | if sod: # ignore the latest eod signal, for backtest; for the last date, got the previous market data
118 | df['hh'] = df['highestPrice'].rolling(lag_window).max().shift()
119 | df['lc'] = df['closePrice'].rolling(lag_window).min().shift()
120 | df['hc'] = df['closePrice'].rolling(lag_window).max().shift()
121 | df['ll'] = df['lowestPrice'].rolling(lag_window).min().shift()
122 | df['accstd'] = df['settlePrice'].rolling(lag_window).std().shift()
123 | else: # the the signal when the date end, signal for the next day, for live trade
124 | df['hh'] = df['highestPrice'].rolling(lag_window).max()
125 | df['lc'] = df['closePrice'].rolling(lag_window).min()
126 | df['hc'] = df['closePrice'].rolling(lag_window).max()
127 | df['ll'] = df['lowestPrice'].rolling(lag_window).min()
128 | df['accstd'] = df['settlePrice'].rolling(lag_window).std()
129 |
130 | df = df.dropna()
131 | df = get_trend_signal(df_mkt=df, k1=k1, k2=k2, sod=sod)
132 | # ls_tag = []
133 | # upbound = []
134 | # lowbound = []
135 | # for item in df.values:
136 | # _max_min_range = item[-3] - item[-2] # acchigh-acclow
137 | # _std = item[-1] # acc std
138 | # _min_mix_weight = options.get('min_max_weight') or 0.2
139 | # _std_weight = options.get('std_weight') or 0.2
140 | # _up_range = item[8] + _min_mix_weight * _max_min_range + _std_weight * _std
141 | # _low_range = item[8] - _min_mix_weight * _max_min_range - _std_weight * _std
142 | # upbound.append(_up_range)
143 | # lowbound.append(_low_range)
144 | # if item[9] > _up_range: # open > acchigh
145 | # ls_tag.append(define.SHORT) # short
146 | # elif item[9] < _low_range: # open < acclow
147 | # ls_tag.append(define.LONG) # long
148 | # else:
149 | # ls_tag.append(define.NO_SIGNAL)
150 | #
151 | # df['upbound'] = upbound
152 | # df['lowbound'] = lowbound
153 | # df['signal'] = ls_tag
154 | return df
155 |
156 |
157 | def handle_bar(signal_df: pd.DataFrame, strategy_option: dict, strategy_name: str = '', save_cache: bool = False):
158 | init_cash = strategy_option.get('init_cash') or 100000
159 | stop_profit = strategy_option.get('stop_profit') or 0.2
160 | stop_loss = strategy_option.get('stop_loss') or 0.2
161 | account = Account.Account(init_cash)
162 | pos = Position.Position()
163 | init_risk_ratio = strategy_option.get('inti_risk_ratio') or 0.3
164 |
165 | mkt_val1 = []
166 | mkt_val2 = []
167 |
168 | product_id = list(signal_df['contractObject'])[0]
169 |
170 | df_contract = DataAPI.FutuGet(secID=u"", ticker=u"", exchangeCD=u"", contractStatus="", contractObject=product_id,
171 | prodID="", field=u"", pandas="1")
172 |
173 | max_drawdown = []
174 | risk_ratio = []
175 | pos_vol_lst = []
176 | transac_lst = []
177 | for item in signal_df.values:
178 | instrument_id = item[1]
179 | open_price = item[10]
180 | close_price = item[14]
181 | settle_price = item[13]
182 | chg = item[19] # close price
183 | chg1 = item[20] # settle price
184 | pre_settle_price = item[8]
185 | pre_close = item[9]
186 | _contract_info = df_contract[df_contract.ticker == instrument_id].to_dict('record')
187 | margin_val = 0.0
188 | pos_vol = 0
189 | transac_vol = 0
190 | if _contract_info:
191 | open_fee = open_fee_overwrite.get(product_id.lower()) or _contract_info[0].get('tradeCommiNum') # 元
192 | close_fee = close_fee_overwrite.get(product_id.lower()) or _contract_info[0].get('tradeCommiNum') # %
193 | multiplier = _contract_info[0].get('contMultNum')
194 | tick_val = _contract_info[0].get('minChgPriceNum')
195 | margin_num = margin_overwrite.get(product_id.lower()) or _contract_info[0].get('tradeMarginRatio')
196 | else:
197 | open_fee = 2
198 | close_fee = 2
199 | multiplier = 10
200 | tick_val = 1
201 | margin_num = 12
202 | _pos = pos.get_position(product_id)
203 | # position.update_position(instrument_id=instrument_id, long_short=define.SHORT, price=_fill_price,
204 | # timestamp=_update_time, vol=_fill_lot, order_type=define.LONG_CLOSE)
205 | # update_lst.append([long_short, price, timestamp, vol])
206 | # print('start of check pos', item[5], _pos)
207 | if _pos: # has position
208 | for long_short, price, ts, vol in _pos:
209 | if item[-1] == long_short or item[
210 | -1] == define.NO_SIGNAL: # hv pos and same signal direction or no signal, pos not change,update account fee and mv
211 | if long_short == define.LONG:
212 | _profit = (pre_close / price - 1) * (100 / margin_num)
213 | _profit1 = (pre_settle_price / price - 1) * (100 / margin_num)
214 | if _profit > stop_profit or _profit1 < -stop_loss: # TODO use close price, close pos, stop profit/loss
215 | print("stop profit/loss for long, profit=>", _profit)
216 | _fee = close_fee * vol
217 | _val = (open_price - pre_close - tick_val) * vol * multiplier
218 | _val1 = (open_price - pre_settle_price - tick_val) * vol * multiplier
219 | account.update_fee(_fee)
220 | account.update_market_value(_val, _val1, _fee)
221 | pos.update_position(instrument_id=product_id, long_short=define.SHORT, price=open_price,
222 | timestamp='', vol=vol, order_type=define.LONG_CLOSE)
223 | margin_val = 0.0
224 | pos_vol = 0
225 | transac_vol = -vol
226 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
227 |
228 | else:
229 | _val = chg * vol * multiplier
230 | _val1 = chg1 * vol * multiplier
231 | _val = _val if long_short == define.LONG else -_val
232 | _val1 = _val1 if long_short == define.LONG else -_val1
233 | account.update_fee(_fee)
234 | account.update_market_value(_val, _val1, _fee)
235 | margin_val = settle_price * vol * multiplier * margin_num / 100
236 | pos_vol = vol
237 | transac_vol = 0
238 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
239 | elif long_short == define.SHORT:
240 | _profit = (price / pre_close - 1) * (100 / margin_num)
241 | _profit1 = (price / pre_close - 1) * (100 / margin_num)
242 | if _profit > stop_profit or _profit1 < -stop_loss: # TODO use close price, close pos, stop profit/loss
243 | print("stop profit/loss for short=>", _profit)
244 | _fee = close_fee * vol
245 | _val = (pre_close - open_price - tick_val) * vol * multiplier
246 | _val1 = (pre_settle_price - open_price - tick_val) * vol * multiplier
247 | account.update_fee(_fee)
248 | account.update_market_value(_val, _val1, _fee)
249 | pos.update_position(instrument_id=product_id, long_short=define.LONG, price=open_price,
250 | timestamp='', vol=vol, order_type=define.SHORT_CLOSE)
251 | margin_val = 0.0
252 | pos_vol = 0
253 | transac_vol = -vol
254 | # print(item[5], 'pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
255 | else:
256 | _val = chg * vol * multiplier
257 | _val1 = chg1 * vol * multiplier
258 | _val = _val if long_short == define.LONG else -_val
259 | _val1 = _val1 if long_short == define.LONG else -_val1
260 | account.update_fee(_fee)
261 | account.update_market_value(_val, _val1, _fee)
262 | margin_val = settle_price * vol * multiplier * margin_num / 100
263 | pos_vol = -vol
264 | transac_vol = 0
265 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
266 | elif long_short == define.LONG: # long pos and short signal, close long;remarks要不要反手??
267 | _fee = close_fee * vol
268 | _val = (open_price - pre_close - tick_val) * vol * multiplier
269 | _val1 = (open_price - pre_settle_price - tick_val) * vol * multiplier
270 | account.update_fee(_fee)
271 | account.update_market_value(_val, _val1, _fee)
272 | pos.update_position(instrument_id=product_id, long_short=define.SHORT, price=open_price,
273 | timestamp='', vol=vol, order_type=define.LONG_CLOSE)
274 |
275 | margin_val = 0.0
276 | pos_vol = 0
277 | transac_vol = -vol
278 | # print('close pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
279 | elif long_short == define.SHORT: # short pos and long signal, close short,remarks 要不要反手?
280 | _fee = close_fee * vol
281 | _val = (pre_close - open_price - tick_val) * vol * multiplier
282 | _val1 = (pre_settle_price - open_price - tick_val) * vol * multiplier
283 | account.update_fee(_fee)
284 | account.update_market_value(_val, _val1, _fee)
285 | pos.update_position(instrument_id=product_id, long_short=define.LONG, price=open_price,
286 | timestamp='', vol=vol, order_type=define.SHORT_CLOSE)
287 |
288 | margin_val = 0.0
289 | pos_vol = 0
290 | transac_vol = -vol
291 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
292 | else:
293 | pass
294 | else: # no position
295 | _mkt_val1 = account.get_trade_market_values()
296 | available_cash = _mkt_val1[-1] if _mkt_val1 else init_cash
297 | order_vol = int(available_cash * init_risk_ratio / (open_price * multiplier * margin_num / 100))
298 | if item[-1] == define.LONG: # no pos and long signal,open long
299 | _fee = open_fee * order_vol
300 | _val = (
301 | close_price - open_price - tick_val) * order_vol * multiplier # assume open price is open_price - 1tick
302 | _val1 = (
303 | settle_price - open_price - tick_val) * order_vol * multiplier # assume open price is open_price - 1tick
304 | account.update_fee(_fee)
305 | account.update_market_value(_val, _val1, _fee)
306 | pos.update_position(instrument_id=product_id, long_short=define.LONG, price=open_price + tick_val,
307 | timestamp='',
308 | vol=order_vol, order_type=define.LONG_OPEN)
309 |
310 | margin_val = settle_price * order_vol * multiplier * margin_num / 100
311 | pos_vol = order_vol
312 | transac_vol = order_vol
313 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
314 | elif item[-1] == define.SHORT: # no pos and short signal, open short
315 | _fee = open_fee * order_vol
316 | _val = (
317 | open_price - close_price - tick_val) * order_vol * multiplier # assume open price is open_price - 1tick
318 | _val1 = (
319 | open_price - settle_price - tick_val) * order_vol * multiplier # assume open price is open_price - 1tick
320 | account.update_fee(_fee)
321 | account.update_market_value(_val, _val1, _fee)
322 | pos.update_position(instrument_id=product_id, long_short=define.SHORT, price=open_price - tick_val,
323 | timestamp='',
324 | vol=order_vol, order_type=define.SHORT_OPEN)
325 |
326 | margin_val = settle_price * order_vol * multiplier * margin_num / 100
327 | pos_vol = -order_vol
328 | transac_vol = order_vol
329 | # print('pos vol:', pos_vol, 'get_position vol:', pos.get_position(instrument_id))
330 | else: # no actions needed
331 | pass
332 | # print(pos.get_position(product_id), item[5], "??????")
333 | mkt_val1.append(account.get_trade_market_values()[-1])
334 | mkt_val2.append(account.get_settle_market_values()[-1])
335 | _max_his_mv = max(account.get_trade_market_values())
336 | _drawdown_val = max(_max_his_mv - account.get_trade_market_values()[-1], 0)
337 | # print(_max_his_mv, account.get_trade_market_values()[-1], _drawdown_val)
338 | _tmp_drawdown_ratio = _drawdown_val / _max_his_mv
339 | max_drawdown.append(_tmp_drawdown_ratio)
340 | risk_ratio.append(margin_val / mkt_val1[-1])
341 | pos_vol_lst.append(pos_vol)
342 | transac_lst.append(transac_vol)
343 | # print(pos.get_position(instrument_id), item[5], "??????")
344 |
345 | signal_df['mkt_val1'] = mkt_val1
346 | signal_df['mkt_val2'] = mkt_val2
347 | signal_df['max_drawdown'] = max_drawdown
348 | signal_df['pos'] = pos_vol_lst
349 | signal_df['trans'] = transac_lst
350 | signal_df['risk_ratio'] = risk_ratio
351 | bc_start = list(signal_df['preClosePrice'])[0]
352 | bc_return = [item / bc_start - 1 for item in signal_df['closePrice']]
353 |
354 | if save_cache:
355 | signal_df.to_csv('{0}.csv'.format(strategy_name), index=False)
356 | signal_lst = [1 if item == define.LONG else -1 if item == define.SHORT else 0 for item in signal_df['signal']]
357 | evaluate_ret = []
358 |
359 | # trade_mkt_values = account.get_trade_market_values()
360 | # settle_mkt_values = account.get_settle_market_values()
361 | net_val1 = [item / init_cash for item in signal_df['mkt_val1']]
362 | net_val2 = [item / init_cash for item in signal_df['mkt_val2']]
363 | _idx_lst = list(range(signal_df.shape[0]))
364 | trade_date_str = [item.replace('-', '') for item in signal_df['tradeDate']]
365 |
366 | return_lst = np.array([item - 1 for item in net_val1])
367 | sharp_ratio = (net_val1[-1] / net_val1[0] - 1) / return_lst.std()
368 | long_sig = len([item for item in signal_lst if item == 1])
369 | short_sig = len([item for item in signal_lst if item == -1])
370 | long_holding = len([item for item in pos_vol_lst if item > 0])
371 | short_holding = len([item for item in pos_vol_lst if item < 0])
372 | open_trans = len([item for item in transac_lst if item > 0])
373 | close_trans = len([item for item in transac_lst if item < 0])
374 | bc_sharp = bc_return[-1] / np.array(bc_return).std()
375 | evaluate_ret.append(net_val1[-1] / net_val1[0] - 1)
376 | evaluate_ret.append(return_lst.std())
377 | evaluate_ret.append(sharp_ratio)
378 | evaluate_ret.append(max(max_drawdown))
379 | evaluate_ret.append(max(risk_ratio))
380 | evaluate_ret.append(long_sig)
381 | evaluate_ret.append(short_sig)
382 | evaluate_ret.append(long_holding)
383 | evaluate_ret.append(short_holding)
384 | evaluate_ret.append(open_trans)
385 | evaluate_ret.append(close_trans)
386 | evaluate_ret.append(bc_return[-1])
387 | evaluate_ret.append(bc_sharp)
388 | tick_interval = 30
389 | fig = plt.figure()
390 | fig.tight_layout()
391 | plt.title('{0}'.format(strategy_name))
392 | plt.axis('off')
393 | plt.subplots_adjust(wspace=0.5, hspace=0.5)
394 | ax1 = fig.add_subplot(221)
395 | ax1.plot(net_val1, label='net value(by close)')
396 | # ax1.plot(df['acclow'], label='acclow')
397 | ax1.legend()
398 |
399 | # ax2 = ax1.twinx()
400 | # # ax2.plot(net_val2, 'r', label='nv2')
401 | # ax2.legend()
402 | ax1.set_xticks(_idx_lst[::tick_interval])
403 | ax1.set_xticklabels(trade_date_str[::tick_interval], rotation=45, fontsize=5)
404 |
405 | ax3 = fig.add_subplot(222)
406 | ax3.plot(signal_df['closePrice'], 'r', label='closePrice')
407 | ax3.legend()
408 | ax3.set_xticks(_idx_lst[::tick_interval])
409 | ax3.set_xticklabels(trade_date_str[::tick_interval], rotation=45, fontsize=5)
410 |
411 | ax8 = ax3.twinx()
412 | ax8.bar(_idx_lst, transac_lst, label='pos_vol')
413 |
414 | # ax4 = ax3.twinx()
415 | ax4 = fig.add_subplot(223)
416 | ax4.bar(_idx_lst, signal_lst, label='signal')
417 | ax4.legend()
418 | ax4.set_xticks(_idx_lst[::tick_interval])
419 | ax4.set_xticklabels(trade_date_str[::tick_interval], rotation=45, fontsize=5)
420 |
421 | ax6 = ax4.twinx()
422 | ax6.plot(signal_df['upbound'], 'r', label='upbound')
423 | ax6.plot(signal_df['lowbound'], 'r', label='lowbound')
424 | ax6.plot(signal_df['preClosePrice'], 'g', label='pre_close')
425 |
426 | ax5 = fig.add_subplot(224)
427 | ax5.plot(max_drawdown, label='max_drawdown')
428 | ax5.legend()
429 | ax5.set_xticks(_idx_lst[::tick_interval])
430 | ax5.set_xticklabels(trade_date_str[::tick_interval], rotation=45, fontsize=5)
431 |
432 | ax7 = ax5.twinx()
433 | ax7.plot(risk_ratio, 'r', label='risk ratio')
434 | if save_cache:
435 | plt.savefig('{0}.jpg'.format(strategy_name))
436 | # plt.show()
437 | return evaluate_ret
438 |
439 |
440 | def backtest(start_date: str = '20210403', end_date: str = '20220419', save_factor: bool = False):
441 | min_max_weight_lst = [0.2, 0.3, 0.5]
442 | # min_max_weight_lst = [0.2]
443 | std_weight_lst = [-0.1, 0, 0.2, 0.3, 0.5]
444 | # std_weight_lst = [0.2]
445 | lag_windows_lst = [5, 12, 26]
446 | # lag_windows_lst = [5]
447 | # product_id_lst = ['P', 'rb', 'm', 'eg', 'fu']
448 | product_id_lst = ['P', 'rb']
449 | k1_lst = [0.2]
450 | k2_lst = [0.2]
451 | back_test_ret = []
452 | strategy_option = {'stop_profit': 0.3, 'stop_loss': 0.5, 'init_cash': 100000, 'inti_risk_ratio': 0.3}
453 | for w1 in k1_lst:
454 | for w2 in k2_lst:
455 | for std_weight in std_weight_lst:
456 | for product_id in product_id_lst:
457 | for lag_window in lag_windows_lst:
458 | strategy_name = "{0}_{1}_{2}_{3}".format(product_id, lag_window, w1, w2)
459 | signal_option = {'k1': w1, 'std_weight': std_weight, 'k2': w2,
460 | 'lag_window': lag_window}
461 | df = get_signal(start_date=start_date, end_date=end_date, product_id=product_id,
462 | options=signal_option, sod=True)
463 | ret = handle_bar(signal_df=df, strategy_option=strategy_option, strategy_name=strategy_name,
464 | save_cache=save_factor)
465 | # select_product(start_date='20220401', end_date='20220415', product_lst=['rb', 'm', ])
466 | ret.append(product_id)
467 | ret.append(start_date)
468 | ret.append(end_date)
469 | ret.append(strategy_name)
470 | back_test_ret.append(ret)
471 | bt_df = pd.DataFrame(back_test_ret,
472 | columns=['acc_return', 'std', 'sharp_ratio', 'max_drawdown', 'max_risk_ratio', 'long_sig',
473 | 'short_sig', 'long_holding', 'short_holding', 'open_trans', 'close_trans',
474 | 'bc_return',
475 | 'bc_sharp_ratio', 'product_id',
476 | 'start_date', 'end_date', 'strategy_name'])
477 | bt_df.to_csv('back_test_result.csv', index=False)
478 |
479 |
480 | def get_eod_signal():
481 | pass
482 |
483 |
484 | if __name__ == '__main__':
485 | backtest(start_date='20210403', end_date='20220419', save_factor=False)
486 |
--------------------------------------------------------------------------------
/corr.csv:
--------------------------------------------------------------------------------
1 | ,log_return,open_close_ratio,price_spread,buy_sell_spread,oi,oir,aoi,slope,cos,macd,dif,dea,bs_vol,trend_0,log_return_0,bsvol_volume_0,trend_1,log_return_1,bsvol_volume_1,label_0,rv_0,price_chg_0,label_clf_0,label_1,rv_1,price_chg_1,label_clf_1,trend_ls_diff,trend_ls_ratio,volume_ls_ratio,turnover_ls_ratio,bs_vol_ls_ratio,bsvol_volume_ls_ratio,norm_lastprice,norm_openinterest,norm_interestdiff,norm_volume,norm_bidvolume1,norm_askvolume1,norm_vwap,norm_wap,norm_bs_tag,norm_volume_ls_diff
2 | log_return,1.0,-0.001909949153267583,-0.011322941331217851,0.01151094444604926,-0.04042932986770494,-0.028231821391445637,-0.04092879521069447,0.5432372099461207,0.5256817719727389,0.15709365258539715,0.07036867889680094,0.02590204426329577,0.36642862863579895,0.28935679860375996,0.28462427236158183,0.09070336289428182,0.18327645850335914,0.18169826891415178,0.058694026936791946,0.36939565843947203,0.016299696091755373,-0.2452747285184763,0.05501590071220928,0.2789478255086415,0.011232752126037282,-0.18275654382672596,0.04637147346560956,0.2219724586039065,0.02248117677720777,0.006144528876721859,0.006163724060260921,-0.0077051058846247045,-0.008400306094597675,0.025550012849563108,0.0016377857662318784,0.0017756125544669292,0.023739479157777535,-0.05793142753675455,0.05041786715463712,0.01174928073368846,0.00831067569562216,0.7799302620131483,-0.0033881076144420025
3 | open_close_ratio,-0.001909949153267583,1.0,0.010759143271798416,-0.010169305844265158,0.0027401594939323783,-0.004075399522453756,0.0019995351406224726,-0.0018034783842755797,-0.0028909726488215203,-0.003561554244411846,0.0153497252515745,0.017369729680281443,-0.02345186856495664,0.007402878472484531,0.007574577866273225,-0.0016315989094493385,0.02401897375457497,0.022655239228405677,0.0011161409214845252,-0.008157562027375371,-0.012382822766602079,-0.007283501168432657,-0.010436211117863211,-0.006262491961302007,-0.016064995876277362,-0.005490545466873095,-0.009093195973181612,-0.008251418824299068,-0.009572022204702133,0.006351558096494069,0.0063610224548742095,0.002429401854186942,0.0012108893752758108,0.041089524033103036,0.02709960272594908,0.23309250267300968,0.01699043437126748,0.0017671644254506874,0.00482710498620479,0.04112423683053382,0.04064287131517503,-0.013868880619242673,0.013794460670124622
4 | price_spread,-0.011322941331217851,0.010759143271798416,1.0,-0.9998907053244683,0.08805672687849317,0.02786474446755065,0.03337186764364601,-0.006026991930247215,-0.003382078702084296,0.0011553227238044806,0.009023794838967054,0.009204724500023265,0.03675925697594281,0.008271409412813133,0.009923362938337841,-0.025503873981625926,0.0018937766020748583,0.0034540050556136734,-0.022576964019916608,-0.0210841355653561,-0.18293246643979927,-0.014830977523974125,-0.06867130750973274,-0.017372080936605125,-0.17059873416850516,-0.012462046829067219,-0.04045205138270358,0.008755990348824322,-0.012713313015204014,-0.07153820161594453,-0.07153775107589254,0.0005745450245647402,0.001884262418507718,0.013426474043960327,0.017222421644095497,0.031080289670741546,-0.17103032842120514,-0.048721028082614144,-0.014040665235812664,0.013130830466571871,0.01339544510464434,-0.039652281053981615,0.04553697472655794
5 | buy_sell_spread,0.01151094444604926,-0.010169305844265158,-0.9998907053244683,1.0,-0.0886072572923769,-0.02790900249689699,-0.033838731197169146,0.006255551277796912,0.0035616345861296615,-0.0010383545962504416,-0.007971827995708449,-0.008126176906679158,-0.03673993852253436,-0.007495170372572372,-0.009148896319394575,0.0257278830582537,-0.0006456330663136456,-0.0021681995558814203,0.023035088857052015,0.020870635127052856,0.1828137190974216,0.014490471970812543,0.06890639960328447,0.01699192634228204,0.17044513627417834,0.011986134395992568,0.04088763300403798,-0.00870568159244484,0.012645761420629969,0.07167242859740797,0.07167245044534787,-0.0004771819932714005,-0.0017958067638953763,0.0009898114853633783,-0.010478300047777384,-0.03016456149377878,0.17116359808955356,0.04839132326040783,0.015071571174353565,0.0012876978139854374,0.0010255800932542295,0.039826838470452056,-0.04563182007460571
6 | oi,-0.04042932986770494,0.0027401594939323783,0.08805672687849317,-0.0886072572923769,1.0,0.035974571943457635,0.9778707639079527,-0.01669337804471738,-0.01035335152741857,0.020050578380352103,-0.004031536274617544,-0.010484088456814372,-0.021345041358703905,0.0014923586263610785,-1.736166064074487e-05,-0.003996286919903948,-0.016483405681227,-0.01480259409550331,-0.00988390389626022,0.010168212937281255,-0.026845873331659564,0.03609804472367022,-0.012923469828317862,0.006231535622128172,-0.026495754452218176,0.025323440743364754,-0.008470170386257529,0.01370491460513496,-0.007314846746996655,-0.016238447726833836,-0.016244292667572123,-0.005796848587927473,-0.00414045202908549,-0.02865597314141963,-0.012727075045276521,-0.015974228593088276,-0.10024699701677081,0.07709366043393663,-0.3106827457049558,-0.02736404606009161,-0.02803940514393768,-0.04076870508108915,0.03596876463416743
7 | oir,-0.028231821391445637,-0.004075399522453756,0.02786474446755065,-0.02790900249689699,0.035974571943457635,1.0,0.03641392813559959,-0.009489349974593138,-0.0103304940650022,0.02150014317213812,-0.002090455580844521,-0.00887618121954512,-0.004167233919382794,-0.003330313483323745,-0.004777296895976673,-0.005690294220441272,-0.012413830934520182,-0.009641099134626938,-0.013867319696322364,0.004110417844789159,-0.0070866017394002985,0.022098208898505688,-0.012172936028767551,0.005659089746495046,-0.006517528371290065,0.01903495847674398,-0.01131629762716183,0.004871206101749936,-0.006440267708494729,-0.004425701795913912,-0.004431856875027815,4.778794005972989e-05,0.0008139952524411973,-0.006115703790106687,-0.0110037009776697,-0.010307054707560943,-0.021387832036459276,-0.02666985918834899,-0.0019040195940504326,-0.006420202135596084,-0.0049491226997694275,-0.019872900348486773,0.007771569962883365
8 | aoi,-0.04092879521069447,0.0019995351406224726,0.03337186764364601,-0.033838731197169146,0.9778707639079527,0.03641392813559959,1.0,-0.015452111519395765,-0.010311577801716452,0.02264237164110034,-0.0034207241410678705,-0.010639757204914446,-0.04135889531388325,0.0019278610658433261,6.666946153895985e-05,-0.0023617427190496137,-0.016363128451382367,-0.014359777431331685,-0.009769023546103987,0.01537761796261592,-0.01884972361485903,0.04185140013258146,-0.01026405080992445,0.00900330401677855,-0.020234707090195786,0.02839780685833531,-0.007609580148801688,0.014151109230898634,-0.006481657826853009,-0.013256869542197095,-0.013262300622624854,-0.006474631201690894,-0.004718902367383397,-0.026952728653736453,-0.010125678610840797,-0.006069948548535816,-0.07672208143973462,0.08359749696233801,-0.2990214444394452,-0.025415427079063066,-0.026428545839160263,-0.04095325791346074,0.03488353637379769
9 | slope,0.5432372099461207,-0.0018034783842755797,-0.006026991930247215,0.006255551277796912,-0.01669337804471738,-0.009489349974593138,-0.015452111519395765,1.0,0.9766755216712971,0.3019671357942443,0.13253360247108506,0.04689633873058686,0.2235246581443786,0.35015683389062807,0.344506609128991,0.12022479036201782,0.22105556212388625,0.2199022440236993,0.0812839590029103,0.12831864075488286,0.022915508260917934,-0.20875703752895422,0.03913245615089788,0.09460903950800492,0.01327145536167107,-0.15757758485384937,0.031139294804627828,0.2691405440098944,0.01565345987903653,0.009142012905006706,0.009173870520016869,-8.942032308483025e-05,-0.00041299255164008126,0.025144763862424112,-8.644076148797822e-05,-0.0031480786108221233,0.04057284529970334,-0.09589511083007193,0.06898893507551075,0.013467134615391093,0.009719650807471879,0.4335951829290541,-0.005177202396516902
10 | cos,0.5256817719727389,-0.0028909726488215203,-0.003382078702084296,0.0035616345861296615,-0.01035335152741857,-0.0103304940650022,-0.010311577801716452,0.9766755216712971,1.0,0.28657617934852947,0.12431945373732596,0.04295990676933593,0.19656090093239098,0.3351418290767474,0.32958570707278345,0.11428076139850706,0.21347247319218543,0.2115033015155072,0.07510243455885618,0.11818401127838393,0.01848536539050914,-0.20825318410182936,0.03444695061686324,0.08771329165476037,0.009370385055645514,-0.15641323400667875,0.033998018871790804,0.25623325168025046,0.010840224434811169,0.00786953590375899,0.007899239690598147,-3.5808092042107104e-05,-0.0003199635755873146,0.02352537823817993,0.002109128668451936,0.0032476791912068306,0.029556972592232875,-0.09546516032471163,0.06642260282288691,0.012131318075888074,0.008087414559543402,0.4341009467743041,-0.001908131550121311
11 | macd,0.15709365258539715,-0.003561554244411846,0.0011553227238044806,-0.0010383545962504416,0.020050578380352103,0.02150014317213812,0.02264237164110034,0.3019671357942443,0.28657617934852947,1.0,0.33349325754068126,0.04360087999533756,0.08248734255792559,0.577797505248084,0.5455202834418865,0.20108627782306446,0.04470457521578339,0.043089881365906364,0.03033081901066395,0.02755586090078684,0.0002258313165772644,-0.07040137085781173,0.001928842131675332,0.03127998095835881,-0.0066502218606768635,-0.04162970243138074,-0.0036045265494982193,0.6747664291809042,0.032539584326466156,0.021106325516925283,0.021057716210632082,-0.00868461468880142,-0.012617976836497307,0.003296579595369577,-0.0026757717763379275,-0.0062780901356971255,0.019813330176989545,-0.1011358829231641,0.07286120425010958,-0.0009043689929572019,-0.0018659150683488952,0.1278477283251027,-0.00794392385915063
12 | dif,0.07036867889680094,0.0153497252515745,0.009023794838967054,-0.007971827995708449,-0.004031536274617544,-0.002090455580844521,-0.0034207241410678705,0.13253360247108506,0.12431945373732596,0.33349325754068126,1.0,0.9563965542376675,0.033369987002097,0.8350328813592699,0.8491344882533649,0.3278114259771233,0.7908395225631711,0.7861286476589804,0.33451812542906006,0.018233532876856615,0.01276671926057492,-0.025325238219704525,-0.00823563575448219,0.018222131727650068,0.021152660784757065,-0.0142203939047115,-0.018018254265371448,0.4518074659732228,0.018235355858930116,0.03847263893140304,0.03888229149853291,0.0013715995613127739,-0.0004451952738086288,0.07142295288883949,-0.007448899871702797,0.03351573820118008,0.025266336342045243,-0.11891065263037233,0.12062077130971308,0.0691896384867596,0.0638135948622747,0.06098510292148342,-0.039037837886383875
13 | dea,0.02590204426329577,0.017369729680281443,0.009204724500023265,-0.008126176906679158,-0.010484088456814372,-0.00887618121954512,-0.010639757204914446,0.04689633873058686,0.04295990676933593,0.04360087999533756,0.9563965542376675,1.0,0.00980752612607598,0.7058912547874612,0.7308346481677316,0.28508878560123174,0.824214783627037,0.8197228418130464,0.3450972050651053,0.010785352464526976,0.013459120875470125,-0.0050267025856654286,-0.009324996153806473,0.00961951219660556,0.02447607871460727,-0.0021723945557887573,-0.017977508455464704,0.2697398539614809,0.00924329212300576,0.03423114938397332,0.034680323890234385,0.004144058785043478,0.0034373568447483077,0.07466667731255003,-0.007064738853267061,0.0374621293546739,0.020636810261246172,-0.09467881236441009,0.10525074305258557,0.07360148344448206,0.06820230257973207,0.025018718014714055,-0.03890790283145778
14 | bs_vol,0.36642862863579895,-0.02345186856495664,0.03675925697594281,-0.03673993852253436,-0.021345041358703905,-0.004167233919382794,-0.04135889531388325,0.2235246581443786,0.19656090093239098,0.08248734255792559,0.033369987002097,0.00980752612607598,1.0,0.12265687075492804,0.11920062528263338,0.15812472238824013,0.06943607447839686,0.07286212030780148,0.1024778550108714,0.16775181079875867,0.01636558771771087,-0.056151315129988576,-0.004647393535255153,0.12257505581952625,-0.01547009838862226,-0.046174325985232585,-0.010565694118499669,0.10004121437457769,0.003594716878716654,-0.07088554921621376,-0.07087457626932733,-0.007090165649876515,-0.007012253069885544,0.0023556796258078355,-0.028488352951787147,-0.006200854813225596,-0.2567478013508999,-0.06661803562337204,-0.03189559972988895,-0.0038778337151370392,-0.002159175215825415,0.3956841939637449,0.09173223322578739
15 | trend_0,0.28935679860375996,0.007402878472484531,0.008271409412813133,-0.007495170372572372,0.0014923586263610785,-0.003330313483323745,0.0019278610658433261,0.35015683389062807,0.3351418290767474,0.577797505248084,0.8350328813592699,0.7058912547874612,0.12265687075492804,1.0,0.9072431536793563,0.34172271154073125,0.576389478176685,0.5728176383754342,0.24552128400376083,0.0876179250829926,0.011120598131294554,-0.09108751740958566,0.0048893549093152544,0.0772174669081728,0.016175918625896603,-0.05643901749824275,-0.005672375013977172,0.8082034390970928,0.0449124086725976,0.03966092134532006,0.03993292004215339,-0.004589419005076029,-0.006870863276785661,0.05428484906657767,-0.0053540979560117025,0.020583604092535367,0.026919171706112615,-0.12699627693903612,0.1117177313108476,0.048425585537789584,0.043564699863022235,0.23178589033488695,-0.028454659410071657
16 | log_return_0,0.28462427236158183,0.007574577866273225,0.009923362938337841,-0.009148896319394575,-1.736166064074487e-05,-0.004777296895976673,6.666946153895985e-05,0.344506609128991,0.32958570707278345,0.5455202834418865,0.8491344882533649,0.7308346481677316,0.11920062528263338,0.9072431536793563,1.0,0.3671649165908091,0.5904670175342477,0.5872504628552454,0.25058870248179893,0.08815654748424076,0.011279064875382175,-0.08753103048563439,0.00720847481743129,0.07639181911276105,0.018098871887724435,-0.05506226791567972,-0.0033918492869243486,0.6845626683256297,0.03244993929637206,0.04057772750919149,0.04086594315396657,-0.005100408640173139,-0.00756266178867319,0.054149605252608354,-0.0060603430584108605,0.022910870570040414,0.026807065190963746,-0.12592117467294603,0.11076592498477474,0.04836636253874355,0.043393423729295603,0.2290337708176124,-0.029192276232836302
17 | bsvol_volume_0,0.09070336289428182,-0.0016315989094493385,-0.025503873981625926,0.0257278830582537,-0.003996286919903948,-0.005690294220441272,-0.0023617427190496137,0.12022479036201782,0.11428076139850706,0.20108627782306446,0.3278114259771233,0.28508878560123174,0.15812472238824013,0.34172271154073125,0.3671649165908091,1.0,0.24071138093989328,0.23937188938424436,0.5878589775440052,0.04254103180539655,0.09278423380214099,-0.01286597650920801,0.025664856149499902,0.03182370654866379,0.1034053693248603,-0.009968187336035286,0.028905753544332398,0.24465558300670953,0.020618797460469053,0.048782564053904715,0.04890615251492233,-0.015945979274746263,-0.020588329229078987,0.01746362032655039,-0.02919093880909888,-0.006468517772344754,0.0485201821949584,-0.07235620695740264,0.017915190388773487,0.01589868055771091,0.014890559170437518,0.13777538854382776,-0.07059150166749972
18 | trend_1,0.18327645850335914,0.02401897375457497,0.0018937766020748583,-0.0006456330663136456,-0.016483405681227,-0.012413830934520182,-0.016363128451382367,0.22105556212388625,0.21347247319218543,0.04470457521578339,0.7908395225631711,0.824214783627037,0.06943607447839686,0.576389478176685,0.5904670175342477,0.24071138093989328,1.0,0.9693498710991307,0.40235441697265284,0.052133424171559146,0.022849798104399903,-0.061134540251971424,0.004641044514580806,0.03640227049388742,0.03071667647798733,-0.04844585092925512,-0.007299187772135927,-0.015397236044889328,0.002230442214599289,0.02911316872136266,0.02952101174263018,0.002986905037955258,0.0031320746435373366,0.08787642571372858,-0.006595132460655768,0.042898605797340714,0.036918920155100426,-0.11179702084845447,0.12216033556844769,0.08396656715226664,0.07805657941770541,0.14758374475352554,-0.04579519025316414
19 | log_return_1,0.18169826891415178,0.022655239228405677,0.0034540050556136734,-0.0021681995558814203,-0.01480259409550331,-0.009641099134626938,-0.014359777431331685,0.2199022440236993,0.2115033015155072,0.043089881365906364,0.7861286476589804,0.8197228418130464,0.07286212030780148,0.5728176383754342,0.5872504628552454,0.23937188938424436,0.9693498710991307,1.0,0.4089533787311917,0.05145489209740354,0.02325360413697211,-0.06085599861707065,0.004684497849879221,0.036845256318698616,0.031057627164509858,-0.04725280043941611,-0.007082492075118972,0.002320567782241834,0.003425503505403228,0.028056008589512,0.028461021811054175,0.00320954186940537,0.003359058335616809,0.09027399340924488,-0.005750751006494651,0.04216374724755004,0.036857385711182154,-0.11079092665173343,0.11937317789143154,0.08636860686832198,0.08053588311631943,0.14616333030985673,-0.046870696474985665
20 | bsvol_volume_1,0.058694026936791946,0.0011161409214845252,-0.022576964019916608,0.023035088857052015,-0.00988390389626022,-0.013867319696322364,-0.009769023546103987,0.0812839590029103,0.07510243455885618,0.03033081901066395,0.33451812542906006,0.3450972050651053,0.1024778550108714,0.24552128400376083,0.25058870248179893,0.5878589775440052,0.40235441697265284,0.4089533787311917,1.0,0.026419010418722674,0.10588699701814405,-0.0095192263327721,0.022017725400942354,0.02515015389454617,0.12817648028767664,-0.0018336255928404366,0.019506246589418276,0.010455897522520897,0.009075403791258083,0.008190495191106144,0.008358507903475287,0.01564832148004116,0.016868362028471005,0.03398571338647485,-0.050624654489106316,-0.00450927232594373,0.05727546071332129,-0.062121378401127136,0.014548854299839393,0.03281813632427243,0.031195824156018317,0.0830097662737387,-0.12894132355353266
21 | label_0,0.36939565843947203,-0.008157562027375371,-0.0210841355653561,0.020870635127052856,0.010168212937281255,0.004110417844789159,0.01537761796261592,0.12831864075488286,0.11818401127838393,0.02755586090078684,0.018233532876856615,0.010785352464526976,0.16775181079875867,0.0876179250829926,0.08815654748424076,0.04254103180539655,0.052133424171559146,0.05145489209740354,0.026419010418722674,1.0,0.05646093255167114,0.8102774370308368,-0.1881836744079126,0.6647371071115457,0.04095802118968368,0.5075365125071036,-0.1398684660580214,0.06963744640588676,-0.010507875695530019,-0.008164553330016155,-0.008149429041154274,-0.008684069051029721,-0.008873873591315333,-0.01667095049772346,-0.0005750080209854852,0.012583724068644293,0.004489172872992793,-0.026688541404795072,0.016518767801762202,-0.016685103109304916,-0.014988885490955856,0.2851905407240717,-0.022181015671228946
22 | rv_0,0.016299696091755373,-0.012382822766602079,-0.18293246643979927,0.1828137190974216,-0.026845873331659564,-0.0070866017394002985,-0.01884972361485903,0.022915508260917934,0.01848536539050914,0.0002258313165772644,0.01276671926057492,0.013459120875470125,0.01636558771771087,0.011120598131294554,0.011279064875382175,0.09278423380214099,0.022849798104399903,0.02325360413697211,0.10588699701814405,0.05646093255167114,1.0,0.04862971188016301,0.19052162492595137,0.058456608672606575,0.7787820034241565,0.05215966904713087,0.10344164691173774,-0.002859905595181167,0.015686672074114137,0.056279343946433295,0.05628790138329882,0.008281351364745844,0.00806242644100997,-0.0016568941571958333,-0.04583158522619454,-0.038919195409231305,0.1704089701968651,-0.018635206417055232,-0.009744178297340856,-0.0017392704257459988,-0.0023177709578571246,0.17217298348812116,-0.23650595214169454
23 | price_chg_0,-0.2452747285184763,-0.007283501168432657,-0.014830977523974125,0.014490471970812543,0.03609804472367022,0.022098208898505688,0.04185140013258146,-0.20875703752895422,-0.20825318410182936,-0.07040137085781173,-0.025325238219704525,-0.0050267025856654286,-0.056151315129988576,-0.09108751740958566,-0.08753103048563439,-0.01286597650920801,-0.061134540251971424,-0.06085599861707065,-0.0095192263327721,0.8102774370308368,0.04862971188016301,1.0,-0.23101882096057486,0.5174989932332564,0.03567352882084029,0.6446772681216031,-0.17520804685645272,-0.06739605811598848,-0.02513349882120021,-0.01234543599667244,-0.012341735136786268,-0.004217006306927853,-0.003974399005642217,-0.03345508684644617,-0.0015555244086645082,0.012003262032685194,-0.010235454039429792,0.00875096511539969,-0.014571007165970633,-0.024766083113741188,-0.020827420339148552,-0.19435742139021403,-0.020988224744090827
24 | label_clf_0,0.05501590071220928,-0.010436211117863211,-0.06867130750973274,0.06890639960328447,-0.012923469828317862,-0.012172936028767551,-0.01026405080992445,0.03913245615089788,0.03444695061686324,0.001928842131675332,-0.00823563575448219,-0.009324996153806473,-0.004647393535255153,0.0048893549093152544,0.00720847481743129,0.025664856149499902,0.004641044514580806,0.004684497849879221,0.022017725400942354,-0.1881836744079126,0.19052162492595137,-0.23101882096057486,1.0,-0.12274759266114206,0.20015549143891284,-0.15141830995166553,0.34271438780689123,0.0026379271974671446,0.018014306205290622,0.04320584570445539,0.04320252628271997,-0.0056613091821502695,-0.005059186778788717,0.019882684459645243,-0.02145922764922648,-0.02368872517107596,0.10580335754605173,0.0017041162602890777,-0.0041909752432901844,0.017787152606758855,0.016752114392454494,0.06661730596897232,-0.08827715153442882
25 | label_1,0.2789478255086415,-0.006262491961302007,-0.017372080936605125,0.01699192634228204,0.006231535622128172,0.005659089746495046,0.00900330401677855,0.09460903950800492,0.08771329165476037,0.03127998095835881,0.018222131727650068,0.00961951219660556,0.12257505581952625,0.0772174669081728,0.07639181911276105,0.03182370654866379,0.03640227049388742,0.036845256318698616,0.02515015389454617,0.6647371071115457,0.058456608672606575,0.5174989932332564,-0.12274759266114206,1.0,0.06673668034161448,0.8931480076285518,-0.25663208977048213,0.06824839829356014,-0.006131971093202885,-0.011257839891772242,-0.01124836461539646,-0.0042133994212118515,-0.0006862049293191473,-0.02741749606915113,-0.001128961242683365,0.0018492339619891598,0.010592857555095498,-0.027434370111809575,0.010441737087579426,-0.02742916942894154,-0.026174052801068676,0.2169026799501304,-0.033097301293992215
26 | rv_1,0.011232752126037282,-0.016064995876277362,-0.17059873416850516,0.17044513627417834,-0.026495754452218176,-0.006517528371290065,-0.020234707090195786,0.01327145536167107,0.009370385055645514,-0.0066502218606768635,0.021152660784757065,0.02447607871460727,-0.01547009838862226,0.016175918625896603,0.018098871887724435,0.1034053693248603,0.03071667647798733,0.031057627164509858,0.12817648028767664,0.04095802118968368,0.7787820034241565,0.03567352882084029,0.20015549143891284,0.06673668034161448,1.0,0.06307065015257253,0.14694430071875036,-0.0023436271685679825,0.003177195993483266,0.06361630972864837,0.06363102121774865,0.009792018240210327,0.007675974996375772,-0.002293873842707399,-0.06251206647829781,-0.05009725158450896,0.19642558228066176,-0.030673839396512203,-0.01570134599105522,-0.0023902834050890174,-0.0029973116451321702,0.11516980057806929,-0.3038610994307427
27 | price_chg_1,-0.18275654382672596,-0.005490545466873095,-0.012462046829067219,0.011986134395992568,0.025323440743364754,0.01903495847674398,0.02839780685833531,-0.15757758485384937,-0.15641323400667875,-0.04162970243138074,-0.0142203939047115,-0.0021723945557887573,-0.046174325985232585,-0.05643901749824275,-0.05506226791567972,-0.009968187336035286,-0.04844585092925512,-0.04725280043941611,-0.0018336255928404366,0.5075365125071036,0.05215966904713087,0.6446772681216031,-0.15141830995166553,0.8931480076285518,0.06307065015257253,1.0,-0.2845207302306171,-0.03414494722349296,-0.016806002725056366,-0.01435022085888908,-0.014349444940272758,-0.0007085251727418098,0.0032422922263392003,-0.03994812201905904,-0.0018010387290632222,0.0011135777265215863,-0.0002405894099277259,-0.0008989105146791506,-0.012906736358955982,-0.03349554864941822,-0.03060030065954934,-0.14322992175916546,-0.032274217249379185
28 | label_clf_1,0.04637147346560956,-0.009093195973181612,-0.04045205138270358,0.04088763300403798,-0.008470170386257529,-0.01131629762716183,-0.007609580148801688,0.031139294804627828,0.033998018871790804,-0.0036045265494982193,-0.018018254265371448,-0.017977508455464704,-0.010565694118499669,-0.005672375013977172,-0.0033918492869243486,0.028905753544332398,-0.007299187772135927,-0.007082492075118972,0.019506246589418276,-0.1398684660580214,0.10344164691173774,-0.17520804685645272,0.34271438780689123,-0.25663208977048213,0.14694430071875036,-0.2845207302306171,1.0,-0.0016804062515182179,-0.002394807357909251,0.026917038230414355,0.026910739339090866,0.004140697410819052,-0.0010915054561001422,0.035690384254963736,-0.019161453969645373,-0.01437336341277636,0.07203721081755816,-0.0061892794991880475,-0.011099557984717888,0.03385356039399726,0.032929275370472466,0.0620132157255632,-0.07549681467533133
29 | trend_ls_diff,0.2219724586039065,-0.008251418824299068,0.008755990348824322,-0.00870568159244484,0.01370491460513496,0.004871206101749936,0.014151109230898634,0.2691405440098944,0.25623325168025046,0.6747664291809042,0.4518074659732228,0.2697398539614809,0.10004121437457769,0.8082034390970928,0.6845626683256297,0.24465558300670953,-0.015397236044889328,0.002320567782241834,0.010455897522520897,0.06963744640588676,-0.002859905595181167,-0.06739605811598848,0.0026379271974671446,0.06824839829356014,-0.0023436271685679825,-0.03414494722349296,-0.0016804062515182179,1.0,0.05334666798872013,0.02754779927043832,0.027586697072235343,-0.007768069942760115,-0.010664223590577964,0.0030932036165731433,-0.001798350653929668,-0.005729457748309333,0.00633193951753354,-0.07482310298601748,0.0486601359672552,-0.0012584217846168604,-0.0029470410466851324,0.17725190509184077,-0.0018139851797676263
30 | trend_ls_ratio,0.02248117677720777,-0.009572022204702133,-0.012713313015204014,0.012645761420629969,-0.007314846746996655,-0.006440267708494729,-0.006481657826853009,0.01565345987903653,0.010840224434811169,0.032539584326466156,0.018235355858930116,0.00924329212300576,0.003594716878716654,0.0449124086725976,0.03244993929637206,0.020618797460469053,0.002230442214599289,0.003425503505403228,0.009075403791258083,-0.010507875695530019,0.015686672074114137,-0.02513349882120021,0.018014306205290622,-0.006131971093202885,0.003177195993483266,-0.016806002725056366,-0.002394807357909251,0.05334666798872013,1.0,0.19194399240348828,0.19194344852915612,0.002897529465602425,-0.0029711791178919666,-0.0036078785153296094,-0.011465222729724481,-0.011658683316863084,0.046113961929263945,0.060614372345330465,0.032796093897679325,-0.004463716144893776,-0.004256243507965783,0.03437174393065234,0.11653108621581809
31 | volume_ls_ratio,0.006144528876721859,0.006351558096494069,-0.07153820161594453,0.07167242859740797,-0.016238447726833836,-0.004425701795913912,-0.013256869542197095,0.009142012905006706,0.00786953590375899,0.021106325516925283,0.03847263893140304,0.03423114938397332,-0.07088554921621376,0.03966092134532006,0.04057772750919149,0.048782564053904715,0.02911316872136266,0.028056008589512,0.008190495191106144,-0.008164553330016155,0.056279343946433295,-0.01234543599667244,0.04320584570445539,-0.011257839891772242,0.06361630972864837,-0.01435022085888908,0.026917038230414355,0.02754779927043832,0.19194399240348828,1.0,0.9999998826525042,0.019764649778022,-0.003385323971168232,0.013462463313919653,0.01304878736881774,-0.037141645539322574,0.21131072652141056,0.02684725455948848,0.06253056679469954,0.013493617845747735,0.013020952652011133,0.012137718458712183,0.25729561907984766
32 | turnover_ls_ratio,0.006163724060260921,0.0063610224548742095,-0.07153775107589254,0.07167245044534787,-0.016244292667572123,-0.004431856875027815,-0.013262300622624854,0.009173870520016869,0.007899239690598147,0.021057716210632082,0.03888229149853291,0.034680323890234385,-0.07087457626932733,0.03993292004215339,0.04086594315396657,0.04890615251492233,0.02952101174263018,0.028461021811054175,0.008358507903475287,-0.008149429041154274,0.05628790138329882,-0.012341735136786268,0.04320252628271997,-0.01124836461539646,0.06363102121774865,-0.014349444940272758,0.026910739339090866,0.027586697072235343,0.19194344852915612,0.9999998826525042,1.0,0.019764818794206735,-0.0033844880597027776,0.013495611056904031,0.0130447842173238,-0.03712285344424824,0.2113205903654169,0.02680792553413025,0.06257984024819177,0.013526481692114595,0.013051329574837513,0.012155158410349072,0.2572715047882559
33 | bs_vol_ls_ratio,-0.0077051058846247045,0.002429401854186942,0.0005745450245647402,-0.0004771819932714005,-0.005796848587927473,4.778794005972989e-05,-0.006474631201690894,-8.942032308483025e-05,-3.5808092042107104e-05,-0.00868461468880142,0.0013715995613127739,0.004144058785043478,-0.007090165649876515,-0.004589419005076029,-0.005100408640173139,-0.015945979274746263,0.002986905037955258,0.00320954186940537,0.01564832148004116,-0.008684069051029721,0.008281351364745844,-0.004217006306927853,-0.0056613091821502695,-0.0042133994212118515,0.009792018240210327,-0.0007085251727418098,0.004140697410819052,-0.007768069942760115,0.002897529465602425,0.019764649778022,0.019764818794206735,1.0,0.910564401721571,0.007335097897226584,-0.011431469213875,0.004849156463890722,0.004466113094543014,-0.002277453039196899,0.0008035528161606313,0.007348170442475548,0.007371431923765388,-0.0076822421929908,-0.008425543287590728
34 | bsvol_volume_ls_ratio,-0.008400306094597675,0.0012108893752758108,0.001884262418507718,-0.0017958067638953763,-0.00414045202908549,0.0008139952524411973,-0.004718902367383397,-0.00041299255164008126,-0.0003199635755873146,-0.012617976836497307,-0.0004451952738086288,0.0034373568447483077,-0.007012253069885544,-0.006870863276785661,-0.00756266178867319,-0.020588329229078987,0.0031320746435373366,0.003359058335616809,0.016868362028471005,-0.008873873591315333,0.00806242644100997,-0.003974399005642217,-0.005059186778788717,-0.0006862049293191473,0.007675974996375772,0.0032422922263392003,-0.0010915054561001422,-0.010664223590577964,-0.0029711791178919666,-0.003385323971168232,-0.0033844880597027776,0.910564401721571,1.0,0.006342871393432024,-0.010018025930311958,0.00486505686500601,0.0024060199550001308,-0.0008857445398116769,-0.0005344922113364504,0.0063689999529933285,0.006509275347651979,-0.007377982428478173,-0.015573187854846567
35 | norm_lastprice,0.025550012849563108,0.041089524033103036,0.013426474043960327,0.0009898114853633783,-0.02865597314141963,-0.006115703790106687,-0.026952728653736453,0.025144763862424112,0.02352537823817993,0.003296579595369577,0.07142295288883949,0.07466667731255003,0.0023556796258078355,0.05428484906657767,0.054149605252608354,0.01746362032655039,0.08787642571372858,0.09027399340924488,0.03398571338647485,-0.01667095049772346,-0.0016568941571958333,-0.03345508684644617,0.019882684459645243,-0.02741749606915113,-0.002293873842707399,-0.03994812201905904,0.035690384254963736,0.0030932036165731433,-0.0036078785153296094,0.013462463313919653,0.013495611056904031,0.007335097897226584,0.006342871393432024,1.0,0.46907003849498463,0.05516895412105506,0.01772320389430136,-0.019192397605104182,0.0707390807255493,0.9996643169889884,0.9994751729390902,0.023567125220449542,-0.01679734578115597
36 | norm_openinterest,0.0016377857662318784,0.02709960272594908,0.017222421644095497,-0.010478300047777384,-0.012727075045276521,-0.0110037009776697,-0.010125678610840797,-8.644076148797822e-05,0.002109128668451936,-0.0026757717763379275,-0.007448899871702797,-0.007064738853267061,-0.028488352951787147,-0.0053540979560117025,-0.0060603430584108605,-0.02919093880909888,-0.006595132460655768,-0.005750751006494651,-0.050624654489106316,-0.0005750080209854852,-0.04583158522619454,-0.0015555244086645082,-0.02145922764922648,-0.001128961242683365,-0.06251206647829781,-0.0018010387290632222,-0.019161453969645373,-0.001798350653929668,-0.011465222729724481,0.01304878736881774,0.0130447842173238,-0.011431469213875,-0.010018025930311958,0.46907003849498463,1.0,0.04996527255215003,0.026461339653671093,0.0794310829832814,0.02743003515127373,0.46919973031408657,0.4701532328509145,-0.001541806601678097,-0.0347744351714621
37 | norm_interestdiff,0.0017756125544669292,0.23309250267300968,0.031080289670741546,-0.03016456149377878,-0.015974228593088276,-0.010307054707560943,-0.006069948548535816,-0.0031480786108221233,0.0032476791912068306,-0.0062780901356971255,0.03351573820118008,0.0374621293546739,-0.006200854813225596,0.020583604092535367,0.022910870570040414,-0.006468517772344754,0.042898605797340714,0.04216374724755004,-0.00450927232594373,0.012583724068644293,-0.038919195409231305,0.012003262032685194,-0.02368872517107596,0.0018492339619891598,-0.05009725158450896,0.0011135777265215863,-0.01437336341277636,-0.005729457748309333,-0.011658683316863084,-0.037141645539322574,-0.03712285344424824,0.004849156463890722,0.00486505686500601,0.05516895412105506,0.04996527255215003,1.0,-0.17043742585694788,-0.007502388047832082,-0.01474561599659727,0.0552690437042062,0.05528161802613098,0.004362877678196357,0.025483461049101774
38 | norm_volume,0.023739479157777535,0.01699043437126748,-0.17103032842120514,0.17116359808955356,-0.10024699701677081,-0.021387832036459276,-0.07672208143973462,0.04057284529970334,0.029556972592232875,0.019813330176989545,0.025266336342045243,0.020636810261246172,-0.2567478013508999,0.026919171706112615,0.026807065190963746,0.0485201821949584,0.036918920155100426,0.036857385711182154,0.05727546071332129,0.004489172872992793,0.1704089701968651,-0.010235454039429792,0.10580335754605173,0.010592857555095498,0.19642558228066176,-0.0002405894099277259,0.07203721081755816,0.00633193951753354,0.046113961929263945,0.21131072652141056,0.2113205903654169,0.004466113094543014,0.0024060199550001308,0.01772320389430136,0.026461339653671093,-0.17043742585694788,1.0,0.07431065178449221,0.1462915656574945,0.017827305853692663,0.016544076225834974,0.04741515623980669,-0.2443153320149407
39 | norm_bidvolume1,-0.05793142753675455,0.0017671644254506874,-0.048721028082614144,0.04839132326040783,0.07709366043393663,-0.02666985918834899,0.08359749696233801,-0.09589511083007193,-0.09546516032471163,-0.1011358829231641,-0.11891065263037233,-0.09467881236441009,-0.06661803562337204,-0.12699627693903612,-0.12592117467294603,-0.07235620695740264,-0.11179702084845447,-0.11079092665173343,-0.062121378401127136,-0.026688541404795072,-0.018635206417055232,0.00875096511539969,0.0017041162602890777,-0.027434370111809575,-0.030673839396512203,-0.0008989105146791506,-0.0061892794991880475,-0.07482310298601748,0.060614372345330465,0.02684725455948848,0.02680792553413025,-0.002277453039196899,-0.0008857445398116769,-0.019192397605104182,0.0794310829832814,-0.007502388047832082,0.07431065178449221,1.0,0.028530113616259938,-0.01820403088297347,-0.013877248648944998,-0.058648302792326495,-0.059375401658092054
40 | norm_askvolume1,0.05041786715463712,0.00482710498620479,-0.014040665235812664,0.015071571174353565,-0.3106827457049558,-0.0019040195940504326,-0.2990214444394452,0.06898893507551075,0.06642260282288691,0.07286120425010958,0.12062077130971308,0.10525074305258557,-0.03189559972988895,0.1117177313108476,0.11076592498477474,0.017915190388773487,0.12216033556844769,0.11937317789143154,0.014548854299839393,0.016518767801762202,-0.009744178297340856,-0.014571007165970633,-0.0041909752432901844,0.010441737087579426,-0.01570134599105522,-0.012906736358955982,-0.011099557984717888,0.0486601359672552,0.032796093897679325,0.06253056679469954,0.06257984024819177,0.0008035528161606313,-0.0005344922113364504,0.0707390807255493,0.02743003515127373,-0.01474561599659727,0.1462915656574945,0.028530113616259938,1.0,0.07008324340591686,0.06648497745295315,0.03685180715174309,-0.06534545744761575
41 | norm_vwap,0.01174928073368846,0.04112423683053382,0.013130830466571871,0.0012876978139854374,-0.02736404606009161,-0.006420202135596084,-0.025415427079063066,0.013467134615391093,0.012131318075888074,-0.0009043689929572019,0.0691896384867596,0.07360148344448206,-0.0038778337151370392,0.048425585537789584,0.04836636253874355,0.01589868055771091,0.08396656715226664,0.08636860686832198,0.03281813632427243,-0.016685103109304916,-0.0017392704257459988,-0.024766083113741188,0.017787152606758855,-0.02742916942894154,-0.0023902834050890174,-0.03349554864941822,0.03385356039399726,-0.0012584217846168604,-0.004463716144893776,0.013493617845747735,0.013526481692114595,0.007348170442475548,0.0063689999529933285,0.9996643169889884,0.46919973031408657,0.0552690437042062,0.017827305853692663,-0.01820403088297347,0.07008324340591686,1.0,0.9996851492425094,0.012809232864344171,-0.016519512372277653
42 | norm_wap,0.00831067569562216,0.04064287131517503,0.01339544510464434,0.0010255800932542295,-0.02803940514393768,-0.0049491226997694275,-0.026428545839160263,0.009719650807471879,0.008087414559543402,-0.0018659150683488952,0.0638135948622747,0.06820230257973207,-0.002159175215825415,0.043564699863022235,0.043393423729295603,0.014890559170437518,0.07805657941770541,0.08053588311631943,0.031195824156018317,-0.014988885490955856,-0.0023177709578571246,-0.020827420339148552,0.016752114392454494,-0.026174052801068676,-0.0029973116451321702,-0.03060030065954934,0.032929275370472466,-0.0029470410466851324,-0.004256243507965783,0.013020952652011133,0.013051329574837513,0.007371431923765388,0.006509275347651979,0.9994751729390902,0.4701532328509145,0.05528161802613098,0.016544076225834974,-0.013877248648944998,0.06648497745295315,0.9996851492425094,1.0,0.009432817879780515,-0.01584170826115794
43 | norm_bs_tag,0.7799302620131483,-0.013868880619242673,-0.039652281053981615,0.039826838470452056,-0.04076870508108915,-0.019872900348486773,-0.04095325791346074,0.4335951829290541,0.4341009467743041,0.1278477283251027,0.06098510292148342,0.025018718014714055,0.3956841939637449,0.23178589033488695,0.2290337708176124,0.13777538854382776,0.14758374475352554,0.14616333030985673,0.0830097662737387,0.2851905407240717,0.17217298348812116,-0.19435742139021403,0.06661730596897232,0.2169026799501304,0.11516980057806929,-0.14322992175916546,0.0620132157255632,0.17725190509184077,0.03437174393065234,0.012137718458712183,0.012155158410349072,-0.0076822421929908,-0.007377982428478173,0.023567125220449542,-0.001541806601678097,0.004362877678196357,0.04741515623980669,-0.058648302792326495,0.03685180715174309,0.012809232864344171,0.009432817879780515,1.0,-0.02135184570787218
44 | norm_volume_ls_diff,-0.0033881076144420025,0.013794460670124622,0.04553697472655794,-0.04563182007460571,0.03596876463416743,0.007771569962883365,0.03488353637379769,-0.005177202396516902,-0.001908131550121311,-0.00794392385915063,-0.039037837886383875,-0.03890790283145778,0.09173223322578739,-0.028454659410071657,-0.029192276232836302,-0.07059150166749972,-0.04579519025316414,-0.046870696474985665,-0.12894132355353266,-0.022181015671228946,-0.23650595214169454,-0.020988224744090827,-0.08827715153442882,-0.033097301293992215,-0.3038610994307427,-0.032274217249379185,-0.07549681467533133,-0.0018139851797676263,0.11653108621581809,0.25729561907984766,0.2572715047882559,-0.008425543287590728,-0.015573187854846567,-0.01679734578115597,-0.0347744351714621,0.025483461049101774,-0.2443153320149407,-0.059375401658092054,-0.06534545744761575,-0.016519512372277653,-0.01584170826115794,-0.02135184570787218,1.0
45 |
--------------------------------------------------------------------------------