# 量化时代来了 **Repository Path**: zheng-huihuang/to-financial-investment ## Basic Information - **Project Name**: 量化时代来了 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2021-11-11 - **Last Updated**: 2025-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 量化回测系统 ## 项目框架 - DataSet_new2.py - 选股_对象.py - 回测_面向对象.py - Factor_Alpha101_code.py - all_stock_data_d_py3_w8.h5 - stock_data文件夹 - sz000001.csv - main.py 前面三个分别是数据处理类、选股类、回测类。Factor_Alpha101_code.py中存储了一些计算alpha因子可能用到的函数,all_stock_data_d_py3_w8.h5是生成的中间数据,stock_data文件夹中包含所有股票的csv文件,sz000001.csv是指定的基准收益,运行整个回测系统只需要运行主函数main.py即可,main.py会自动调用其他类最终返回交易的收益。 ## 程序结构 程序框架由三个类组成,由类之间的交互实现整体功能。将数据处理作为一个单独的类,alpha因子计算和选股作为第二个类,回测和可视化作为第三个类。 ### 数据处理类主要实现从本地csv文件中读取历史股票数据,读取收益基准的信息,并根据选股周期进行股票信息的周期转换,将整理好的数据保存在本地。 ### 因子计算和选股类读取整理好的基本信息,根据基础数据计算alpha因子,并在因子的基础上实现若干选股策略进行股票买入卖出。 ### 回测类根据股票的交易记录计算交易的收益并与基准收益(股票指数)进行比较并实现可视化。 ## 各类介绍 综述:详细介绍各类实现的功能以及类与类之间如何完成数据交流。 ### 类1:数据读写类:DataSet类 介绍:读入本地的股票历史数据包括股票名称,股票代码,开盘价,收盘价,成交量,涨跌幅等信息。 #### 1.类基本属性 基本属性包括:股票csv文件地址,指数csv地址,股票数据,生成hdf文件地址。 #### 2.读取股票信息 遍历读取存储csv文件夹中的csv文件,在读取股票信息过程中根据基本数据生成‘下日_是否交易’,‘一字涨停’,‘下日_开盘涨停’,‘下日_是否ST’等信息,用于后续的选股。生成的股票信息存储在DataSet.all_stock_data属性中。 ``` # 计算涨停价格 df['涨停价'] = df['前收盘价(元)'] * 1.1 # 针对st进行修改 df.loc[df['简称'].str.contains('ST'), '涨停价'] = df['前收盘价(元)'] * 1.05 # 四舍五入 df['涨停价'] = df['涨停价'].apply(lambda x: float(Decimal(x * 100).quantize(Decimal('1'), rounding=ROUND_HALF_UP) / 100)) # 判断是否一字涨停 df['一字涨停'] = False df.loc[df['最低价(元)'] >= df['涨停价'], '一字涨停'] = True # 判断是否开盘涨停 df['开盘涨停'] = False df.loc[df['开盘价(元)'] >= df['涨停价'], '开盘涨停'] = True # =计算下个交易的相关情况 df['下日_是否交易'] = df['是否交易'].shift(-1) df['下日_一字涨停'] = df['一字涨停'].shift(-1) df['下日_开盘涨停'] = df['开盘涨停'].shift(-1) df['下日_是否ST'] = df['简称'].str.contains('ST').shift(-1) df['下日_是否退市'] = df['简称'].str.contains('退').shift(-1) df['下日_开盘买入涨跌幅'] = df['开盘买入涨跌幅'].shift(-1) ``` #### 3.周期转换 初始csv中存储的数据是每日的股票数据,为便于后续指定选股周期,在DataSet类中定义了一个用于周期转换的方法,根据指定的period_type(取值为'd','w','m')可以分别根据初始的每日数据生成每周数据,每月数据。主要代码如下所示: ``` df['周期最后交易日'] = df['日期'] df.set_index('日期', inplace=True) # print(df.columns) period_df = df.resample(rule=period_type).agg(self.retain_factors) # 计算必须额外数据 period_df['交易天数'] = df['是否交易'].resample(period_type).sum() period_df['市场交易天数'] = df['代码'].resample(period_type).size() period_df = period_df[period_df['市场交易天数'] > 0] # 有的时候整个周期不交易(例如春节、国庆假期),需要将这一周期删除 # 计算周期资金曲线 period_df['每天涨跌幅'] = df['涨跌幅(%)'].resample(period_type).apply(lambda x: list(x))这里输入代码 # 重新设定index period_df.reset_index(inplace=True) period_df['交易日期'] = period_df['周期最后交易日'] # 删除上市的第一个周期 df.drop([0], axis=0, inplace=True) # 删除第一行数据 # 计算下周期每天涨幅 df['下周期每天涨跌幅'] = df['每天涨跌幅'].shift(-1) # print('*'*20) # print(df['下周期每天涨跌幅']) # print('*'*20) # del df['涨跌幅(%)'] # =删除不能交易的周期数 # 删除月末为st状态的周期数 df = df[df['简称'].str.contains('ST') == False] # 删除月末有退市风险的周期数 df = df[df['简称'].str.contains('退') == False] # 删除月末不交易的周期数 df = df[df['是否交易'] == 1] ``` #### 4.数据存储为hdf 为方便将数据传给后续类,进行因子计算和回测,将整理好的数据重新存储为hdf,后续获取数据可直接读取hdf文件。 ``` # 对数据进行排序,重新设定索引,并存入hdf文件 def sort_reindex_store(self,sortby:list): ''' 对数据进行排序,重新设定索引,并存入hdf文件 param: sortby,column组成的数组,排序的关键字 ''' # 将数据存入数据库之前,先排序、reset_index self.all_stock_data.sort_values(sortby, inplace=True) self.all_stock_data.reset_index(inplace=True, drop=True) # 将数据存储到hdf文件 self.all_stock_data.to_hdf(self.hdf_dir + r'/all_stock_data_d'+r'.h5', 'df', format = 'fixed', mode='w') print("pd2hdf5:Completed:" + self.hdf_dir) ``` ### 类2:选股策略类:ImportData类 介绍:包含多个函数,可计算不同因子的IC、IR值,实现单因子选股、基于IC的多因子选股、基于IR的多因子选股以及普通多因子选股。 #### 1.类基本属性 基本属性包括:原始股票数据;选股数量;各选股因子的ic、ir值;根据选股策略筛选后留下的股票池数据 #### 2.单因子策略选股 根据原始股票数据,以及输入的作为选股标准的因子名,对每支股票的单个因子值进行排序,基于持仓数量,保留每一个选股周期应当持有的股票 ``` def select_stock_single(self,factor): #根据输入的因子名,进行单因子选股排序 self.df[f"{factor}排名"] = self.df.groupby('日期')[factor].rank(method='first') #根据选股数量,筛选出每一个选股周期应当持有的股票 self.df = self.df[self.df[f"{factor}排名"]<=self.select_stock_num] ``` #### 3.普通多因子选股策略 根据原始股票数据,以及输入的作为选股标准的因子列表,计算基于普通多因子选股策略下的因子综合得分,并基于持仓数量,保留每一个选股周期应当持有的股票。 ``` def select_multistock_normal(self,factor_list): #获取原始数据中包含的因子值 all_alpha_list=self.df.columns.values.tolist() [self.df.columns.values.tolist().index('alpha001'):self.df.columns.values.tolist().index('交易天数')] drop_list=[] retain_alpha_list=[] #根据传入的参数factor_list,计算出本次选股在原始数据中应当保留的因子以及应该舍弃的因子 for j in all_alpha_list: if j not in factor_list: drop_list.append(j) if j in factor_list: retain_alpha_list.append(j) self.df['overrall_score']=0 #计算普通多因子策略下每支股票的因子综合得分 for k in retain_alpha_list: self.df['overrall_score']=self.df['overrall_score']+self.df[k] #删除原始数据中未用的因子 self.df.drop(drop_list,axis=1,inplace=True) #计算每支股票的因子综合得分排名 self.df[f"overrall_score排名"] = self.df.groupby('日期')['overrall_score'].rank(method='first') #选出应当持仓的股票 self.df = self.df[self.df["overrall_score排名"]<=self.select_stock_num] self.df.set_index('代码', drop=False, inplace=True) #删除因子值为空的数据、删除不需要的数据 self.df.dropna(subset=retain_alpha_list,how='any', inplace=True) self.df.drop(['overrall_score排名','overrall_score'],axis=1,inplace=True) ``` #### 4.计算因子IC、IR值 根据原始股票数据,以及每支股票的因子值数据,计算出每个因子的IC、IR值并保存。 ``` def get_ic_and_ir(self,factor_list): #获取原始数据中包含的因子值 all_alpha_list=self.df.columns.values.tolist()[self.df.columns.values.tolist().index('alpha001'):self.df.columns.values.tolist().index('交易天数')] #根据传入的参数factor_list,计算出本次选股在原始数据中应当保留的因子以及应该舍弃的因子 drop_list=[] retain_alpha_list=[] for j in all_alpha_list: if j not in factor_list: drop_list.append(j) if j in factor_list: retain_alpha_list.append(j) self.df.dropna(subset=retain_alpha_list,how='any', inplace=True) #计算因子的IC、IR值 for i in factor_list: self.df[f'{i}的排名比_icir'] = self.df.groupby('日期')[i].rank(pct=True,method='first') self.df[f'{i}的排名_icir'] = self.df.groupby('日期')[i].rank(method='first') if self.df['下日_开盘买入涨跌幅'].dtypes!=list: self.df['下日_开盘买入涨跌幅'] = self.df['下日_开盘买入涨跌幅'].apply(lambda x: [x]) self.df['下周期每天涨跌幅'] = self.df['下周期每天涨跌幅'].apply(lambda x: x[1:]) self.df['下周期每天涨跌幅'] = self.df['下日_开盘买入涨跌幅'] + self.df['下周期每天涨跌幅'] self.df['下周期涨跌幅'] = self.df['下周期每天涨跌幅'].apply(lambda x: np.cumprod(np.array(list(x))+1)[-1]-1) self.df['下周期涨跌幅排名']=self.df.groupby('日期')['下周期涨跌幅'].rank(method='first') # print(self.df) # exit() ic1=[] for (k1,k2) in self.df.groupby('日期')['下周期涨跌幅排名',f'{i}的排名_icir']: ic=np.corrcoef(k2['下周期涨跌幅排名'],k2[f'{i}的排名_icir'])[0,1] # if np.isnan(ic): # pass # else: ic1.append(ic) ir=np.average(ic1)/np.std(ic1) ic=round(np.average(ic1),5) ir=np.round(ir,5) if ir>0: self.df[f'{i}的排名_icir'] = self.df.groupby('日期')[f'{i}'].rank(ascending=False,method='first') self.ic_ir_dic[f'{i}_ic']=ic self.ic_ir_dic[f'{i}_ir']=ir #输出因子的IC、IR值 for key,value in self.ic_ir_dic.items(): print(f'{key}={value}') #删除不需要的列 for i in factor_list: self.df.drop([f'{i}的排名比_icir',f'{i}的排名_icir'],axis=1,inplace=True) ``` #### 5.基于IC/IR的多因子选股策略 根据原始股票数据,以及输入的作为选股标准的因子列表,计算基于IC或IR下的多因子策略的因子综合得分,并基于持仓数量,保留每一个选股周期应当持有的股票。 ``` def select_multistock_ic_ir(self,factor_list,icorir): #获取原始数据中包含的因子值 all_alpha_list=self.df.columns.values.tolist()[self.df.columns.values.tolist().index('alpha001'):self.df.columns.values.tolist().index('交易天数')] #根据传入的参数factor_list,计算出本次选股在原始数据中应当保留的因子以及应该舍弃的因子 drop_list=[] retain_alpha_list=[] for j in all_alpha_list: if j not in factor_list: drop_list.append(j) if j in factor_list: retain_alpha_list.append(j) self.df.drop(drop_list,axis=1,inplace=True) self.df['overrall_score']=0 #根据传入参数,决定是基于IC展开多因子策略还是基于IR展开多因子策略 if icorir=='ic': for k in retain_alpha_list: self.df['overrall_score']=self.df['overrall_score']+self.df[k]*self.ic_ir_dic[f'{k}_ic'] if icorir=='ir': for k in retain_alpha_list: self.df['overrall_score']=self.df['overrall_score']+self.df[k]*self.ic_ir_dic[f'{k}_ir'] #计算不同策略下的因子综合得分 self.df[f"overrall_score排名"] = self.df.groupby('日期')['overrall_score'].rank(method='first') #筛选股票 self.df = self.df[self.df["overrall_score排名"]<=self.select_stock_num] self.df.set_index('代码', drop=False, inplace=True) #删除不需要的行和列 self.df.dropna(subset=retain_alpha_list,how='any', inplace=True) # self.df.drop(['overrall_score排名','overrall_score'],axis=1,inplace=True) ``` ### 类3:BackTest类 介绍:回测类,利用当前的策略进行模拟回测。在历史数据中进行模拟交易,得到最终的收益率,夏普比率,最大回撤以及同时期上证指数的收益率。 - 类基本属性包括:整理后的文件路径,上证指数的文件路径,买卖手续费费率,印花税。 - 方法 cal_return(self):以一周为换股周期,每个周期选择出3只股票进行买入与卖出。通过对这三只股票的收益率进行连乘的操作,算出这个周期的 最终收益率,并进行扣税操作,算出该周期后的本金变化,最终算出从19年1月1日至21年1月1日的收益变化。应用相同的计算方法得到了同时期上证指数的收益率。 选股最终收益率 -0.33683016538446964 上证指数最终收益率 0.3106549770344962 - 方法cal_sharpe(self):根据cal_return方法计算出的各个时期的收益率,算出两年的平均收益减去无风险利率的差除以该时期的标准差,从而计算出夏普比率的大小。 夏普比率是 -0.02362457321795516 - drawplot(self):可视化的绘制。画出了上证以及我们的选股策略在这两年的收益率变化曲线。 ![输入图片说明](%E6%94%B6%E7%9B%8A%E7%8E%87%E6%9B%B2%E7%BA%BF%E5%9B%BE.png) 其中橘线是上证指数的收益率曲线,蓝线是我们的收益率曲线。 - maxdrawdown(self):maxdrawdown函数式用于计算股票的最大回撤,利用收益率的变化曲线数据,计算各个时期开始的收益率报到之后时期最低点的收益率报的百分比变化的最大变动,从而计算出最大回撤。 最大回撤率是指在选定周期内任一历史时点往后推,产品净值走到最低点时的收益率回撤幅度的最大值。 最大回撤: 0.4983222968382417 ## 策略说明 Alpha101介绍 ### 1、金融含义 WorldQuant根据数据挖掘的方法发掘了101个alpha,据说里面80%的因子仍然还行之有效并运行在他们的投资策略中。Alpha101给出的公式,也就是计算机代码101年真实的定量交易Alpha。他们的平均持有期大约范围0.6 - 6.4天。平均两两这些Alpha的相关性较低,为15.9%。回报是与波动强相关,但对换手率没有明显的依赖性,直接确认较早的间接经验分析结果。我们从经验上进一步发现换手率对alpha相关性的解释能力很差。 ### 2、算法说明 #### Alpha001因子: 该因子的总体思路为,找出每只股票前 5 天的记录值(前 20 天的标准差或收盘价,取决于收益率的负号)的最大值的交易日期离当前日期的间隔天数作为其权重,然后对每只股票的权重进行排序,最后返回股票对应排名的 boolean 值(排名所占总位数的百分比)减去 0.5 作为因子 alpha001 的值。 ''' rank(ts_argmax(inner ** 2, 5)) ''' #### Alpha002因子: 此策略属于量价背离策略,利用相关系数的数学方法来研究量价之间的关系。首先,在量方面:先对成交量取对数来达到稳定数据的作用,而后利用当天的成交量和过去第 2 天的值进行差分运算,得到量方面的增量程度;另一方面,在价方面:直接求当天的价格变化率来达到价方面的增量程度。接下来,对量价方面分别对每只股票进行排序操作,最后求他们过去 6 天以来的相关系数的。alpha 为量价之间的相关系数取反后的值,从而达到量价背离的目的。 ''' -1 * correlation(rank(delta(log(epsilon+self.volume), 2)), rank((self.close - self.open) / self.open), 6) ''' #### Alpha003因子: 该策略对交易量和开盘价两个指标的排序结果计算近 10 日的相关系数,所以该因子评估的是短期开盘价与成交量的相关性高低,最后加上负号则将相关性进行了颠倒处理,为的是构建投资组合时买入开盘价和成交量相关性低的股票,内在逻辑是做多开盘价与成交量产生背离的股票。 ''' -1 * correlation(rank(self.open), rank(self.volume), 10) ''' #### Alpha004因子: 该策略计算过去 9 天最低价排名的情况,最后加上负号则将相关性进行了颠倒处理,内在逻辑是做多最低价较低的股票,认为低价股票具有反弹潜力。 ''' -1 * ts_rank(rank(self.low), 9) ''' #### Alpha005因子: Alpha005 因子的逻辑是,先判断过去 5 天是否有负的差价,如果全部都是正的,则返回 t-1 的 close 差价。如果有负的,则判断是否全部为负,如果全部为负,则返回 t-1 的 close差价,否则返回 t-1 的 close 差价的负值。潜在意义是:如果过去 5 天价格单调上升,如果t-1 上升的越多,就越可以投资。如果过去 5 天价格不断下降,则 t-1 下降的越多越不可以投资。如果过去 5 天,价格有升有降,则 t-1 的时候如果下降的越多,越可以投资。 ''' (rank((self.open - (sum(self.vwap, 10) / 10))) * (-1 * abs(rank((self.close - self.vwap))))) ''' #### Alpha006因子: 该因子的公式较为简单,逻辑清晰,即先计算出近十日开盘价序列和成交量序列之间的相关系数,若相关系数大,则说明近十日呈现出开盘价越高、成交量越大的“同涨同跌”趋势。由于最后添加了一个-1,因此该因子倾向于买入开盘价高、成交量低的“量价背离”型股票。 ''' -1 * correlation(self.open, self.volume, 10) ''' 总而言之,本系统有划分清晰的类,每个类中有定义明确的字段和方法,方法中对于非法或异常情况有相应处理。通过类的组合实现了全部的基础功能。代码有关键信息的注释,变量命名合理,程序冗余代码少,可读性高,可扩展性强。