BigQuant使用文档

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
  • bigtraderdaibigquant 平台提供的量化交易与数据查询模块。
  • 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 代码整体运行逻辑

  1. 初始化阶段:设定交易佣金、初始化变量,通过 SQL 查询筛选符合条件的股票数据并存储。
  2. 每日数据处理阶段:获取当前日期数据,筛选出符合条件且股息率较高的 5 只股票,设置目标权重,根据计数器判断是否调仓,对持仓进行调整。
  3. 策略运行与结果展示阶段:按设定的参数运行策略,最后展示策略运行结果。


\

5.代码

https://bigquant.com/codesharev3/ca0ad3dc-45fc-4ed1-bc0f-1dff17852ab4

标签

投资策略基本面分析
{link}