用python做机器学习去解决交易问题的话,第一个模型就是简单的线性回归,难度上并不大,应用上看看效果。这次还是用聚宽网来做,这样就省得下数据来,缺点也明显,就是计算力真的有限,每次回测都得等1分钟。
假设一:螺纹钢走势和短期历史价格呈现线性相关
用螺纹钢1分钟走势进行回测,在知道过去5/10/30/60分钟前的价格,以及前1分钟价格的2/3/4次的前提之下,使用lasso做回归发现,方差和标准差是(9.8852876247738557, 3.1440877253622959),还是比较大,因为平均数3多一些,说明效果很一般。
代码如下
from jqdata import *
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
# import numpy as np
# import matplotlib.pyplot as plt
from sklearn.linear_model import Lasso,Ridge
from sklearn.model_selection import GridSearchCV
## 初始化函数,设定基准等等
def initialize(context):
set_option('use_real_price', True)
# 期货类每笔交易时的手续费是:买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023,close_today_commission=0.0023), type='index_futures')
# 设定保证金比例
set_option('futures_margin_rate', 0.15)
run_daily( find, 'every_bar')
def find(context):
close=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['close'])
high=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['high'])
low=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['low'])
volume=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['volume'])
preclose=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['pre_close'])
# print(all)
a=pd.concat([preclose,close,high,low,volume],axis=1)
a['volatility']=a['close']-a['pre_close']
‘’‘加入特征,5/10/30/60分钟前的价格,以及前1分钟价格的2/3/4次方作为因子’‘’
a['pre5']=a['close'].shift(5)
a['pre10']=a['close'].shift(10)
a['pre30']=a['close'].shift(30)
a['pre60']=a['close'].shift(60)
a['pre_close*2']=a['pre_close']**2
a['pre_close*3']=a['pre_close']**3
a['pre_close*4']=a['pre_close']**4
‘’‘
计算螺纹钢正常波动率和方差
# av=np.average(np.abs(a['volatility']))
# u=(a['volatility']-av)**2
# avu=np.average(u)
# sqavu=np.sqrt(avu)
’‘’
x=a.iloc[60:,[0,4,6,7,8,9,10,11,12]]
y=a['close'][60:]
xtrain,xtest,ytrain,ytest=train_test_split(x,y,random_state=1)
# lr=LinearRegression()
# lr.fit(xtrain,ytrain)
# yhat=lr.predict(xtest)
‘’‘使用lasso用网格搜索法,10折交叉验证,得到均方误差和标准差’‘’
model1=Lasso()
al=np.linspace(-2,3,100)
modcv=GridSearchCV(model1,param_grid={'alpha':al},cv=10)
modcv.fit(xtrain,ytrain)
yhat=modcv.predict(xtest)
mse=np.average((yhat-ytest)**2)
mseq=np.sqrt(mse)
print(mse,mseq)
假设2,螺纹价格和铁矿石/焦炭/焦煤线性相关
把螺纹钢价格作为标签,其他三个作为因子,建立线性回归,这个模型有两种可能,第一种就是只看波动率的绝对值,不看方向,第二种是二者都看。
建立线性回归模型,用的是lasso,其实用正常的线性回归也一样,反正也没有加高次项,代码如下
# 导入函数库
from jqdata import *
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
# import numpy as np
# import matplotlib.pyplot as plt
from sklearn.linear_model import Lasso,Ridge
from sklearn.model_selection import GridSearchCV
## 初始化函数,设定基准等等
def initialize(context):
set_option('use_real_price', True)
# 期货类每笔交易时的手续费是:买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023,close_today_commission=0.0023), type='index_futures')
# 设定保证金比例
set_option('futures_margin_rate', 0.15)
run_daily( find, 'every_bar')
def find(context):
# pd.set_option('precision',2)
close=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['close'])
# high=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['high'])
# low=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['low'])
# volume=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['volume'])
preclose=get_price('RB9999.XSGE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['pre_close'])
jmclose=get_price('JM9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['close'])
tksclose=get_price('I9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['close'])
jtclose=get_price('J9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['close'])
jmpre=get_price('JM9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['pre_close'])
tkpre=get_price('I9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['pre_close'])
jtpre=get_price('J9999.XDCE', start_date='2018-08-01', end_date='2018-10-31', frequency='1m', fields=['pre_close'])
# 有价格波动的方向
lwvo1=close['close']-preclose['pre_close']
jmvo1=jmclose['close']-jmpre['pre_close']
tkvo1=tksclose['close']-tkpre['pre_close']
jtvo1=jtclose['close']-jtpre['pre_close']
# 不考虑方向只考虑波动幅度
lwvo=np.abs(lwvo1)
jmvo=np.abs(jmvo1)
tkvo=np.abs(tkvo1)
jtvo=np.abs(jtvo1)
# # 要是归一化处理呢
# lwvo=lwvo2/lwvo1
# jmvo=jmvo2/jmvo1
# tkvo=tkvo2/tkvo1
# jtvo=jtvo2/jtvo1
a=pd.concat([lwvo1,tkvo1,jtvo1,jmvo1],axis=1)
a.columns=['lw','tk','jt','jm']
a.dropna(inplace=True)
# a['volatility']=a['close']-a['pre_close']
# a['jmvo']=a['jmclose']-a['jmpre']
# a['tkvo']=a['tksclose']-a['tkpre']
# a['jtvo']=a['jtclose']-a['jtpre']
# a['pre5']=a['close'].shift(5)
# a['pre10']=a['close'].shift(10)
# a['pre30']=a['close'].shift(30)
# a['pre60']=a['close'].shift(60)
# a['pre_close*2']=a['pre_close']**2
# a['pre_close*3']=a['pre_close']**3
# a['pre_close*4']=a['pre_close']**4
'''
# av=np.average(np.abs(a['volatility']))
# u=(a['volatility']-av)**2
# avu=np.average(u)
# sqavu=np.sqrt(avu)
'''
x=a.iloc[:,[1,2,3]]
y=a['lw']
xtrain,xtest,ytrain,ytest=train_test_split(x,y,random_state=1)
lrg=LinearRegression()
lrg.fit(xtrain,ytrain)
yhat=lr.predict(xtest)
# model1=Ridge()
# al=np.linspace(-2,3,100)
# modcv=GridSearchCV(model1,param_grid={'alpha':al},cv=10)
# modcv.fit(xtrain,ytrain)
# yhat=modcv.predict(xtest)
# 随后计算r2值,算法有3种,一种直接调公式,注意传入的是xtest,ytest使用测试集来评价模型好坏
r2=r2_score(ytest,yhat)
print('方法1的r2是%s'%r2)
# # 方法二就是直接算r2
# ymean=np.mean(ytest)
# tss=np.mean((ytest-ymean)**2)
# rss=np.mean((ytest-yhat)**2)
# r2_2=1-rss/tss
# print('方法2的r2是%s'%r2_2)
# # 方法3是用lr.score(),传入xtest,ytest
# r2_3=lr.score(xtest,ytest)
# print('方法3的r2是%s'%r2_3)
corrcoef=lr.coef_
print('回归方程系数%s'%corrcoef)
# # print(modcv.best_params_)
# # print(x)
# # print(a)
# # print(av)
# # print(u)
# # print(avu)
# # print(sqavu)
'''
# plt.plot(u)
# plt.plot()
# plt.show()
'''
第一种只考虑波动率,不看方向,结论是r2是0.19。
第二种二者都要的话,结论是r2是0.39,预测值和实际值的相关系数为0.625
结论很明显,黑色系品种之间同涨同跌的情况还是比较多,这才使得方向成为提高准确率的一个因素,但是就算有所提高,R2整体还是太低,基本没法用。
如果正常用线性回归,得出的结论基本一致。但是铁矿/焦炭/焦煤的线性回归系数和截距分别是:
array([ 1.10955314, 0.62495403, 0.18057547]), -0.017346562494255414),明显可以看出来还是铁矿相关性更强,这个结论不用算也知道,焦煤相关性很低,不用线性回归也知道。
注意事项:
1.因为螺纹钢和铁矿石/焦煤/焦炭的交易时间不一样,主要是夜盘,所以数据在合并之后,会螺纹钢会出现大量空值——巨坑爹,这个小问题看起来不大,但是很容易忽略,因为在抓数据的时候,是没有空值的,而且很少会想到收盘时间不同造成的空值。而且聚宽网也很搞笑,报错的时候只报错了一个“keyerror 0”,完全不明白是在说什么。
2.计算r2的时候,一共有3种方法都可以,方法2就是公式,1,3两种方法就是调用简单函数,但是不同的地方是参数已经不是每种模型都有score函数,所以尽量使用r2_score;
还有就是rss在计算的时候,用的预测的yhat和ytest,而且要计算ytest的tss和rss,而不是用全样本去计算,这个地方很容出错。
问题:
1.这个结论是建立在1分钟图上,那么能否推广呢?
2.既然铁矿石相关性这么强,很明显可以套利了,但是方向性存在问题,应该怎么解决?还是根本不用解决靠止损硬套?
3.套利的话,很明显靠人工有点慢,这回必须靠软件了,不知道聚宽行不行,不行的话,还得自己建立模型,基础函数搭建实在太浪费时间
4.1分钟用套利的话,成本必然高的吓人,不知道夏普比率有多少
4.这个模型用的是1分钟图,如果换一下时间周期会不会更好?