机器学习SVC分类预测三个月后的股价
思路:通过学习近两年的每个季度报的基本面财务数据,建立模型,买入并持有预测三个月后会涨5%以上的股票,直到下一批季度报
数据采集:用到了大约10018行数据(已去除缺失值,不采用填充),其中采用了两个技术指标(趋势指标CYES,CYEL)
circulating_market_cap operating_revenue net_profit roe \ count 10018.000000 1.001800e+04 1.001800e+04 10018.000000 mean 154.594481 3.588924e+09 4.234473e+08 3.140488 std 600.063348 2.005736e+10 3.071935e+09 3.247377 min 3.120000 1.224100e+06 1.966154e+04 0.002400 25% 33.570000 2.299639e+08 2.410783e+07 1.472500 50% 58.854650 5.483732e+08 5.702822e+07 2.544550 75% 109.488325 1.511452e+09 1.540881e+08 3.915775 max 16176.732400 5.821850e+11 7.766000e+10 99.828000 inc_net_profit_year_on_year inc_revenue_year_on_year pe_ratio \ count 10018.000000 10018.000000 10018.000000 mean 385.339754 91.570442 216.210794 std 4309.253887 1648.839653 6348.503816 min 0.017300 -98.926300 1.920300 25% 18.204850 7.450175 33.834325 50% 47.288100 23.729200 54.822600 75% 140.021300 48.867150 96.220775 max 308628.250000 122241.677300 594205.375000 CYEL1 CYES1 peg price_increase_rate \
count 10018.000000 10018.000000 10018.000000 10018.000000 mean 0.065483 -0.031435 9.038361 -1.866428 std 1.070460 0.626431 128.634609 13.427512 min -7.415192 -3.243705 0.000165 -56.206703 25% -0.511237 -0.325856 0.455366 -9.436908 50% -0.028772 0.013026 1.163501 -3.026580 75% 0.554540 0.262382 3.137165 3.569913 max 10.001834 9.773301 8321.761237 247.121711 value count 10018.000000 mean 0.214314 std 0.410366 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000
数据处理:
去除空白值、缺失值,对原始股价求出增长率*100,若增长率>5%,目标值value=1,反之为0
计算了PE/G,按照金融指标利润,PEG越小(负值滚出克),越能体现股票的价值和成长性
数据源来自聚宽,源码:
import numpy as np
#去除np默认省略
np.set_printoptions(suppress=True)
import pandas as pd
import re
import time
import jqdata
from jqlib.technical_analysis import *
np.set_printoptions(threshold=np.inf)
def getdata(statdate):
#字符处理
year=statdate[0:4]
date=statdate[-1]
if date=='1':
date_start='0430'
date_end='0830'
elif date=='2':
date_start='0831'
date_end='1030'
elif date=='3':
date_start='1031'
date_end='1231'
elif date=='4':
year=str(int(year)+1)
date_start='0429'
date_end='0630'
date_start=year+date_start
date_end=year+date_end
print date_start,date_end
q = query(
valuation.code, #代码
valuation.circulating_market_cap, #流通市值
income.operating_revenue, #营业收入
income.net_profit, #净利润
indicator.roe , #净资产收益率
indicator.inc_net_profit_year_on_year, #同比净利润增长率
indicator.inc_revenue_year_on_year, #同比营业收入增长率
valuation.pe_ratio
).order_by(
valuation.code.asc())
df = get_fundamentals(q, statDate=statdate)
df.index=df['code']
#print df['code']
#正则取code列表
a=df['code'].values
a=str(a)
#print a
pattern=r"'(.*?)'"
list=re.findall(pattern,a)
#print list
print len(list)
#改日期格式
date_start = time.strptime(date_start, "%Y%m%d")
date_start=time.strftime("%Y-%m-%d",date_start)
print date_start
date_end = time.strptime(date_end, "%Y%m%d")
date_end=time.strftime("%Y-%m-%d",date_end)
print date_end
#date_start = time.strftime("%Y/%m/%d %H:%M:%S", timeStruct)
#获取时间区间股价及股价变化
df2 = get_price(list, start_date=date_start, end_date=date_end, frequency='daily', fields='close')
df3=pd.DataFrame(df2['close'])
#获取CYE(CYES,CYEL)
#df['CYEL1','CYES1']= CYE(df['code'].values,check_date= date_start)
CYEL1,CYES1 = CYE(df['code'],check_date= date_start
df['CYEL1']=pd.Series(CYEL1)
df['CYES1']=pd.Series(CYES1)
# 计算PE/G
df['peg'] = df.apply(lambda x: x['pe_ratio'] / x['inc_net_profit_year_on_year'], axis=1)
#df['price_increase_rate']
df4= pd.DataFrame((df3.ix[-1]-df3.ix[0])/df3.ix[0]*100)
df4.columns=['price_increase_rate']
df4['value']= df4['price_increase_rate'].where(df4['price_increase_rate']>5,0)
df4['value']= df4['value'].where(df4['price_increase_rate']<=5,1)
#print df
#print df4
#print df4.describe()
#df['price_increase_rate']=
df4=pd.concat([df,df4],axis=1)
print df4['price_increase_rate']
print df4.describe()
return df4
#list=['2016q2','2016q3','2016q4','2017q1','2017q2','2017q3']
list=['2016q1']
for statdate in list:
data=getdata(statdate)
#print data
#print data.describe()
string=str(statdate)+'.csv'
data.to_csv(string)
print'已储存%s'%(statdate)
#读取csv
from six import StringIO
csvlist=['2016q2.csv','2016q3.csv','2016q4.csv','2017q1.csv','2017q2.csv','2017q3.csv']
body=read_file(csvlist[0])
data=pd.read_csv(StringIO(body))
#data.index=data['code']
i=1
while i <= len(csvlist)-1:
body=read_file(csvlist[i])
data2=pd.read_csv(StringIO(body))
#data2.index=data['code']
#del data['code.1']
#print data.head(20)
data=pd.concat([data,data2],axis=0)
i=i+1
del data['code.1']
print len(data)
#去除负值(没有成长性和目前经营不善的股票)
df=data[(data['net_profit'] >0) & (data['roe'] > 0)&(data['pe_ratio'] > 0)&(data['peg'] > 0)]
df.dropna(inplace=True)
print df.head(5)
print df.describe()
模型制作:
数据中去除了负值(没有成长性和目前经营不善的股票),保留了高成长性和基本面优异的股票下一步
70%训练,30%检验,数据进行了归一化。
from sklearn import preprocessing
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.svm import SVC
# 数据集0.3 0.7分集
X_train,X_test,y_train,y_test=train_test_split(df.ix[:,['circulating_market_cap','operating_revenue','net_profit','roe','inc_net_profit_year_on_year','inc_net_profit_year_on_year', \
'pe_ratio','CYEL1','CYES1','peg']],df['value'], test_size=0.3, random_state=40)
#model2 = MLPClassifier(activation='relu', solver='adam', alpha=0.0001)
#做了标准化归一化
X_scaled = preprocessing.scale(X_train)
X_test_scaled = preprocessing.scale(X_test)
model2 = SVC(C=1.0, kernel='rbf', gamma='auto')
model2.fit(X_scaled,y_train)
predict = model2.predict(X_test_scaled)
print model2.score(X_test_scaled, y_test)
#混淆矩阵
print confusion_matrix(y_true=y_test, y_pred=predict)
考虑到股票预测的特殊性,SCORE准确度虽然高达78%,但假阳性/阳性,真阳性/阳性的比率才是我所需要的
在本次测试中,测试集一共做出了18次判断,假阳7次,真阳11次。效果看似并不理想,其实63%准确度的三个月5%的预测结果足以让投资者获得超额收益。
***假阳并不意味着亏损,毕竟即使涨1%,虽然不符合value判断阀门,但也是盈利。
准确度:
0.787425149701混淆矩阵:
[[2356 7]
[ 632 11]]
sklearn svc模型的导入和使用
使用sklearn的joblib包保存模型
将新的季度财务数据导入模型源码:
# -*- coding: utf-8 -*-
from sklearn.externals import joblib
from sklearn import preprocessing
import numpy as np
np.set_printoptions(suppress=True)
import pandas as pd
model = joblib.load('SVC_STOCK.model')
#读取csv
from six import StringIO
body=read_file('2018q1.csv')
data=pd.read_csv(StringIO(body))
#data.index=data['code']
del data['code.1']
print len(data)
#去除负值(没有成长性和目前经营不善的股票)
df=data[(data['net_profit'] >0) & (data['roe'] > 0)&(data['pe_ratio'] > 0)&(data['peg'] > 0)]
del df['price_increase_rate']
df.dropna(axis = 0)
print df.head(5)
print df.describe()
print len(df)
X_scaled=preprocessing.scale(X1)
predict = model.predict(X_scaled)
count=0
for e in predict:
if e == 1:
count=count+1
print count
11
2018年一季报的财务结果并预测了11只股票会增长超过5%
#输出股票代码
df['predict']=predict
list=[]list=df['code'][df['predict'] == 1].values
print list
['000333.XSHE' '000651.XSHE' '000858.XSHE' '002290.XSHE' '002415.XSHE' '600019.XSHG' '600267.XSHG' '600276.XSHG' '600311.XSHG' '600519.XSHG' '600747.XSHG']
对上述的结果进行回测检验
joinquant 回测代码:
# 导入函数库
import jqdata
from datetime import *
import math
import numpy as np
import pandas as pd
# 初始化函数,设定基准等等
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
#2015Q2
#g.stock=['000554.XSHE' ,'000687.XSHE', '600321.XSHG' ,'600519.XSHG' ,'600533.XSHG','600629.XSHG']
#2016Q1
g.stock_2016q1=['000892.XSHE', '600519.XSHG', '601766.XSHG']
#2016Q2
g.stock_2016q2=['000609.XSHE' ,'600519.XSHG' ,'600984.XSHG', '601398.XSHG', '601766.XSHG','601800.XSHG']
#2016Q3
g.stock_2016q3=['000002.XSHE' ,'000333.XSHE' ,'600519.XSHG' ,'601318.XSHG', '601628.XSHG','601800.XSHG']
#2016Q4
g.stock_2016q4=['600519.XSHG', '601766.XSHG']
#2017Q1
g.stock_2017q1=['000333.XSHE', '000557.XSHE', '000651.XSHE', '002122.XSHE' ,'002141.XSHE',
'002265.XSHE' ,'002346.XSHE', '300093.XSHE' ,'300338.XSHE' ,'300391.XSHE',
'600019.XSHG', '600251.XSHG' ,'600519.XSHG', '600693.XSHG', '600817.XSHG',
'600876.XSHG' ,'601628.XSHG']
#2017Q2
g.stock_2017q2=['000002.XSHE', '000333.XSHE', '000651.XSHE' ,'000858.XSHE' ,'002415.XSHE',
'300618.XSHE' ,'600519.XSHG', '601601.XSHG']
#2017Q3
g.stock_2017q3=['000002.XSHE' ,'000333.XSHE' ,'000651.XSHE' ,'002192.XSHE' ,'300354.XSHE',
'600019.XSHG' ,'600238.XSHG', '600519.XSHG' ,'600769.XSHG' ,'601177.XSHG',
'601318.XSHG', '601601.XSHG']
#2018q1
g.stock_2018q1=['000333.XSHE', '000651.XSHE' ,'000858.XSHE', '002290.XSHE' ,'002415.XSHE',
'600019.XSHG', '600267.XSHG' ,'600276.XSHG' ,'600311.XSHG' ,'600519.XSHG',
'600747.XSHG']
# 0430-0830,0831-1030,1031-0430,0429-0629(年报不用,发布太慢)
#2016q1-2018q1
# 过滤掉order系列API产生的比error级别低的log
# log.set_level('order', 'error')
### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
## 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入'000300.XSHG'或'510300.XSHG'是一样的)
# 开盘前运行
run_daily(before_market_open, time='before_open', reference_security='000300.XSHG')
# 开盘时或每分钟开始时运行
run_daily(market_open, time='every_bar', reference_security='000300.XSHG')
# 收盘后运行
run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')
## 开盘前运行函数
def before_market_open(context):
#context.current_dt
#引用股票列表
if date(2016, 03, 31)<context.current_dt.date()<= date(2016, 8, 30):
g.stock=g.stock_2016q1
elif date(2016, 8, 31)<=context.current_dt.date()<= date(2016, 10, 30):
g.stock=g.stock_2016q2
elif date(2016, 10, 31)<=context.current_dt.date()<= date(2017, 04, 30):
g.stock=g.stock_2016q3
if date(2017, 05, 01)<=context.current_dt.date()<= date(2017, 8, 30):
g.stock=g.stock_2017q1
elif date(2017, 8, 31)<=context.current_dt.date()<= date(2017, 10, 30):
g.stock=g.stock_2017q2
elif date(2017, 10, 31)<=context.current_dt.date()<= date(2018, 04, 30):
g.stock=g.stock_2017q3
else:
g.stock=g.stock_2018q1
'''
elif date(2017, 05, 01)<=context.previous_date<= date(2017, 8, 30):
g.stock=g.stock_2018q1
elif date(2016, 8, 31)<=context.previous_date<= date(2016, 10, 30):
g.stock=g.stock_2016q2
elif date(2016, 10, 31)<=context.previous_date<= date(2017, 04, 30):
g.stock=g.stock_2016q3
'''
#过滤st和停牌
remove_paused_and_st(g.stock)
## 开盘时运行函数
def market_open(context):
cash = context.portfolio.available_cash
if len( context.portfolio.positions)>0:
for f in context.portfolio.positions:
if f not in g.stock:
order_target(f, 0)
for e in g.stock:
if context.portfolio.available_cash>100000 :
order_value(e, 100000)
## 收盘后运行函数
def after_market_close(context):
pass
def remove_paused_and_st(stock_list):
current_data = get_current_data(stock_list)
return [stock for stock in stock_list if not current_data[stock].paused
and not current_data[stock].is_st
and 'ST' not in current_data[stock].name
and '*' not in current_data[stock].name
and '退' not in current_data[stock].name]
这个策略只择股不择时,不止损和无风控,纯属毛坯房。未来将考虑使用技术指标的改进版进行择时交易,并进行止盈止损。
最大回测20%(时间区间为今年初A股受中美经济战崩盘),事实上201805月已明显反弹股价,并表现明显强于沪深300