1、相关理论基础简单介绍。
1.1常见的几种量化投资策略(股票)
1.选股策略如财务选股->什么样的股票长期能上涨?
2.择时策略->什么时候买入,什么时候卖出?现在买时否合适?如技术指标择时。
3.套利策略->利用统计学进行统计套利,例如期货的价差套利。
4.事件驱动策略->例如上面说的电影票房,或者公司回购股票是否有利于股票上涨?
等等.....
1.2如何开发量化策略(股票)
量化策略的开发有一个大致的流程:
1.一个idea,可以是一个想法,也可以是一个历史规律,或者偶然间得到的一句启发。
2.收集数据,可以是历史数据,也可以是实时数据,整理数据,清洗数据,去重数据,补全数据,将数据转换成适合的格式。
3.分析数据,利用统计学,时间序列分析,机器学习等方法,对数据进行深入分析,以发现有用的信息和模式,得到一些规律,制定相关策略模型。
4.开发策略模型回测,根据策略模型,编写代码,基于模型买入、卖出或持有股票等决策,回溯历史收益。、
5.策略的优化,优化策略,提高策略的收益率,稳定性。
6.策略实盘,落地交易以及持续监控优化
1.3本节小结:
对于新手来说,难度在2-5,对于入门者来说难度在1和6。
前者需要学习新的技能,入门难度大,后者监控/优化老策略,失效后又需要开发新策略,而往往新策略的开发最费功夫。
这里新手入门量化投资策略,可参考书籍:《量化投资策略》《基本面量化投资》
更多策略来源:1.各大券商投资研报。2.量化投资论坛。3.相关学术论文等。
2、基于技术指标的指数择时策略及python代码实现
2.1技术指标及指数择时策略
上百年的金融历史,诞生了无数的技术指标,例如MACD,RSI,KDJ,BOLL,SAR等。
这些技术指标基于各种历史数据,例如开盘价、最高价、最低价、收盘价、成交量等数据,构建出趋势、动量、摆动等类型的指标。
这些指标能反映一些市场趋势,例如牛市、熊市、超买、超卖等。
当然也有一些基于另类数据的指标,这些指标往往基于一些历史事件,例如IPO、重大股东增持、重大股东减持、重大资产重组等。
本节我们将基于成分股涨跌数据,自定义一个涨跌比例技术指标,并尝试开发一个指数择时策略。
2.2数据整理及统计分析
#导入相关库,为了避免重复,这里一次性导入本内容所需要的所有库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import akshare as ak
import warnings
import backtrader as bt
import datetime
warnings.filterwarnings('ignore')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
#数据已经获取好了,直接导入即可
df=pd.read_excel(r'/home/mw/input/000018533/QF_6_000300sh.xlsx')
df
#查看数据的基本信息
df.info()
#需要设置index,并且是日期格式
df.set_index(pd.to_datetime(df.日期),inplace=True)
df.drop('日期', axis=1, inplace=True)
df
#查看数据是否缺失
df.isnull().sum()
#可视化看看是否异常
fig ,axex= plt.subplots(3,1,figsize=(16,6),dpi=100) # 创建图
ax1,ax2,ax3=axex.flatten() # 创建子图
#绘制股价图
ax1.plot(df.close,color='b',label='收盘价')
ax1.grid()
ax1.legend(fontsize=10)
ax2.bar(df.index,df.vol,color='r',label='成交量')
ax2.legend(fontsize=10)
ax3.plot(df.ADRCALL, color='r', label='上涨数量')
ax3.plot(df.ADRPUT, color='b', label='下跌数量')
ax3.legend(fontsize=10)
2.3策略开发及回测
本次指数择时策略开发思路:
首先我们要明白一点,股票的指数是由成分股构成的,例如:
沪深300指数,是由300家股票构成,而中证500指数,是由500家股票构成。那么我们是否可以通过分析成分股的涨跌情况,来判断指数是否出现超买/超卖现象,从而判断预测指数未来的走势呢?
我这里举个例子,例如沪深300指数成分股也就300只而已,例如今天收盘时上涨的成分股数量是30,平盘的是10,下跌的是260家!那请问指数短期是不是超跌呢?后面有没有可能反弹或者修复?
我们这里可以先做一下统计,看看这种情况(90%的成分股上涨或下跌)出现的比例有多少。
#计算上涨家数/300占比数据,并可视化
df['上涨成分股比例']=df.ADRCALL/300
df_copy=df.copy()
#可视化看看是否异常
fig ,axex= plt.subplots(2,1,figsize=(16,6),dpi=100) # 创建图
ax1,ax2=axex.flatten() # 创建子图
#绘制股价图
ax1.plot(df.close,color='b',label='收盘价')
ax1.grid()
ax1.legend(fontsize=10)
ax2.bar(df.index,df.上涨成分股比例, color='r', label='上涨比例')
ax2.legend(fontsize=10)
上涨成分股比例越大表明市场越强势,这里就出现了一个分水岭,有人认为这种状态是市场趋势,很强势应该视作买入信号。
但也有人认为物极必反,是反转的信号,应该卖出!
我们这里做一下简单测试,看看哪种相对正确。
#计算N日后涨跌幅
day_list=[1,5,10,20,30]
def calculate_N_pct(data,day_list):
for day in day_list:
data['%s日后涨幅'%day]=data['close'].shift(0-day)/data['close']-1
data.loc[data['%s日后涨幅'%day]>0,'%s日后是否上涨'%day]=1#上涨记为1,下跌记为0
data.loc[data['%s日后涨幅'%day]<=0,'%s日后是否上涨'%day]=0
return data
df_000300=calculate_N_pct(df_copy,day_list)
#统计交易信号的胜率及赔率
def signal_order_buy_sell(data_code,day_list,TorF,num):
if TorF=='True':#True为大于,False为小于num
data_code_copy=data_code.loc[data_code['上涨成分股比例']>num]
for x in day_list:
print({'%s日后涨幅大于0比例为'%x:data_code_copy[data_code_copy['%s日后是否上涨'%x]==1].shape[0]/data_code_copy.shape[0],'且上涨平均收益为:':data_code_copy[data_code_copy['%s日后是否上涨'%x]==1]['%s日后涨幅'%x].mean()})
elif TorF=='False':#True为大于,False为小于num
data_code_copy=data_code.loc[data_code['上涨成分股比例']<num]
for x in day_list:
print({'%s日后涨幅大于0比例为'%x:data_code_copy[data_code_copy['%s日后是否上涨'%x]==1].shape[0]/data_code_copy.shape[0],'且上涨平均收益为:':data_code_copy[data_code_copy['%s日后是否上涨'%x]==1]['%s日后涨幅'%x].mean()})
df_000300_copy=df_000300['2015':'2021']#用2015-2021年数据做统计测试,2021年之后的数据做回测。
signal_order_buy_sell(df_000300_copy,day_list,'True',0.90)
#计算指标大于0.9时,历史收益表现
signal_order_buy_sell(df_000300_copy,day_list,'False',0.1)
#计算指标小于0.1时,历史收益表现
结果显示,相较于历史趋势,物极必反略胜一筹,这也与我个人的历史经验相吻合——在A股,行情越是火热的时候,越容易出现短期高点。
所以我们接下来以第二种为模板,构建一个物极必反的指数择时策略。即在成分股下跌比例较大的时候买入,在成分股上涨比例较大的时候卖出!
回测标的为沪深300,时间为20220101-20240208。
看涨信号设置:“成分股上涨比例小于10%”!
卖出信号设置:持股5天就卖出!
class PandasData_more(bt.feeds.PandasData):
lines = ('ADRPUT','ADRCALL' ) # 要添加的线
# 设置 line 在数据源上的列位置
params=(
('ADRPUT', -1),('ADRCALL', -1),)
# -1表示自动按列明匹配数据,也可以设置为线在数据源中列的位置索引
cerebro = bt.Cerebro()
data_000300=pd.read_excel(r'/home/mw/input/000018533/QF_6_000300sh.xlsx')
data_000300['openinterest']=0#添加一列数据
data_000300=data_000300.loc[:,['open','high','low','close','vol','openinterest','日期','ADRCALL','ADRPUT']]#选择数据
data_000300.columns=['open','high','low','close','volume','openinterest','datetime','ADRCALL','ADRPUT']#修改列名
data=data_000300.set_index(pd.to_datetime(data_000300['datetime'].astype('str'))).sort_index()#排
data.loc[:,['volume','openinterest']] = data.loc[:,['volume','openinterest']].fillna(0)
data.loc[:,['open','high','low','close']] = data.loc[:,['open','high','low','close']].fillna(method='pad')
datafeed = PandasData_more(dataname=data,
fromdate=datetime.datetime(2022,1,1),
todate=datetime.datetime(2024,2,8))
cerebro.adddata(datafeed, name='000300.SH') # 通过 name 实现数据集与股票的一一对应
print('读取成功')
# 创建策略
class ADR_day(bt.Strategy):
def __init__(self):
#基于ADRPUT/ADRCALL计算指标
self.adrcall=self.data.ADRCALL/300
self.num=0
def next(self):
#计算买卖条件
#空仓时开多
if self.getposition(self.data).size==0:#空仓情况下
self.num=0
if self.adrcall[0]<0.10:
self.order = self.order_target_percent(self.data0,0.99)
self.num+=1
print('时间:%s买入开仓成功'%self.datetime.date(0),self.adrcall[0])
elif self.getposition(self.data).size>0:#持有多单仓情况下
self.num+=1
if self.num>=5:
self.order = self.order_target_percent(self.data0,0)
print('时间:%s卖减多仓成功'%self.datetime.date(0))
cerebro.addstrategy(ADR_day)
# 初始资金 1,000,000
cerebro.broker.setcash(10000000.0)
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003)
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001)
# 添加分析指标
# 返回年初至年末的年度收益率
cerebro.addanalyzer(bt.analyzers.AnnualReturn,_name='_AnnualReturn')
# 计算最大回撤相关指标
cerebro.addanalyzer(bt.analyzers.DrawDown,_name='_DrawDown')
# 计算年化收益
cerebro.addanalyzer(bt.analyzers.Returns,_name='_Returns',tann=252)
# 计算年化夏普比率
cerebro.addanalyzer(bt.analyzers.SharpeRatio_A,_name='_SharpeRatio_A')
# 返回收益率时
cerebro.addanalyzer(bt.analyzers.TimeReturn,_name='_TimeReturn')
# 启动回测
result=cerebro.run()#这里核心关键是设置cpu单核测试
#走势对比
ret = pd.Series(result[0].analyzers._TimeReturn.get_analysis())#获得收益时序图
#画图
fig, ax1 = plt.subplots(figsize=(10,6),dpi=100)#创建画布
ax1.plot((ret+1).cumprod().index,(ret+1).cumprod().values,'r-',label = "成分股涨跌比例策略走势")
ax1.plot((data['2022':].close.pct_change(1)+1).cumprod(),'b-',label = "沪深300走势")
ax1.set_title('成分股涨跌比例策略收益与沪深300指数对比',fontsize=10)
ax1.legend(loc='upper right')
plt.show()
#自带的工具可视化展示
cerebro.plot()[0]
不考虑其它指标,单从回测收益看,这个策略已经算成功——策略在沪深300指数趋势向下的情况还略有盈利,唯一的不足是,交易次数少,粗略看去一年不到10次,更偏向于中期择时。
但是一个策略的成功与否,不能单看收益率,也可以结合其它指标,比如年化收益率、最大回撤、夏普比率等,来综合评估策略的优劣。
2.4策略优化
策略模型优化的方向主要有以下几个方面:
1.更新和优化模型参数:通过对模型参数进行更新和优化,提高模型对市场变化的敏感度和预测精度。这可以通过调整模型参数、添加新变量或重新构建模型来实现。
2.引入新的模型和方法:不断研究和引入新的模型和方法,可以提高策略模型的适应性和预测能力。这可能涉及到更复杂的数学和统计方法,以及最新的机器学习算法等。
3.增加数据源:通过增加更多的数据源,可以扩大策略模型的数据基础,提高模型的预测能力和稳定性。这可能包括从多个不同的数据供应商处获取数据,或者使用更高级的数据分析技术,如文本分析等。
4.优化算法交易:通过优化算法交易策略,可以提高交易的效率和盈利能力。这可能包括改进交易算法、优化交易量、确定最佳的交易时间等。
上述案例策略其实有很多优化方向,比如仓位管理——空仓时购入国债逆回购,增厚无风险收益。也可以进一步挖掘市场特征,如市场趋势、市场波动性等,辅助优化策略。这里受制于篇幅,不再展开。
感兴趣的同学可以自行研究。
2.5本节练习:
使用前面学习的评价指标,如年化收益率、最大回撤、夏普比率等综合评价此策略的优劣。
3、基于财务因子的选股策略及python代码实现
3.1财务因子及选股策略
在训练营一中我们学习了因子分析,本章我们尝试多因子/多条件构建一个因子选股策略。
范围为全部A股,案例条件为:
1.pb>0,升序排列,取前1000名。
2.股息率>0,降序排列,取前1000名。
3.当期营收增长>0,降序排列,取前1000名。
4.当期扣非净利润增速>0,降序排列,取前1000名。
5.取上述条件交集,每月第一个交易日调仓换股。
3.2数据整理及统计分析
#节约时间,直接读取即可,数据不再展示。
#stock_list=pd.read_excel(r'C:\Users\Administrator\Desktop\QF_6_调仓表.xlsx')
#code_data=pd.read_excel(r'I:\项目\mw量化训练营\QF_6_数据表.xlsx')
3.3策略开发及回测
# 实例化 cerebro
cerebro = bt.Cerebro()
# 读取行情数据
daily_price = pd.read_excel(r'/home/mw/input/000018533/QF_6_数据表.xlsx', parse_dates=['trade_date'])
daily_price['openinterest']=0
daily_price = daily_price.set_index(pd.to_datetime(daily_price['trade_date']))
# 按股票代码,依次循环传入数据
for stock in daily_price['code'].unique():
# 日期对齐
data = pd.DataFrame(index=daily_price.index.unique()) # 获取回测区间内所有交易日
df = daily_price.query(f"code=='{stock}'")[['open','high','low','close','volume','openinterest']]
data_ = pd.merge(data, df, left_index=True, right_index=True, how='left')#通过日期合并需要的数据。
data_.sort_index(inplace=True) # 按日期排序
# 缺失值处理:日期对齐时会使得有些交易日的数据为空,所以需要对缺失数据进行填充
data_.loc[:,['volume','openinterest']] = data_.loc[:,['volume','openinterest']].fillna(0)
data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(method='pad')
data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(0)
# 导入数据
datafeed = bt.feeds.PandasData(dataname=data_,
fromdate=datetime.datetime(2022,1,1),
todate=datetime.datetime(2023,12,29))
cerebro.adddata(datafeed, name=stock) # 通过 name 实现数据集与股票的一一对应
print(f"{stock} 导入成功 !")
# 回测策略
class StockStrategy(bt.Strategy):#创建策略名称
'''多因子选股 - 基于调仓表'''
def log(self, txt, dt=None):#日志函数。
''' 策略日志打印函数'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))#打印一些相关的交易信息,每日执行信息等。可以自己设置。
def __init__(self):#In it的方法只执行一次
self.buy_stock = pd.read_excel(r'/home/mw/input/000018533/QF_6_调仓表.xlsx')
# 读取调仓日期,即每月的第一个交易日
self.trade_dates = pd.to_datetime(self.buy_stock['date'].unique()).tolist()#创建日期列表。
self.buy_stock['date']=pd.to_datetime(self.buy_stock['date']).dt.date
self.order_list = [] # 记录以往订单,方便调仓日对未完成订单做处理
self.buy_stocks_pre = [] # 记录上一期持仓
def next(self):#主要交易函数。交易信息在这个里面去进行填写。
dt = self.datas[0].datetime.date(0) # 获取当前的回测时间点
# 如果是调仓日,则进行调仓操作
if dt in self.trade_dates:#判断当天日期是否在之前导入的日期列表里面
print("--------------{} 调仓日----------".format(dt))
# 在调仓之前,取消之前所下的没成交也未到期的订单
if len(self.order_list) > 0:
for od in self.order_list:
self.cancel(od) # 如果订单未完成,则撤销订单
self.order_list = [] #重置订单列表
# 提取当前调仓日的持仓列表
buy_stocks_data=self.buy_stock[self.buy_stock['date']==dt].dropna(axis=1)
buy_stocks_data=buy_stocks_data.drop(['date'],axis=1)
long_list = buy_stocks_data.values.tolist()[0]#提取当前持仓日的代码,并转为列表。
print('long_list', long_list) # 打印持仓列表。
# 对现有持仓中,调仓后不再继续持有的股票进行卖出平仓
sell_stock = [i for i in self.buy_stocks_pre if i not in long_list]
if len(sell_stock) > 0:#判断有多少个?
print("-----------对不再持有的股票进行平仓--------------")
print('sell_stock', sell_stock) # 打印平仓列表
for stock in sell_stock:
data = self.getdatabyname(stock)#调用列表名称。
if self.getposition(data).size > 0 :#这个地方查询的是持仓量。
od = self.order_target_percent(data=data, target=0)#卖出。
self.order_list.append(od) # 记录订单
# 买入此次调仓的股票:多退少补原则
print("-----------买入此次调仓期的股票--------------")
for stock in long_list:#循环需要买入的代码。
w=1/len(long_list)
data = self.getdatabyname(stock)
order = self.order_target_percent(data=data, target=w*0.97) # 为减少可用资金不足的情况,这里每只只买预期的97%
self.order_list.append(order)
self.buy_stocks_pre = long_list # 保存此次调仓的股票列表'''
def notify_order(self, order):#订单日志函数。
# 未被处理的订单
if order.status in [order.Submitted, order.Accepted]:#从status订单状态函数里面查询是否有被经纪商接收和传递的订单。
return
# 已经处理的订单
if order.status in [order.Completed, order.Canceled, order.Margin]:#从订单状态函数里面查询成交的。被撤销的。需要增加保证金的订单。
if order.isbuy():#判断是买单。
self.log(
'BUY EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
(order.ref, # 订单编号
order.executed.price, # 成交价
order.executed.value, # 成交额
order.executed.comm, # 佣金
order.executed.size, # 成交量
order.data._name)) # 股票名称
else: # Sell#判断是卖单的。
self.log('SELL EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
(order.ref,
order.executed.price,
order.executed.value,
order.executed.comm,
order.executed.size,
order.data._name))
# 初始资金 100,000,000
cerebro.broker.setcash(100000000.0)
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003)
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001)
# 将编写的策略添加给大脑,别忘了 !
cerebro.addstrategy(StockStrategy)
# 回测时需要添加 PyFolio 分析器
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
result = cerebro.run()
# 借助 pyfolio 进一步做回测结果分析
pyfolio = result[0].analyzers.pyfolio
returns, positions, transactions, gross_lev = pyfolio.get_pf_items()
#提取收益率时序可视化下
#画图
%matplotlib inline
fig, ax1 = plt.subplots(figsize=(10,6),dpi=100)#创建画布
ax1.plot((returns+1).cumprod().index,(returns+1).cumprod().values,'r-',label = "策略走势")
ax1.plot((df_copy['2022':'2023'].close.pct_change(1)+1).cumprod(),'b-',label = "沪深300走势")
ax1.set_title('策略收益与沪深300指数对比',fontsize=10)
ax1.legend(loc='upper right')
plt.show()
3.4本节小结
本节我们学习了利用财务因子条件进行选股,从回测的结果来看收益虽然不高,但是胜在跑赢市场。但是我们发现选股的股票数量很少,部分时段不到5只。存在集中选股的问题。
此外在回测过程中,我们为加入风险控制如持仓止损等条件,可能存在部分个股损失较大的情况。
3.5本节练习
针对上述情况,请你尝试做两个动作:
1.若选股数量较少,低于10只时,只使用总仓位的50%建仓,其余的50%空仓或者用于购买国债逆回购。
2.加入止损条件,即持仓达到设定的止损点时,进行止损操作。
建议:此动手题略有难度,不熟悉backtrader的可以使用量化平台进行研究回测,什么方便用什么。