Python量化交易学习——7:增强版中证红利策略
中证红利指数用于反映A股市场中高股息股票的整体运行状态与价格变动趋势。该指数选取了来自上海证券交易所与深圳证券交易所的100只股票作为样本,这些股票具备较高的现金股息率、稳定的分红记录以及良好的规模和流动性。其设立的目标在于为市场参与者提供一个具有广泛代表性的观察窗口,用以评估A股市场内高股息股票的表现情况。在样本选择方面,该指数遵循一系列严格的标准,包括过去两年持续进行现金分红、税后现金股息率每年均超过零,并且在过去一年中日均流通市值位列前80%,同时综合考量了流动性等其他因素。该指数的发布意在为投资者提供一种专注于高股息率股票的投资工具,同时也体现了市场对稳定分红及高股息率股票的需求。
中证红利指数具备较强的防御性特征,但成长性相对较弱。由于所涵盖的成分股主要来自发展较为成熟的行业,在市场整体表现疲软时,这些股票通常能够维持较为稳定的盈利水平,从而使得中证红利指数在熊市期间展现出较强的抗跌能力。然而,在牛市阶段,由于缺乏显著的成长潜力,该指数的表现可能不及一些具有更高成长性的指数。
此外,中证红利指数的成分股覆盖多个行业领域,例如房地产、钢铁、交通运输以及银行业等。随着市场环境的变化和选股规则的调整,不同行业的配置比例也会随之发生相应变动。因此,该指数不仅反映了高股息率股票的整体走势,也间接体现了对特定行业趋势的跟踪。
中证红利指数的基日设定为2024年12月31日,基准点数为1000点,并分别在上海证券交易所和深圳证券交易所使用代码000922与399922进行标识。该指数于2005年5月26日正式对外发布。
基于中证红利的指数增强基金策略的构建
针对不同指数的特性,应制定对应的增强型策略。从前文分析可知,中证红利指数选取的是沪深两市中现金股息率较高、分红稳定、具备一定规模及流动性的100只股票,其主要特征包括:稳定性突出、业绩表现良好且具备一定规模。然而,对于个股的成长性并未提出特殊要求。
因此,我们还可以借助以下指标来构建中证红利指数增强基金:
股息率:股息率(Dividend Yield Ratio)指的是年度总派息额与当前市价之间的比例,通常以股票最后销售价格的百分比形式表示。该指标可视为投资收益率的一种简化表达方式。具体而言,股息率是股息与股票价格之间的比率。在实际投资过程中,股息率是评估企业是否具备投资价值的重要参考标准之一。
市净率:市净率(Price-to-Book Ratio,简称P/B PBR)是指每股股价与每股净资产的比率。在股票投资分析中,市净率较低的股票通常具有较高的投资价值;反之,则可能价值较低。不过,在判断具体的投资价值时还需综合考虑当时的市场环境以及公司的经营状况、盈利能力等多方面因素。
市盈率:市盈率(Price Earnings Ratio,简称P/E或PER),又称为“本益比”、“股价收益比率”或“市价盈利比率”。该指标是通过将股票价格除以每股收益(EPS)得出的比率;也可以通过公司市值除以年度股东应占溢利进行计算。在计算过程中,股价一般采用最新收盘价;而EPS则分为两种情况:若使用已公布的上年度数据,则称为历史市盈率(historical P/E);若采用对未来EPS的预估值,则称为预估市盈率(forward P/E),这种预估值通常基于市场平均预测值(consensus estimates),即由追踪公司业绩的相关机构汇总多位分析师预测结果后得出的平均或中位数值。至于何为合理的市盈率,并没有统一的标准。
贝塔值:反映个股与大盘波动的相关程度;
自相关波动率:体现个股自身的波动性;
构建策略:
依据股息率、市盈率、市净率、贝塔值以及波动性等因素对中证红利指数成分股进行等权重排序(在此策略中,我们倾向于选择股息较高、同时具备较低的市盈率/市净率/贝塔值/波动性的标的股票作为优选对象,在因子权重设计上设定为:股息率为1,其余各项权重均为-1)。随后从排序结果中挑选排名前五的个股进行买入操作,并每月重复此过程一次以观察最终的投资收益表现。
新建一个Python文件作为用于实现中证红利增强型策略的功能模块文件,其主要功能是按照上述条件获取排序后的股票清单。此处我们将该文件命名为:bouns_enhance.py 代码如下:
import numpy as np
import pandas as pd
from gm.api import *
import datetime
import statsmodels.api as sm
from dateutil.relativedelta import relativedelta
from sklearn.preprocessing import MinMaxScaler
"""
根据股息率/市盈率/市净率/beta值/波动率等权重合成因子,对中证红利指数的成分股进行排序
"""
def bouns_enhance(now,index = "SHSE.000922"):
last_day = get_previous_trading_date("SHSE",now)
symbol_dir = stk_get_index_constituents(index) # 获取成分股数据
symbol_list = symbol_dir["symbol"].values.tolist()
DY_PE_PB = stk_get_daily_valuation_pt(symbols=symbol_list, fields="dy_lfy,pe_ttm,pb_lyr", trade_date=last_day, df=True) # 获取股息率/市盈率/市净率
DY_PE_PB = DY_PE_PB.dropna() # dataframe中已包含了股息率/市盈率/市净率数据
symbol_list = []
for symbol in DY_PE_PB["symbol"].values : #提取剔除无效数据后的股票序列
symbol_list.append(symbol)
beta_list = []
volatility_list = []
for symbol in symbol_list:
beta = get_beta_weight(symbol,now,count=20) #获取波动率数据
beta_list.append(beta)
volatility = get_volatility_normal(symbol,now,count=20)
volatility_list.append(volatility)
DY_PE_PB["BETA"] = beta_list
DY_PE_PB["volatility"] = volatility_list
#计算各个因子的相关系数,以便于设置权重,这个后续在写,目前按照等权重预估
# 对各个因子进行加权处理
factor_matrix = DY_PE_PB.iloc[:,2:] #提取因子
factor_matrix = np.asmatrix(factor_matrix)
weight_matrix = np.asmatrix([[1],[-1],[-1],[-1],[-1]]) #设置各个因子权重
res = np.dot(factor_matrix,weight_matrix) #对因子和权重进行点乘
DY_PE_PB["SCORE"] = res
DY_PE_PB = DY_PE_PB.sort_values(["SCORE"],ascending=False) #根据计算结果进行从大到小排序
symbol_list = DY_PE_PB['symbol'].values
return symbol_list
"""
beta系数:与大盘联动的波动率;
一般用单个股票的历史收益率对同期大盘指数收益率进行回归,回归系数就是BETA系数
"""
def get_beta_weight(symbol,now,count,market_index="SHSE.000001"):
last_day = get_previous_trading_date("SHSE",now)
market_data = history_n(symbol=market_index, frequency="1d", count=count, adjust=ADJUST_PREV,end_time=last_day, fields="high,low,close",df=True)
market_close = market_data["close"].values.tolist()
market_close_ratio = []
for i in range(1,len(market_close)):
ratio = (market_close[i]-market_close[i-1])/market_close[i-1]#获取大盘每天的收益率
market_close_ratio.append(ratio)
symbol_data = history_n(symbol=symbol, frequency="1d", count=count, adjust=ADJUST_PREV,end_time=last_day, fields="high,low,close",df=True)
symbol_close= symbol_data["close"].values.tolist()
symbol_close_ratio = []
for i in range(1,len(symbol_close)):
ratio = (symbol_close[i]-symbol_close[i-1])/symbol_close[i-1] #获取目标股票每天的收益率
symbol_close_ratio.append(ratio)
# 有可能数据有缺失,因此需要判断是否相等,对于缺失数据,波动率设置为无限大的值
if len(market_close_ratio) == len(symbol_close_ratio):
market_close_ratio = sm.add_constant(market_close_ratio) # 因为OLS回归中不包括常数项,因此在数据左侧增加上常数项
model = sm.OLS(symbol_close_ratio, market_close_ratio) # 做线上拟合
results = model.fit() # 然后获取拟合结果
beta_weight = results.params[1] # 拟合公式为kx+b的形式,提取拟合后的k值
else:
beta_weight = 100
return beta_weight
"""
自相关波动率:自身的波动率;
"""
def get_volatility_normal(symbol,now,count):
last_day = get_previous_trading_date("SHSE",now)
symbol_data = history_n(symbol=symbol, frequency="1d", count=count, adjust=ADJUST_PREV,end_time=last_day, fields="high,low,close",df=True)
symbol_data_close = symbol_data["close"].values.tolist()
res = np.var(symbol_data_close)/np.mean(symbol_data_close) #var() 函数通常指的是 numpy 库中的 numpy.var() 函数,它用来计算给定数组(或一维数组的某轴向)的样本方差或者总体方差,取决于提供的参数。
res = np.sqrt(res)
return res
"""
测试代码;
"""
set_token("自己的token码")
index = "SHSE.000922"
now = datetime.datetime.now()
symbol_list = bouns_enhance(now,index)
print(symbol_list)
# beta = get_beta_weight("SHSE.600501",now,count=20)
# print(beta)
随后,我们通过主函数调用前述函数,开展回测工作。在此过程中,我们创建了一个新的Python文件,文件名称为bouns_enhance_main.py,具体代码内容如下:
# coding=utf-8
from __future__ import print_function, absolute_import
import os
import numpy as np
import pandas as pd
from gm.api import *
import datetime
import statsmodels.api as sm
from dateutil.relativedelta import relativedelta
from sklearn.preprocessing import MinMaxScaler
import bouns_enhance as be
def init(context):
# 在init函数中设置全局变量
# algo执行定时任务函数,只能传context参数
# index股票池代码
# num买卖股票数据,暂定5只
# schedule在指定时间自动执行策略算法, 通常用于选股类型策略,每月一次,早上9点31分执行定时任务
# date_rule执行频率,目前暂时支持1d、1w、1m,其中1w、1m仅用于回测,实时模式1d以上的频率,需要在algo判断日期
# time_rule执行时间, 注意多个定时任务设置同一个时间点,前面的定时任务会被后面的覆盖
context.index = "SHSE.000922"
context.num = 5
schedule(schedule_func=algo, date_rule='1m', time_rule='09:31:00') #设置回测方式,定时任务
def algo(context):
now = context.now #获取当前时间
day_time, hour_and_mins = str(now.strftime('%Y-%m-%d %H:%M:%S')).split(" ") # Python time.strftime() 函数用于格式化时间,返回以可读字符串表示的当地时间;详细用法见 https://www.runoob.com/python/att-time-strftime.html
order_close_all() #这里为例简化,全部清仓上一阶段股票
target_list = be.bouns_enhance(now,index=context.index) #获得排序后的股票代码清单
target_symbol = target_list[:context.num] #取列表中前5只作为标的股票
for symbol in target_symbol:
order_target_percent(symbol=symbol,percent=1/context.num,order_type=OrderType_Market,position_side=PositionSide_Long) #执行买入操作
print("买入",symbol,now)
# 查看最终的回测结果
def on_backtest_finished(context, indicator):
print(indicator)
if __name__ == '__main__':
'''
strategy_id策略ID, 由系统生成
filename文件名, 请与本文件名保持一致
mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID, 可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
backtest_match_mode市价撮合模式,以下一tick/bar开盘价撮合:0,以当前tick/bar收盘价撮合:1
'''
run(strategy_id='自己的策略ID',
filename='bouns_enhance_main.py',
mode=MODE_BACKTEST,
token='自己的token码',
backtest_start_time='2021-01-01 09:00:00',
backtest_end_time='2024-05-31 15:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=100000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001,
backtest_match_mode=1)
本次回测所选取的期间为自2021年1月1日起至2024年5月31日止,相关测试所得结果如下:

