利用TabNet模型进行股票长线预测
提示:本篇文章只提供一个思路,因本人是金融小白,搭建模型时所参考的股票指标并不完善,所以结果准确性无法保证,各位不要将结果作为投资参考!!!
文章目录
前言
重要的事情需要再次强调:本人是金融小白,人工智能初学者,第一次搭建股票预测模型,参考的股票指标并不全面,因此预测结果与真实值有较大误差,准确性无法保证,不要将预测结果作为投资的参考依据。
股票各指标均使用Tushare大数据开放社区提取,我的TushareID:423626。Tushare大数据社区网址:https://waditu.com/
一、TabNet框架介绍及下载
1.TabNet介绍
TabNet是一种具有可解释性的可以对表格数据进行高效分类和回归的模型。
论文题目: TabNet: Attentive Interpretable Tabular Learning
论文地址: https://arxiv.org/abs/1908.07442
为何使用TabNet?
目前决策树在处理表格数据上占据主导地位,其原因是:
1.表格数据存在近似的超平面边界,而决策树处理起来非常有效率。
2.在跟踪决策节点上,决策树具有高解释性。
3.决策树可以快速训练数据。
4.预先提出的DNN不适用于表格数据,因为传统DNN是基于多个传统层或多层感知,这会导致过参数化,使DNN找不到解决表格数据的最佳方法。
TabNet的优势:
1.与基于树的方法不同,TabNet可以输入未加工的数据,并且不需要任何特征处理。
2.TabNet在决策的每一步,使用序列注意力来选择特征进行推理。具有可解释性。
3.TabNet使用了非监督预训练来预测masked特征,该模型是第一个对表格数据进行自监督学习的。
2.TabNet的Pytorch版下载
Pytorch版下载: https://github.com/dreamquark-ai/tabnet
我使用的是Pycharm来运行TabNet,根据下载文件中的README_md文件完成库的安装。
二、从Tushare提取股票数据
1.Tushare是什么?
TuShare是一个免费、开源的python财经数据接口包。主要实现对股票等金融数据从数据采集、清洗加工 到 数据存储的过程,能够为金融分析人员提供快速、整洁、和多样的便于分析的数据,为他们在数据获取方面极大地减轻工作量,使他们更加专注于策略和模型的研究与实现上。
在Tushare 旧版运行了3年后,Tushare Pro在大家的期待下,终于要跟大家见面了。可以略显激动地说,Pro版数据更稳定质量更好了,但Pro依然是个开放的,免费的平台,不带任何商业性质和目的。
Tushare运行三年多以来,数据从广度和深度都得到了提升,Pro版正是在此基础上做了更大的改进。数据内容将扩大到包含股票、基金、期货、债券、外汇、行业大数据,同时包括了数字货币行情等区块链数据的全数据品类的金融大数据平台,为各类金融投资和研究人员提供适用的数据和工具。
Tushare官方网站:https://waditu.com/
2.如何预测股票的长线情况?
股票类型
为了减小游资操控股票这一因素的影响,此次预测的目标为市值大于50亿的股票。
预测目标
预测目标为某一年(下方例子为2020年)市值大于50亿的股票当年最大涨幅。
应用的股票指标
作为直接输入的特征的股票指标有:
市盈率、市净率、净资产收益率、销售毛利率、每股现金流、每股未分配利润、每股营业收入、每股资本公积。
作为需要二次计算的股票指标有:
当前股价、前六个月股价、前六个月流通股本、前六个月净流入、当年每月的最高股价。
历史高低位系数(自定义):(当前股价 - 前六个月股价平均值)/ 当前股价
放缩量系数(自定义):前六个月净流入总和 / 前六个月流通股本总和
当年股价最高位:Max(当年每月最高股价)
当年股票最大涨幅:当年股价最高位 / 当前股价
3.从Tushare提取股票数据代码
以下代码用于下载2020年市值大于50亿股票数据。
代码如下:
# -*-encoding:utf-8-*-
import tushare as ts
import pandas as pd
import numpy as np
import time
pro = ts.pro_api(token='***')
ts.set_token('***')
# 所有股票列表。 返回值1:股票代码、名字、行业,有表头索引。 返回值2:所有股票代码,有表头索引
def all_code():
stock_list = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name,industry')
code_list = stock_list['ts_code']
return stock_list, code_list
# 总市值大于50亿的股票。 输入值:所有股票代码,开年开盘日期。返回值:股票代码、市值、市盈率、市净率,有表头索引。
def stock_over50(code_list, date):
over50_list = pd.DataFrame()
for code in code_list:
total_mv_list = pro.daily_basic(ts_code=code, trade_date=date, fields='ts_code, total_mv, pe, pb')
time.sleep(0.1)
try:
if total_mv_list.iloc[0, 3] == None:
time.sleep(0.2)
elif total_mv_list.iloc[0, 3] > 500000:
over50_list = pd.concat([over50_list, total_mv_list], axis=0)
print(over50_list)
time.sleep(0.2)
else:
time.sleep(0.2)
except:
pass
continue
print(over50_list)
over50_list.to_csv('./over50_list_2020.csv', encoding='utf-8')
return over50_list
# 市盈率市净率。输入值:大于50亿股票。输出值:市盈率市净率列表。有表头索引:'pe', 'pb'
def pepb_list(over50_list):
pepb = over50_list[['ts_code', 'pe', 'pb']]
return pepb
# 前一年的年报数据。输入值1:大于50亿列表。输入值2:去年年报时间,如20191231。输出值,年报数值,有表头索引
def year_news_list(code_list, date_lastyear):
year_news1 = pro.fina_indicator(ts_code=code_list, period=date_lastyear)
year_news2 = pd.DataFrame(year_news1).iloc[0:1, :]
year_news3 = year_news2[['ts_code', 'roe', 'grossprofit_margin', 'cfps', 'undist_profit_ps', 'revenue_ps', 'capital_rese_ps']]
time.sleep(0.1)
# print(year_news3, 'years_news complete')
return year_news3
def history(code_over50):
now = pro.monthly(ts_code=code_over50, start_date='20200201', end_date='20200228', fields='ts_code,trade_date,open')
now = pd.DataFrame(now)
time.sleep(0.1)
avg6 = pro.monthly(ts_code=code_over50, start_date='20190801', end_date='20200131',
fields='ts_code,trade_date,open')
time.sleep(0.1)
list_his = float(0)
for i in avg6['open']:
list_his = list_his + i
history1 = list_his / 6
history = (now['open'].at[0] - history1) / now['open'].at[0]
history = '{:.2f}'.format(history)
history = pd.DataFrame(list([history]), columns=['history_level'])
# ts_code = now['ts_code']
# all_history = pd.concat([ts_code, history], axis=1)
# print(history)
return history
# 量:输入值:50亿以上股票。输出值:股票代码,量。有表头
def Vol(code_over50):
mf_vol = pro.moneyflow(ts_code=code_over50, start_date='20191001', end_date='20200301')['net_mf_vol']
time.sleep(0.1)
float_share_get = pro.daily_basic(ts_code=code_over50, start_date='20191001', end_date='20200301',
fields='ts_code, float_share')
time.sleep(0.1)
float_share = float_share_get['float_share']
mf_vol_sum = sum(mf_vol)
float_share_sum = sum(float_share)
Vol = mf_vol_sum / float_share_sum
Vol_all = pd.DataFrame(list([Vol]), columns=['Vol'])
# ts_code = pd.DataFrame(float_share_get['ts_code'])
# list_all = pd.concat([ts_code.iloc[0:1, 0], Vol_all], axis=1)
# print(Vol_all)
return Vol_all
# 计算一年内最高涨幅和最低跌幅。输入值:大于50亿股票代码。输出值1:股票代码,最大涨幅。输出值2:股票代码,最低跌幅。有表头
def Max_Min(code_over50):
All_data = pro.monthly(ts_code=code_over50, start_date='20200301', end_date='20210101',
fields='ts_code, open, trade_date, high, low')
time.sleep(0.1)
Max_data = max(All_data['high'])
Min_data = min(All_data['low'])
Open = pd.DataFrame(All_data).iloc[-1, 2]
Max_vol = 100 * (Max_data - Open) / Open
Min_vol = 100 * (Min_data - Open) / Open
Max_pd = pd.DataFrame(list([Max_vol]), columns=['Max_vol'])
Min_pd = pd.DataFrame(list([Min_vol]), columns=['Min_vol'])
# ts_code = pd.DataFrame(All_data).iloc[0, 0]
# ts_code = pd.DataFrame(list([ts_code]), columns=['ts_code'])
# Max_list = pd.concat([ts_code, Max_pd], axis=1)
# Min_list = pd.concat([ts_code, Min_pd], axis=1)
return Max_pd, Min_pd
stock_list, code_list = all_code()
date = '20200301'
over50_list = stock_over50(code_list, date)
over50_list.to_csv('./over50_list_2020.csv')
over50_list = pd.read_csv('./over50_list_2020.csv')
code_over50 = over50_list['ts_code']
print(code_over50)
pepb = pepb_list(over50_list)
print(pepb)
list_value = pd.DataFrame()
count = 0
for code in code_over50:
try:
new_list = year_news_list(code, '20191231')
history_list = history(code)
VOL = Vol(code)
Max_final, Min_final = Max_Min(code)
list1 = pd.concat([new_list, history_list, VOL, Max_final], axis=1)
list1.index = [count]
list_value = pd.concat([list_value, list1])
count += 1
if count % 10 == 0:
print('step:', count)
print(list_value)
except:
count += 1
pass
continue
list_value.to_csv('./list_value_2020.csv', encoding='utf-8')
Final_list = pd.concat([pepb, list_value], axis=1)
# Final_list = pd.concat([pepb, new_list, history, VOL, Max_final], axis=1)
# print(Final_list)
#
Final_list.to_csv('./Final_list_2020.csv', encoding='utf-8')
print('all complete')
三、用TabNet训练数据
此次测试运用的是TabNet的Regression做预测。
代码如下:
from pytorch_tabnet.tab_model import TabNetRegressor
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import mean_squared_error
import pandas as pd
import numpy as np
np.random.seed(0)
import os
dataset_name = 'stock'
# Load data and split
train = pd.read_csv('./Final_list_2020.csv')
target = 'int'
if "Set" not in train.columns:
train["Set"] = np.random.choice(["train", "valid", "test"], p=[.8, .1, .1], size=(train.shape[0],))
train_indices = train[train.Set == "train"].index
valid_indices = train[train.Set == "valid"].index
test_indices = train[train.Set == "test"].index
print(test_indices)
# Simple preprocessing
categorical_columns = []
categorical_dims = {}
for col in train.columns[train.dtypes == object]:
print(col, train[col].nunique())
l_enc = LabelEncoder()
train[col] = train[col].fillna("VV_likely")
train[col] = l_enc.fit_transform(train[col].values)
categorical_columns.append(col)
categorical_dims[col] = len(l_enc.classes_)
for col in train.columns[train.dtypes == 'float64']:
train.fillna(train.loc[train_indices, col].mean(), inplace=True)
# Define categorical features for categorical embeddings
unused_feat = ['Set', 'Max_vol']
features = [col for col in train.columns if col not in unused_feat + [target]]
cat_idxs = [i for i, f in enumerate(features) if f in categorical_columns]
cat_dims = [categorical_dims[f] for i, f in enumerate(features) if f in categorical_columns]
# define your embedding sizes : here just a random choice
# cat_emb_dim = [5, 4, 3, 6, 2, 2, 1, 10]
# Network parameters
clf = TabNetRegressor(cat_dims=cat_dims, cat_emb_dim=1, cat_idxs=cat_idxs)
# Training
X_train = train[features].values[train_indices]
y_train = train[target].values[train_indices].reshape(-1, 1)
X_valid = train[features].values[valid_indices]
y_valid = train[target].values[valid_indices].reshape(-1, 1)
X_test = train[features].values[test_indices]
print('X_test', X_test, X_test.shape)
y_test = train[target].values[test_indices].reshape(-1, 1)
print('y_test', y_test, y_test.shape)
max_epochs = 100 if not os.getenv("CI", False) else 2
# 训练误差:rmsle:均方根对数误差 mae:平均绝对误差 rmse:均方根误差 mse:均方误差
clf.fit(
X_train=X_train, y_train=y_train,
eval_set=[(X_train, y_train), (X_valid, y_valid)],
eval_name=['train', 'valid'],
eval_metric=['rmsle', 'mae', 'rmse', 'mse'],
max_epochs=max_epochs,
patience=10,
batch_size=12800, virtual_batch_size=64,
num_workers=0,
drop_last=False
)
# Deprecated : best model is automatically loaded at end of fit
# clf.load_best_model()
preds = clf.predict(X_test)
y_true = y_test
test_score = mean_squared_error(y_pred=preds, y_true=y_true)
print(f"BEST VALID SCORE FOR {dataset_name} : {clf.best_cost}")
print(f"FINAL TEST SCORE FOR {dataset_name} : {test_score}")
# Save model and load
# save tabnet model
saving_path_name = "./tabnet_model_test_2020"
saved_filepath = clf.save_model(saving_path_name)
print('saved_filepath', saved_filepath)
# define new model with basic parameters and load state dict weights
loaded_clf = TabNetRegressor()
loaded_clf.load_model(saved_filepath)
loaded_preds = loaded_clf.predict(X_test)
predict_list = pd.DataFrame(loaded_preds)
ture_list = pd.DataFrame(y_test, columns=['int'])
all_list = pd.concat([predict_list, ture_list], axis=1)
all_list.to_csv('./predict_2020.csv') # 将2020年test数据预测值与真实值导入表格中
loaded_test_mse = mean_squared_error(loaded_preds, y_test)
print(f"FINAL TEST SCORE FOR {dataset_name} : {loaded_test_mse}")
四、测试数据并进行对比
1.损失函数显示误差
这个结果并不能很好的表明预测结果,但系统告诉我们最好的权重是在第35个epoch,所以我们看一看第35个epoch的结果。
epoch 35 | loss: 0.32233 | train_rmsle: 0.09384 | train_mae: 0.40749 | train_rmse: 0.71892 | train_mse: 0.51685 | valid_rmsle: 0.07114 | valid_mae: 0.34773 | valid_rmse: 0.45248 | valid_mse: 0.20474 | 0:00:09s
这里我们关注MAE这个指标:平均绝对误差。该指标是指真实值和预测值的误差平均值。这里预测值valid_mae的值下降到了0.34773,说明预测值和真实值的差距平均大概在±35%以内。也就是说,我们预测当年的最大涨幅的误差大概在±35%,而这个结果就是为什么不能作为参考价值的原因,误差实在是太大了。
2.手动测试计算准确率
因为MAE指标只是显示误差区间,并不能准确的计算我们的投资是否成功,因此我将数据导入csv文件并手动将未参与训练的test部分比较误差。
下面代码是输入测试数据并生成预测值与真实值比较表格:
from pytorch_tabnet.tab_model import TabNetRegressor
from sklearn.metrics import mean_squared_error
import pandas as pd
import numpy as np
np.random.seed(0)
dataset_name = "预测误差"
predict = pd.read_csv('Final_predict_test.csv')
print(predict)
features = [col for col in predict.columns if col not in ['set', 'int', 'Max_vol']]
X_test = np.array(predict[features])
print(X_test)
y_test = np.array(predict['int']).reshape(-1, 1)
print(y_test)
# define new model with basic parameters and load state dict weights
saving_path_name = "./tabnet_model_test_1.zip"
# saved_filepath = clf.save_model(saving_path_name)
loaded_clf = TabNetRegressor()
loaded_clf.load_model(saving_path_name)
loaded_preds = loaded_clf.predict(X_test)
print("预测值:", loaded_preds)
loaded_test_mse = mean_squared_error(loaded_preds, y_test)
print(f"FINAL TEST SCORE FOR {dataset_name} : {loaded_test_mse}")
predict_list = pd.DataFrame(loaded_preds)
ture_list = predict['int']
all_list = pd.concat([predict_list, ture_list], axis=1)
all_list.to_csv('./predict_all.csv')
这里我一共训练了2017年-2020年四年的数据并生成模型,然后用2020年的test数据作为测试数据生成了预测值与真实值表格:
其中B列为预测值,C列为真实值,D列为比较真实值是否大于预测值。
从上图中可以看出,准确率只有57%
如果我们降低一下要求,真实值如果在预测值以下5%也可以接受的话,那么准确性数值勉强可观一些。
准确率可以提升到65%了!
六、总结
1.误差分析
我的股票预测误差如此之大是在意料之中的,可以分为以下几点讨论:
(1)股票是人操控的,影响股票走势的因素绝不仅仅与其基本面等等表象数据有关,因此对股票预测的准确率本身就不会达到绝对高的精度。
(2)因为我是金融小白,对股票这方面了解甚少,所以采用的输入特征,也就是股票基本面指标,与股票长线的走势关系不大,也有更多有关联的指标我没有用上。我相信股票的走势和基本面数据是存在关联的,但股票预测准确率的提高需要建立在比较完整的数据架构上。
(3)TabNet模型本身是一个比较完善的模型,其在森林覆盖与扑克手数据集预测上都有较好的准确率。但其内部有较多的参数和超参数,对于不同的模型可能需要修改以缩减收敛速度,增加预测准确率。但因为我的个人实力有限,不能将TabNet的效率和准确率发挥到最大。
(4)数据集的特征并不平均,大部分数据真实值都在0-0.4之间,这导致模型为了减小loss,预测值往往比较中庸。
(5)每年的大盘走势都不同,比如2018年股票大跌,2020年股票大涨,因此在预测2018年时,准确率大大下滑,而预测2020年数据的准确率就会比较客观。
2.未来展望
通过这次简单的测试,能看出股票走势与其基本面存在一定的联系,但要真正提高准确率,不仅需要更完善的人工智能处理框架,最重要的是需要一个较为完整的金融体系框架来分析。
如果有这方面经验和兴趣的,可以和我这只菜鸡一起交流。