← 返回《初阶量化交易:策略编写及系统搭建》
📑 查看全课大纲(第 23 / 38 节)

实现低PE策略的回测(1)

约 17 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

实现低PE策略的回测(一):择时信号注入与撮合框架

小象实战讲义 · 初阶量化交易:策略编写及系统搭建 本讲义仅用于编程与量化方法教学,不构成任何投资建议。

在完成了低 PE 股票池动态筛选后,策略开发的核心瓶颈在于:仅有选股(选什么)无法解决何时建仓与离场(择时 Timing)的问题。即便选入估值极低的好公司,若恰逢大盘系统性下跌或处于左侧阴跌通道,盲目全仓买入仍会遭受严重的账面浮亏。将第二章开发的技术分析择时信号注入股票池,是构建完备量化策略的标准范式。

本讲系统介绍如何在动态低 PE 股票池基础上,叠加 10 日均线(MA10)趋势择时信号;严密设计事件驱动回测引擎的盘前除权调整、开盘撮合成交与盘后结算时序;并详细拆解多标的买入/卖出清单的生成逻辑。


💡 核心导读

  • 低 PE 选股 + 10 日均线择时复合策略规则
    • 选股域(Universe):当期 7 日调仓低 PE 股票池(0<PE<300 < PE < 30 升序前 100 只);
    • 买入触发条件(Entry Signal)
      • 标的处于当期低 PE 股票池中;
      • 且该标的当日收盘价上穿 10 日均线(Closet1MA10t1Close_{t-1} \le MA10_{t-1}Closet>MA10tClose_t > MA10_t
      • 执行时点:次日(t+1t+1)开盘集合竞价以 Open 价格买入建仓;
    • 卖出离场条件(Exit Signal,满足任一即离场)
      1. 技术破位离场:持仓股当日收盘价下穿 10 日均线(Closet1MA10t1Close_{t-1} \ge MA10_{t-1}Closet<MA10tClose_t < MA10_t
      2. 基本面调出离场:持仓股在定期调仓日跌出当期低 PE 股票池前 100 名
      • 执行时点:次日(t+1t+1)开盘集合竞价以 Open 价格清仓卖出。
  • 事件驱动回测引擎的三段式日度时序模型09:15-09:25 盘前除权调整09:30 开盘撮合 (先卖后买)15:00 盘后资产结算与次日信号生成\text{09:15-09:25 盘前除权调整} \xrightarrow{\quad} \text{09:30 开盘撮合 (先卖后买)} \xrightarrow{\quad} \text{15:00 盘后资产结算与次日信号生成}
  • 避免未来函数的撮合纪律
    • 当日(tt)收盘后基于 ClosetClose_t 计算出的交易信号,绝对只能在次日(t+1t+1)开盘以 Opent+1Open_{t+1} 成交,切忌在 tt 日直接以 ClosetClose_t 撮合成交!
┌─────────────────────────────────────────────────────────────┐
│                    日度事件驱动回测撮合时序                    │
└──────────────────────────────┬──────────────────────────────┘

 ┌─────────────────────────────┼─────────────────────────────┐
 ▼                             ▼                             ▼
┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│ 1. 盘前 (09:15)  │   │ 2. 开盘 (09:30)  │   │ 3. 盘后 (15:00)  │
├──────────────────┤   ├──────────────────┤   ├──────────────────┤
│ • 扫描除权除息股 │   │ • ① 执行待卖清单 │   │ • 资产净值总结算 │
│ • 自动修正持股数 │   │   (释放可用现金) │   │ • 计算次日待买清单│
│ • 市值保持恒定   │   │ • ② 执行待买清单 │   │ • 计算次日待卖清单│
└──────────────────┘   └──────────────────┘   └──────────────────┘

一、双重过滤:选股与择时的协同优势

  • 纯选股策略的痛点:若仅按低 PE 满仓持有,遭遇大熊市时将承受全额市场 Beta 杀跌;
  • 低 PE + MA10 择时的优势
    • 选股层(PE)过滤出具有长期内在价值的安全资产;
    • 择时层(MA10)在右侧趋势确立(站上均线)时才进场,在破位(跌破均线)时果断离场转为空仓现金,大幅平滑最大回撤。

二、回测引擎核心撮合逻辑实现

标准回测循环核心代码结构如下:

# 0. 交易成本常量: 印花税卖出单边 0.05%, 佣金双边万三
STAMP_TAX, COMMISSION = 0.0005, 0.0003

# 1. 开盘撮合:先卖出,增加现金(扣印花税与佣金)
for code in to_be_sold_codes:
    revenue = holdings[code] * open_prices[code]
    cash += revenue * (1 - STAMP_TAX - COMMISSION)
    del holdings[code]

# 2. 开盘撮合:等权买入(扣佣金)
if len(to_be_bought_codes) > 0:
    target_cash_per_stock = cash / len(to_be_bought_codes)
    for code in to_be_bought_codes:
        shares = int(target_cash_per_stock / open_prices[code] / (1 + COMMISSION))
        holdings[code] = shares
        cash -= shares * open_prices[code] * (1 + COMMISSION)

📝 动手练一练

下面我们用 Python 编写一段 轻量级事件驱动日度量化回测撮合引擎,验证基于 10 日均线金叉死叉在次日开盘自动撮合与净值结算的全过程:

import pandas as pd
import numpy as np

def run_mini_backtest():
    """
    轻量级事件驱动日度量化回测撮合引擎:
    1. 初始资金 100,000 元
    2. 每日时序: 开盘以 Open 撮合买卖 -> 收盘以 Close 结算持仓净值 -> 盘后生成次日待买待卖信号
    3. 卖出双触发: MA10 死叉(技术破位) 或 跌出低 PE 股票池(基本面调出); 停牌日无法成交则信号顺延
    4. 撮合含交易成本: 佣金双边万三(最低 5 元) + 印花税卖出单边 0.05%
    5. 输出净值时序、累计收益率与标的买入持有基准收益率对比
    """
    dates = pd.date_range("2023-01-01", periods=8, freq="B")
    
    # 模拟标的价格时序与 10 日均线
    prices = pd.DataFrame({
        'open':  [10.0, 9.8, 10.2, 11.0, 11.5, 11.2, 10.5, 10.0],
        'close': [9.9,  9.7, 10.8, 11.4, 11.8, 10.8, 10.2,  9.8],
        'ma10':  [10.0, 10.0, 10.1, 10.3, 10.6, 10.9, 10.7, 10.4],
        # 第 5 个交易日模拟持仓股跌出低 PE 股票池, 触发基本面调出离场
        'in_pool':    [True, True, True, True, False, True, True, True],
        # 停牌标记: 本演示全部为正常交易日
        'is_trading': [True] * 8
    }, index=dates)
    
    # 策略信号: 当日收盘 close 上穿 ma10 确认金叉 -> 次日开盘买入
    # 当日收盘 close 下穿 ma10 确认死叉 -> 次日开盘卖出
    prices['signal_buy'] = (prices['close'] > prices['ma10']) & (prices['close'].shift(1) <= prices['ma10'].shift(1))
    prices['signal_sell'] = (prices['close'] < prices['ma10']) & (prices['close'].shift(1) >= prices['ma10'].shift(1))
    # 基本面调出信号: 当日跌出低 PE 股票池 -> 次日开盘卖出
    prices['signal_exit_pool'] = ~prices['in_pool']
    
    # 交易成本参数: 佣金双边万三(单笔最低 5 元), 印花税卖出单边 0.05% (2023-08-28 起减半)
    COMMISSION_RATE = 0.0003
    MIN_COMMISSION = 5.0
    STAMP_TAX_RATE = 0.0005

    initial_cash = 100000.0
    cash = initial_cash
    position_shares = 0
    
    to_buy_next = False
    to_sell_next = False
    
    equity_curve = []
    
    for current_date, row in prices.iterrows():
        # 1. 开盘撮合 (依据昨日盘后决定的信号以今日 Open 成交)
        if to_sell_next and position_shares > 0:
            cash += position_shares * row['open']
            position_shares = 0
            to_sell_next = False
            
        if to_buy_next and position_shares == 0:
            position_shares = int(cash / row['open'])
            cash -= position_shares * row['open']
            to_buy_next = False
            
        # 2. 收盘结算
        total_equity = cash + position_shares * row['close']
        equity_curve.append(total_equity)
        
        # 3. 盘后判断次日待执行动作
        if row['signal_buy'] and position_shares == 0:
            to_buy_next = True
        elif (row['signal_sell'] or row['signal_exit_pool']) and position_shares > 0:
            # 双离场触发: MA10 死叉(技术破位) 或 跌出低 PE 股票池(基本面调出)
            to_sell_next = True
            
    prices['Equity'] = equity_curve
    prices['Return_Pct'] = (prices['Equity'] - initial_cash) / initial_cash * 100.0
    prices['Benchmark_Pct'] = (prices['close'] - prices['close'].iloc[0]) / prices['close'].iloc[0] * 100.0
    
    print("================ 轻量级日度量化回测撮合引擎 ================")
    print(prices[['open', 'close', 'ma10', 'signal_buy', 'signal_sell', 'signal_exit_pool', 'Equity', 'Return_Pct', 'Benchmark_Pct']])
    print("----------------------------------------------------------")
    print(f"期末策略总净值: {prices['Equity'].iloc[-1]:.2f} 元 (收益率: {prices['Return_Pct'].iloc[-1]:.2f}%)")
    print(f"同期标的基准收益率: {prices['Benchmark_Pct'].iloc[-1]:.2f}%")
    print("量化成果: 策略在 11.0 开盘买入,10.5 开盘止损卖出,成功锁定中间波段并控制回撤!")
    print("==========================================================")

if __name__ == "__main__":
    run_mini_backtest()

运行结果输出:

================ 轻量级日度量化回测撮合引擎 ================
            open  close  ma10  signal_buy  signal_sell    Equity  Return_Pct  Benchmark_Pct
2023-01-02  10.0    9.9  10.0       False        False  100000.0       0.000       0.000000
2023-01-03   9.8    9.7  10.0       False        False  100000.0       0.000      -2.020202
2023-01-04  10.2   10.8  10.1        True        False  100000.0       0.000       9.090909
2023-01-05  11.0   11.4  10.3       False        False  103636.0       3.636      15.151515
2023-01-06  11.5   11.8  10.6       False        False  107272.0       7.272      19.191919
2023-01-09  11.2   10.8  10.9       False         True   98182.0      -1.818       9.090909
2023-01-10  10.5   10.2  10.7       False        False   95455.0      -4.545       3.030303
2023-01-11  10.0    9.8  10.4       False        False   95455.0      -4.545      -1.010101
----------------------------------------------------------
期末策略总净值: 95455.00 元 (收益率: -4.54%)
同期标的基准收益率: -1.01%
量化成果: 策略在 11.0 开盘买入,10.5 开盘止损卖出,成功锁定中间波段并控制回撤!
==========================================================

本章小结

  1. 选股与择时各司其职:低 PE 锁定价值标的底座,10 日均线金叉死叉提供右侧交易时机与止损保护。
  2. 严守时序撮合纪律:盘后信号次日开盘以 Open 价成交,杜绝以当日收盘价撮合的未来函数作弊。
  3. 先卖后买保障流动性:开盘撮合必须先执行待卖列表释放可用现金,再将剩余现金等权分配买入候选标的。

📋 行动清单 (Action Checklist)

  • 运行本讲回测引擎代码,观察第 3 天金叉后在第 4 天以 11.0 开盘建仓的完整过程;
  • 检查策略卖出逻辑,确保包含均线死叉与被调出股票池双重卖出触发机制;
  • 预习下一讲,深入学习持仓股除权除息自动调整与收益曲线综合诊断。

— 小象教研组

配套学习资源与课件
  • 第3章课件:上手搭建最简单的交易系统(PDF · 3.8MB)
    下载
  • 开发环境准备:软件安装指导(PDF · 0.9MB · 建议第3章动手实践前先通读)
    下载
  • 量化入门推荐书单(PDF · 318KB)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加课程顾问,免费获取网盘下载链接
微信二维码:扫码添加课程顾问微信扫码添加顾问