通过回测数据可以发现,当前该增强型策略相较中证红利指数所获得的超额收益为39.25%,这一数值尚未达到预期的理想水平。因此,后续可通过调整不同因子所占权重的方式,进一步对增强型策略进行优化。
此外,针对2020年1月1日至2024年5月31日这一时间段的数据测算结果显示,所得结论与此前存在明显差异:

超额收益水平攀升至101.4%,从折线图的趋势可见,2020年7月份的超额收益表现尤为突出,对整体收益水平起到了显著的拉升作用。在这一时期,买入的股票包括:
买入 SHSE.600188 2020-07-01 09:31:00+08:00 #兖矿能源,涨幅达137%
买入 SHSE.601216 2020-07-01 09:31:00+08:00 #君正集团,涨幅达312%
买入 SZSE.002128 2020-07-01 09:31:00+08:00 #电投能源,涨幅达69.84%
买入 SHSE.600971 2020-07-01 09:31:00+08:00 #恒源煤电,涨幅达33%
买入 SHSE.655325 2555-55-55 55:55:55+88:88 #华发股份,涨幅达8%
这些个股的大幅上涨推动了整体收益的增长。然而,这一现象并不能直接证明所采用的策略具有有效性。因此,在未来评估策略成效时,仍需选取多个样本以及不同时间段进行测算分析,方能获得更为可靠和准确的结论。
