量化投资学习笔记103——股价预测1:总体规划及数据获取

又想了一个问题:预测股价。这回跟量化有直接关系了吧?
先看两篇文章
首先是一篇综述性文章。
徐程成.股票价格预测方法综述.中国市场,2020, (9):42-43,68.
股票预测的方法有:
1.传统方法:
①基于统计学和概率论的VAR(向量自回归模型)、ARM(自回归滑动平均模型)、指数平滑模型。
②基于非统计原理的GM、SVM以及ANN创新型预测模型。
③灰色预测法。
④人工神经网络。
2.集合经验模态分解方法(EEMD)
3.机器学习方法:多种机器学习方法与金融模型融合。
4.时间序列方法:ARMA,ARIMA,GARCH等,以及与小波分析等方法结合。
5.神经网络:BP神经网络,小波神经网络,遗传算法等。
再来看具体的预测方法。
一篇用简单的多元线性回归模型进行预测的:
王培冬.基于多元线性回归的股价分析及预测.科技经济市场,2020(1):84-85.
此文用多元线性回归模型对沪深300指数进行了预测,以开盘价,收盘价,最高价,最低价,成交量,成交额,次日开盘价为自变量,以第二天收盘价为预测目标,用多元线性回归模型进行预测。我先用python尝试实现一下吧。
首先要搞到数据。开始用tushare,这个我从一开始就用了,发现提示要升级到pro版了,要使用更多功能要去挣积分,关键是积分有效期一年……我尝试了一下,好麻烦。收费我绝对不反对,但不应该这么折腾初级用户。想到了我以前用的一个笔记软件——为知笔记。它开始收费我就换到印象笔记了,尽管免费版基本够用了,我还是买了高级账户。扯远了,回来。找到个替代品:akshare。官网
选取沪深300指数2018年一整年的数据进行预测分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import tushare as ts
import akshare as ak
import pandas as pd
import os
import run


# 下载历史数据,用akshare
@run.change_dir
def downloadData(code="sh000300"):
result = ak.stock_zh_index_daily_em(symbol=code)
result.index = result.date
result = result.loc[:, ["open", "close", "high", "low", "volume", "amount"]]
# print(result)
result.to_csv("./result.csv")


# 从文件读取数据
@run.change_dir
def loadData(code="sh000300", start="2018-01-01", end="2018-12-31", refresh = False):
datafile = "./result.csv"
if os.path.exists(datafile) == False or refresh == True:
downloadData(code)
data = pd.read_csv("./result.csv", index_col="date")
data = data[start : end]
# print(data.describe())
return data

接着对数据进行处理,生成特征和目标值。次日收盘价为目标值。
就用多元线性回归模型对次交易日指数进行预测。

1
2
3
4
5
6
7
# 数据预处理
def preProcess(data):
data["nextclose"] = data["close"].shift(-1)
data["nextopen"] = data["open"].shift(-1)
result = data.iloc[:-1, :]
print(len(result))
return result

OK,可以开始干活了。
加载完数据,画箱状图看看。

成交量数据远远大于其它数据,做标准化吧。
做完再画图看看。(标化以后又乘500再加了3500)

画配对图看看

OK,开始正式干活吧。

1
2
3
4
5
6
7
# 训练
model = LinearRegression()
model.fit(X_train, Y_train)
a = model.intercept_
b = model.coef_
print("截距:", a)
print("回归系数:", b)

结果
截距: [2.36916081] 回归系数: [[-0.15301665 0.17758906 0.25901863 -0.39743459 -0.02732929 1.09816001 0.04366043]]
模型评分: 0.9880317755151593
论文给出的R²评分为0.989或0.988,我的结果与其很接近的。
下面输出残差对预测值的散点图。

随机分布,无任何规律性,说明数据满足模型基本假设。
下面应用模型,载入2019年的数据,用模型进行预测看看。
模型验证评分: 0.976653552728345

验证结果,大的趋势还是蛮像的,但是短期会有很大偏离。而我们使用今天的数据(以及明天开盘的数据)预测明天收盘,所以这种预测并没有啥卵用……
再看看预测误差

画直方图看看。

基本还是正态分布的。平均误差率-0.15%。
现在,来重构一下整个程序,把重复的部分提取出来。
最后,用一个策略来实际检验一下模型吧:从第二个交易日开始,用模型对当天的收盘价进行预测,并与头一天对比,如果上涨则以开盘价全仓(前一天资金的90%,因为还要cover手续费)买入,如果下跌则以开盘价清仓。当然这个策略不太真实,我不可能以开盘价买入,而且买入那么多。
这是用2019年整年的数据回测的结果。

