158-探寻优质基本面高股息策略:从传统策略误区出发
由qxiao创建,最终由qxiao 被浏览 18 用户
1. 引言
在投资领域,不少投资者热衷于追逐热门概念,期望短期内获取高额收益。其中,追涨热门题材股策略曾备受追捧。以过去几年的新能源汽车概念为例,在行业利好消息频出时,相关股票价格大幅飙升。众多投资者被短期的股价上涨所吸引,纷纷重仓买入。然而,市场风云变幻,当行业热度稍有减退,这些热门题材股的股价便如坐过山车般急剧下跌。从回测曲线来看,以 2021 年 1 月 4 日为起始点,对每日调仓购买概念涨幅最大的策略进行跟踪。在初期,策略一定涨幅,策略收益涨幅也接近 10% 。然而,随着时间推移,策略收益震荡下行。到 2022 年底,策略收益低至 - 35% 左右。从前期相对高点计算,策略收益最大回撤超 45% 。
==下图为每日调仓购买概念涨幅最大的策略的回测==
这种追涨热门题材股策略之所以失败,主要是因为投资者过度关注短期市场热点,忽视了股票的基本面和内在价值。当市场热度消散,缺乏基本面支撑的股票价格自然难以维持高位。
那么,有没有一种更加稳健的投资策略,能够在复杂多变的市场环境中持续创造收益呢?答案是肯定的,==优质基本面高股息策略==便是其中之一。
2.股息与股价的内在逻辑
股价变动背后有着清晰的逻辑。以戴维斯理论来看
这意味着,股价上涨、投资获利有两条途径:
一是提升每股收益,即企业业绩增长,当企业净利润大幅增加,每股收益提高,在市盈率不变时,股价会上涨,投资者赚取企业业绩增长的钱;
二是增大市盈率,即提升估值,投资者对股票价值的看法会影响估值,若每股收益 1 元,20 倍估值时股价 20 元,30 倍估值时股价 30 元 。当业绩和估值同时增长,便会出现戴维斯双击。但在当前大盘行情下,成交量萎缩,资金增量不足,总体流动性溢价难以实现,此时有业绩支撑、基本面良好的股票更具抗跌性\n
不过,每股收益有时并非真金白银,可能存在应收账款和高价库存,面临坏账风险。
真正能落到投资者手中的现金,是现金股利 / 分红。
因此,股利贴现模型(Dividend Discount Model,DDM)常被用于衡量股票内在价值,该模型将股票内在价值表达为未来股利的折现值之和。近年来,有两大事件促使更多投资者青睐高股息高分红股票。
\n
一是新 “国九条” 的出台。去年国务院印发《关于加强监管防范风险推动资本市场高质量发展的若干意见》,着重强调对上市公司现金分红的监管,对多年未分红或分红比例偏低的公司限制大股东减持、实施风险警示,同时加大对分红优质公司的激励力度。沪深交易所也积极跟进,在《股票上市规则》中加入对分红不达标公司的强约束措施,主板和科创板、创业板分别对分红金额做出明确规定。
二是 “资产荒” 的出现。随着一些行业的调整,曾经稳定的高息理财逐渐减少,一年期大额存单利率降至 2% 以下,消费贷利率也跌到 3% 以下。相比之下,股息率较高的股票显得更具吸引力。而且,愿意慷慨分红的公司通常拥有充沛的现金流,长期稳定高分红的公司,往往具备更强的创造正向现金流的盈利能力,净利润增速也相对更高。
3.优质基本面高股息策略剖析
基于上述背景,可以构建优质基本面高股息策略。单纯依赖股息率这一因子,策略效果并不理想,还需加入其他基本面条件辅助。首先,不能仅看当期股息率,对于周期性行业,景气度高时股息率高,但此时买入容易被套,因此要看近 N 年的分红 / 股息率;其次,估值要合理,可以借助市盈率 PE 和市净率 PB 等估值指标进行筛选,参考如茅台等优质企业的估值指标;最后,公司要具有一定成长性,至少不能负增长,以维持各方面稳定。在筛选出的股票池中购买股息率最高的前 N 支股票。
策略回测数据亮眼
从 2013 年初至今进行回测,每个交易日换仓。选股时剔除 ST 股、停牌股、涨跌停股等,默认只能看到前一天的基本面数据。每次选取 5支股票,开盘交易,考虑正常交易费率和滑点。12 年多的时间里,累计收益率==高达 1237.11%==,年化收益 24.57%,夏普率为 0.9。虽因是纯多头策略,最大回撤达到 33.42%,但持仓的都是基本面优秀的股票,投资者更有底气。在大部分时间里,该策略都能跑赢沪深 300 指数,近 12 年来每年都保持正收益,除 2020 年外,且均跑赢沪深 300 指数。
4.代码详细解析
下面将对这段量化交易代码进行全方位的详细介绍,涵盖代码功能概述、各部分代码解析、指标计算深入解读、代码整体运行逻辑等内容。
4.1 代码功能概述
此代码为基于 bigquant
平台的量化交易策略,主要用于筛选中国A股市场中符合特定基本面条件的股票,并依据每日调仓策略开展交易。其核心目标是通过构建一个优质基本面高股息的股票投资组合,实现长期稳定的投资收益。
4.2 代码详细解析
4.2.1 导入必要的库
from bigquant import bigtrader, dai
import pandas as pd
bigtrader
和dai
是bigquant
平台提供的量化交易与数据查询模块。pandas
是强大的数据处理库,用于数据的读取、处理和分析。
4.2.2 初始化函数 initialize
def initialize(context: bigtrader.IContext):
context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
context.logger.info("开始计算选股数据...")
context.holding_nums = 5
context.counter = 0
sql = """
select
date,
instrument,
-- 稳定性指标
m_avg(dividend_yield_ratio,200*3) as dividend_yield_ratio_mean_5Y,
-- 估值合理性指标
-- 风险控制
-- 成长性指标
m_lag(net_profit_to_parent_shareholders_ttm_yoy, 0) > 0.06 AS _np_growth_0,
m_lag(net_profit_to_parent_shareholders_ttm_yoy, 12*1) > 0.06 AS _np_growth_1,
m_lag(net_profit_to_parent_shareholders_ttm_yoy, 12*2) > 0.06 AS _np_growth_2,
if(list_bool_and([_np_growth_0,_np_growth_1,_np_growth_2]) , 1 , 0 ) as continuing_operation_net_profit_ttm_yoy_3Y_IF,
AVG(pe_ttm) OVER (PARTITION BY cs_level2) AS avg_pe_ttm_by_industry,
AVG(pcf_net_leading) OVER (PARTITION BY cs_level2) AS avg_pcf_net_leading_by_industry,
AVG(debt_to_asset_lf) OVER (PARTITION BY cs_level2) AS avg_debt_to_asset_lf_industry,
FROM cn_stock_prefactors
QUALIFY
-- COLUMNS(*) IS NOT NULL
-- PB小于1.5 倍
pb < 1.5
AND fcff / total_market_cap > 0.03
-- 连续增长3年
AND continuing_operation_net_profit_ttm_yoy_3Y_IF == 1
-- PE-TTM 小于行业平均
AND pe_ttm < avg_pe_ttm_by_industry
-- 市现率PCF小于行业平均
AND pcf_net_leading < avg_pcf_net_leading_by_industry
-- 资产负债率 小于行业平均
AND debt_to_asset_lf < avg_debt_to_asset_lf_industry
-- 上市五年
AND list_days > 225*5
ORDER BY date,instrument,dividend_yield_ratio_mean_5Y DESC
"""
# 查询数据
df = dai.query(sql, filters={"date": [context.add_trading_days(context.start_date, -1500), context.end_date]}).df()
context.logger.info(f"数据计算完成,共 {len(df)} 条记录")
context.data = df
- 设置交易佣金:借助
context.set_commission
设定交易佣金,买入成本为 0.03%,卖出成本为 0.13%,最低成本为 5 元。 - 初始化变量:
context.holding_nums = 5
:设定持仓股票数量为 5 只。context.counter = 0
:初始化计数器,用于控制调仓频率。
- 编写 SQL 查询语句:从
cn_stock_prefactors
表筛选符合条件的股票数据,具体筛选条件如下:- 稳定性指标:
m_avg(dividend_yield_ratio,200*3) as dividend_yield_ratio_mean_5Y
:计算过去约 5 年的平均股息率,以此评估公司分红稳定性。
- 成长性指标:
m_lag(net_profit_to_parent_shareholders_ttm_yoy, n)
函数获取不同时期的净利润同比增长率,要求连续 3 年增长率大于 6%。continuing_operation_net_profit_ttm_yoy_3Y_IF
用于标记是否满足连续 3 年增长条件。
- 估值合理性指标:
AVG(pe_ttm) OVER (PARTITION BY cs_level2) AS avg_pe_ttm_by_industry
:按行业计算 PE - TTM 平均值,筛选出 PE - TTM 低于行业平均的股票。AVG(pcf_net_leading) OVER (PARTITION BY cs_level2) AS avg_pcf_net_leading_by_industry
:按行业计算市现率平均值,筛选出市现率低于行业平均的股票。
- 风险控制指标:
AVG(debt_to_asset_lf) OVER (PARTITION BY cs_level2) AS avg_debt_to_asset_lf_industry
:按行业计算资产负债率平均值,筛选出资产负债率低于行业平均的股票。
- 其他条件:
pb < 1.5
:市净率小于 1.5 倍。fcff / total_market_cap > 0.03
:自由现金流与总市值比率大于 3%。list_days > 225*5
:上市时间大于 5 年。
- 稳定性指标:
- 查询数据:利用
dai.query
执行 SQL 查询,将结果存储在df
中,并将数据存入context.data
。
4.2.3 数据处理函数 handle_data
def handle_data(context: bigtrader.IContext, data: bigtrader.IBarData):
context.counter += 1
#获取当前日期
current_date = data.current_dt.strftime("%Y-%m-%d")
# 获取当日数据
current_day_data = context.data[context.data["date"] == current_date]
instrument_list = current_day_data.sort_values(["dividend_yield_ratio_mean_5Y"], ascending=[False])["instrument"].values[:context.holding_nums]
context.target_weights = {ins: 1/len(instrument_list) for ins in instrument_list}
if len(current_day_data) == 0:
return
if context.counter >= 1:
context.counter = 0
# 获取当前已持有股票
current_hold_instruments = set(context.get_account_positions().keys())
for ins in current_hold_instruments:
# 若长线大于短线且有持仓
if ins not in instrument_list:
context.order_target(ins, 0)
for ins in instrument_list:
# 若短线大于长线且没有持仓
if ins not in current_hold_instruments:
context.order_target_percent(ins, context.target_weights[ins])
- 更新计数器:每次调用该函数,计数器加 1。
- 获取当前日期和当日数据:
current_date = data.current_dt.strftime("%Y-%m-%d")
:获取当前日期。current_day_data = context.data[context.data["date"] == current_date]
:从context.data
筛选出当日数据。
- 筛选股票并设置目标权重:
instrument_list = current_day_data.sort_values(["dividend_yield_ratio_mean_5Y"], ascending=[False])["instrument"].values[:context.holding_nums]
:按 5 年平均股息率降序排序,选取前 5 只股票。context.target_weights = {ins: 1/len(instrument_list) for ins in instrument_list}
:为每只股票设置相同目标权重。
- 调仓操作:
- 若计数器大于等于 1,进行调仓操作。
- 对于当前持仓股票,若不在新股票列表中,则卖出。
- 对于新股票列表中的股票,若不在当前持仓中,则按目标权重买入。
4.2.4 运行策略并展示结果
performance = bigtrader.run(
market=bigtrader.Market.CN_STOCK,
frequency=bigtrader.Frequency.DAILY,
start_date="2013-01-01",
end_date="2025-04-05",
capital_base=1000000,
initialize=initialize,
handle_data=handle_data,
)
performance.render()
bigtrader.run
函数运行量化交易策略,设置市场为中国股票市场,交易频率为每日,起始日期为 2013 年 1 月 1 日,结束日期为 2025 年 4 月 5 日,初始资金为 100 万元,初始化函数为initialize
,数据处理函数为handle_data
。performance.render()
展示策略运行结果。
4.2.5 代码整体运行逻辑
- 初始化阶段:设定交易佣金、初始化变量,通过 SQL 查询筛选符合条件的股票数据并存储。
- 每日数据处理阶段:获取当前日期数据,筛选出符合条件且股息率较高的 5 只股票,设置目标权重,根据计数器判断是否调仓,对持仓进行调整。
- 策略运行与结果展示阶段:按设定的参数运行策略,最后展示策略运行结果。
\
5.代码
https://bigquant.com/codesharev3/ca0ad3dc-45fc-4ed1-bc0f-1dff17852ab4