策略开发(股票)

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的可以使用量化平台进行研究回测,什么方便用什么。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值