可以说相当好了,一年涨了3倍。下面计算各种回测指标。用empyrical库计算,建了一个回测类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import empyrical as ey
import math




# 策略回测类
class BackTest:
def __init__(self, model, code="sh000300", start="2019-01-01", end="2019-12-31"):
self.data = loadData(code=code, start=start, end=end)
self.X, self.Y = splitData(self.data)
self.model = model # 模型
self.stock = [0] # 持仓
self.cash = [100000000] # 现金
self.value = [] # 资产总额
self.cost = [0.0] # 交易成本
self.fee_rate = 1e-4 # 手续费率
self.modelname = str(model)[:-2] # 模型名称
self.bk_results = pd.DataFrame()

# 进行回测
def run(self):
for i in range(len(self.data)):
today_X = self.X.iloc[i, :]
pred_Y = self.model.predict(today_X.values.reshape(1, -1))
if i == 0:
# print("第0天")
amount = 0
elif pred_Y[0][0] > today_X.open: # 全仓买入
# print("买")
money = self.cash[i - 1]
price = today_X.open
amount = math.floor(0.9*money/price)
# 买入操作
self.stock.append(self.stock[i-1] + amount)
self.cash.append(money - price*amount*(1.0 + self.fee_rate))
self.cost.append(self.cost[i-1] + price*amount*self.fee_rate)
elif pred_Y[0][0] <= today_X.open: # 清仓
# print("卖")
amount = self.stock[i-1]
price = today_X.open
self.stock.append(0)
money = amount*price
self.cash.append(money*(1.0 - self.fee_rate) + self.cash[i-1])
self.cost.append(self.cost[i-1] + money*self.fee_rate)
self.value.append(self.cash[i] + self.stock[i]*today_X.close)

# 生成收益率数据
self.genReturn()

# 计算回测指标
self.evaluation()

return self.bk_results

# 生成收益率数据
def genReturn(self):
# 生成收益率数据
self.return_value = pd.DataFrame(self.value)
self.return_value["value"] = self.value
self.return_value["returns"] = self.return_value["value"].pct_change()
self.return_value["benchmark_returns"] = self.data["close"].pct_change().values
self.return_value["date"] = self.data.index[:len(self.value)]
self.return_value.index = self.return_value["date"]

# 画结果
def draw(self):
oldpath = os.getcwd()
newpath = "/home/code/"
os.chdir(newpath)
plt.figure()
plt.plot(self.value)
plt.savefig("./output/" + modelname + "_backtest_value.png")
plt.close()
# 画每日收益率图
plt.figure()
plt.plot(self.return_value["returns"])
plt.savefig("./output/" + modelname + "_backtest_returns.png")
plt.close()
os.chdir(oldpath)

# 计算并返回回测评估结果
def evaluation(self):
returns = self.return_value.returns
benchmark = self.return_value.benchmark_returns
excess_return = returns - benchmark

# 用empyrical计算回测指标
# 年化收益率
self.bk_results["年化收益率"] = [ey.annual_return(returns)]
# 累计收益率
self.bk_results["累计收益率"] = [ey.cum_returns(returns)]
# 最大回撤
self.bk_results["最大回撤"] = [ey.max_drawdown(returns)]
# 夏普比率
self.bk_results["夏普比率"] = [ey.sharpe_ratio(excess_return)]
# 索提比率
self.bk_results["索提比率"] = [ey.sortino_ratio(returns)]
# αβ值
ab = ey.alpha_beta(returns, benchmark, risk_free = 0.02)
self.bk_results["α"] = ab[0]
self.bk_results["β"] = ab[1]

回测结果:
最大回撤 -0.014615
夏普比率 6.859218
索提比率 49.698812
α -0.729592
β 0.5453
好不好呢?不好评价,看看其它方法吧。
源代码

我发文章的三个地方,欢迎大家在朋友圈等地方分享,欢迎点“在看”。
我的个人博客地址:https://zwdnet.github.io
我的知乎文章地址: https://www.zhihu.com/people/zhao-you-min/posts
我的微信个人订阅号:赵瑜敏的口腔医学学习园地

欢迎打赏!感谢支持!