跳转至

第 6 章:A 股市场微观结构与策略实战

⏱️ 预计阅读 ~25 分钟 | 🎯 难度 ★★★☆☆(进阶)

A 股市场(中国内地股票市场)具有独特的微观结构和交易规则,这些规则不仅影响着投资者的交易行为,更是量化回测引擎必须精确模拟的核心要素。本章将从学术和工程的双重角度,详细剖析 A 股市场的核心机制——包括 T+1 交收制度涨跌停板制度集合竞价机制以及交易成本模型,并展示如何在 AKQuant 中对这些机制进行建模。

学习目标

  • 理解 A 股 T+1、涨跌停、集合竞价与交易成本等制度语义。
  • 掌握中国股票市场规则在回测中的建模方法。
  • 能够解释制度约束如何改变策略表现与成交路径。

前置知识

  • 已掌握第 3 到 5 章中的数据、回测与策略基础。
  • 了解 A 股日常交易流程即可。

本章实践入口

快速运行与验收

python examples/textbook/ch06_stock_a.py

验收要点:

  1. 脚本可完成 A 股规则下的回测流程并输出结果。
  2. 日志或结果中可观察到 T+1、涨跌停或交易成本相关影响。
  3. 修改交易成本参数后,收益与回撤指标出现可解释变化。

6.1 市场微观结构概述 (Market Microstructure)

市场微观结构 (Market Microstructure) 是研究金融资产交易机制、价格形成过程以及市场设计对价格发现和流动性影响的学科。A 股市场作为典型的新兴市场,其微观结构具有四个相互关联的显著特征。首先,它是指令驱动 (Order-Driven) 的市场,价格主要由买卖双方的限价指令(Limit Orders)撮合形成,而非由做市商报价(Quote-Driven)。其次,它实行 T+1 交收 (T+1 Settlement),买入的股票当日不可卖出,次日方可卖出。第三,它设有价格限制 (Price Limits),即每日涨跌幅限制,旨在抑制过度投机。最后,它仍以散户为主 (Retail-Dominated):虽然机构投资者占比逐年上升,但散户交易量仍占主导地位,由此导致市场噪声交易(Noise Trading)较多。

6.2 T+1 交收制度与持仓管理

6.2.1 制度定义

T+1 制度 指的是证券交易的交收规则:

  • 资金 (Cash):T 日卖出股票获得的资金,T 日可用(可用于买入),但 T+1 日可取(可转出银行卡)。
  • 股份 (Shares):T 日买入的股票,T 日登记在册,但 T+1 日才可卖出。

这一制度的核心目的是限制日内回转交易(Day Trading),防止市场过度投机,但同时也降低了市场的定价效率。

6.2.2 引擎实现:持仓状态机

AKQuant 中,为了精确模拟 T+1 规则,我们将持仓对象 (Position) 设计为一个包含两个核心状态变量的结构体:

  • total_quantity (总持仓):账户名下当前拥有的所有股份数量。
  • available_quantity (可用持仓):当前时刻可以用于卖出的股份数量。

持仓状态的变迁遵循以下状态机 (State Machine) 逻辑:

  1. 初始状态 (T日开盘前)

    \[ Available_T = Total_T \]
  2. 买入成交 (Buy Fill): 当发生买入成交(数量为 \(Q_{buy}\))时:

    \[ Total_{new} = Total_{old} + Q_{buy} \]
    \[ Available_{new} = Available_{old} \quad (\text{不变}) \]

    注:新买入的股份计入总持仓,但不增加可用持仓。

  3. 卖出成交 (Sell Fill): 当发生卖出成交(数量为 \(Q_{sell}\))时:

    \[ Total_{new} = Total_{old} - Q_{sell} \]
    \[ Available_{new} = Available_{old} - Q_{sell} \]

    注:卖出操作同时扣减总持仓和可用持仓。

  4. 日终清算 (End of Day Settlement): 在 T 日交易结束后,系统执行清算操作,将当日买入的冻结股份释放为可用:

    \[ Available_{T+1} = Total_{T, end} \]

6.2.3 代码示例:T+1 验证

下面的代码展示了 AKQuant 如何严格执行 T+1 规则。尝试在买入当日立即卖出会被引擎拒绝。

"""
第 6 章:A 股交易实战 (T+1 与 涨跌停).

本示例展示了如何处理中国 A 股市场特有的交易规则:
1. **T+1 交易制度**:当天买入的股票,第二个交易日才能卖出。
2. **涨跌停限制**:涨停板无法买入,跌停板无法卖出。
3. **最小交易单位**:买入必须是 100 股的整数倍 (手)。

策略逻辑:
- 每天开盘尝试买入
- 每天收盘尝试卖出
- 观察 T+1 限制如何阻止当日卖出
"""

import akquant as aq
import numpy as np
import pandas as pd
from akquant import Bar, Strategy


# 模拟数据生成 (包含涨跌停场景)
def generate_mock_data(length: int = 20) -> pd.DataFrame:
    """生成模拟数据."""
    np.random.seed(42)
    dates = pd.date_range(start="2023-01-01", periods=length, freq="D")

    # 构造价格序列
    prices = np.full(length, 100.0)

    # 第 3 天:涨停 (假设涨停价为 110.0)
    prices[2] = 110.0

    # 第 5 天:跌停 (假设跌停价为 90.0)
    prices[4] = 90.0

    df = pd.DataFrame(
        {
            "date": dates,
            "open": prices,
            "high": prices + 2,
            "low": prices - 2,
            "close": prices,
            "volume": 100000,
            "symbol": "600000",
        }
    )

    # 手动设置涨跌停状态 (通过 extra 字段模拟,或者由引擎根据昨收自动判定)
    # 在真实回测中,AKQuant 会根据昨日收盘价自动计算涨跌停
    # 这里我们通过特定的价格行为来触发引擎的涨跌停逻辑
    # 注意:AKQuant 的涨跌停判定依赖于配置的 limit_up_price / limit_down_price
    # 或者通过 use_china_market() 自动启用规则

    return df


class TPlusOneStrategy(Strategy):
    """T+1 策略演示."""

    def on_bar(self, bar: Bar) -> None:
        """收到 Bar 事件的回调."""
        symbol = bar.symbol

        # 获取账户持仓详情
        # position.quantity: 总持仓
        # position.available: 可用持仓 (T+1 解锁后)
        pos = self.get_position(symbol)
        avail = self.get_available_position(symbol)

        self.log(f"当前持仓: 总={pos}, 可用={avail}, 价格={bar.close}")

        # 1. 尝试买入 (T+0)
        if pos == 0:
            self.log("尝试买入 100 股...")
            self.buy(symbol, 100)

        # 2. 尝试卖出 (T+1)
        # 注意:如果当天刚买入,avail 应该为 0,卖单会被拒绝或挂起
        elif pos > 0:
            if avail > 0:
                self.log(f"可用持仓 {avail} > 0,尝试卖出...")
                self.sell(symbol, avail)
            else:
                self.log("可用持仓为 0 (受 T+1 限制),无法卖出!")


if __name__ == "__main__":
    df = generate_mock_data()

    print("开始运行第 6 章示例策略...")

    # 启用 ChinaMarket 模式 (关键!)
    # 这会自动开启 T+1、印花税等规则
    # aq.set_context(market="cn_stock")

    result = aq.run_backtest(
        strategy=TPlusOneStrategy,
        data=df,
        initial_cash=100_000,
        commission_rate=0.0003,
        stamp_tax_rate=0.001,  # 印花税 (仅卖出收取)
        t_plus_one=True,  # 显式开启 T+1
    )

运行结果解析

[Day 1] 尝试买入 100 股...
[Day 1] 成交: 买入 100 股
[Day 1] 当前持仓: 总=100, 可用=0 (受 T+1 限制) -> 无法卖出!

[Day 2] 当前持仓: 总=100, 可用=100 -> 尝试卖出...
[Day 2] 成交: 卖出 100 股
这一机制确保了回测结果不会出现“未来函数”式的违规交易。

6.3 涨跌停板制度 (Price Limits)

6.3.1 规则分类

为了稳定市场情绪,A 股对每日股价的波动幅度设定了限制。基准价通常为前一交易日的收盘价 (Pre-Close)。

板块/类型 涨跌幅限制 备注
主板 (Main Board) \(\pm 10\%\) 沪深主板通用
创业板 (ChiNext) \(\pm 20\%\) 注册制改革后
科创板 (STAR Market) \(\pm 20\%\) 上市前 5 日不设限
ST / *ST \(\pm 5\%\) 风险警示股票
北交所 (BSE) \(\pm 30\%\)

6.3.2 磁吸效应 (Magnet Effect)

学术研究表明,涨跌停板具有磁吸效应:当股价接近涨停板时,买盘会加速涌入,推动价格更快封板;反之亦然。这导致了流动性的突然枯竭。

6.3.3 引擎处理逻辑

在回测中,处理涨跌停板的关键在于成交判定。从市场机制角度看,常见的建模原则可以沿着三条线索展开。第一条是价格检查:若 \(High = LimitUp\)(涨停),通常意味着买盘拥挤,保守回测中往往不应假设还能轻易买入;反过来,若 \(Low = LimitDown\)(跌停),则意味着卖盘拥挤,保守回测中往往不应假设还能轻易卖出。第二条是流动性检查:即使价格未封板,如果当前 Bar 的成交量 (Volume) 极小,大额订单也可能无法完全成交,为此 AKQuant 当前可通过 volume_limit 参数(如 Bar 成交量的 10%)限制最大成交量。第三条针对特殊状态,其中最典型的是一字板:如果 \(Open = High = Low = Close = LimitUp/LimitDown\),且 \(Volume \approx 0\),在更保守的建模中通常应视为全天几乎无法成交。

实现说明:当前版本的 AKQuant 默认撮合逻辑不会内置一套覆盖不同板块、ST 状态与历史时期变化的动态涨跌停规则表。如果你的数据源已经计算好了 limit_uplimit_downcan_buycan_sell 等字段,可以通过 Bar.extra 一并传入,用这些字段辅助策略判断,或在自定义撮合/执行扩展中消费这些字段。

6.4 交易成本模型 (Transaction Cost Model)

在 A 股回测中,忽视交易成本往往导致策略表现虚高。AKQuant 引擎内置了精细的成本模型。

6.4.1 印花税 (Stamp Duty)

  • 税率:目前为 0.05% (万分之五)。
  • 规则:仅在卖出成交金额上征收,买入不征收。
  • 引擎实现tax = fill_price * fill_quantity * 0.0005 if side == SELL else 0

6.4.2 佣金 (Commission)

  • 费率:券商收取的交易费用,通常在 0.01% - 0.03% (万一到万三) 之间。
  • 最低收费:单笔交易最低 5 元。这意味着对于小资金(如 1 万元以下)的交易,佣金比例可能高达 0.1% 甚至更多。
  • 默认引擎实现commission = max(min_commission, fill_price * fill_quantity * commission_rate)
  • AKQuant 扩展模式
    • percent:按成交额比例收费,是股票默认口径。
    • fixed:每次成交固定金额,不随成交数量变化。
    • per_unit:按成交数量线性收费,即 fill_quantity * unit_fee
  • 实践建议:A 股常规券商账户优先使用 percent + min_commission;只有在你的真实费用口径明确是“每股/每手线性收费”时,再切换到 per_unit

6.4.3 过户费 (Transfer Fee)

  • 费率:目前为 0.001% (十万分之一)。
  • 规则:双向征收。通常包含在佣金中,但在精细化回测中应单独计算。

6.4.4 滑点 (Slippage)

滑点并非显性成本,而是由于市场流动性不足或价格波动导致的成交价劣于预期价的部分。

  • 固定滑点:每笔交易假设损失 1 跳 (Price Tick)。
  • 百分比滑点:每笔交易假设损失成交额的 0.1%。

6.5 集合竞价机制 (Call Auction)

A 股每个交易日有两次集合竞价时间,这是发现价格的关键时刻,分别发生在开盘和收盘前后。

开盘集合竞价 (09:15 - 09:25) 又可细分为两段:在 09:15 - 09:20 之间,可申报、可撤单,此时主力经常挂假单试盘,制造虚假价格;进入 09:20 - 09:25 后,仍可申报,但不可撤单,因此这一时段的价格才具有参考意义。到 09:25,系统按最大成交量原则产生开盘价。

收盘集合竞价 (14:57 - 15:00) 则在尾盘进行,目前全市场均已实行(部分板块如科创板、创业板早已实行,主板后来跟进),其目的是防止尾盘操纵价格("做收盘价")。

策略启示

  • 打板策略:需要在 09:25 之前通过“排队”逻辑判断是否能买入。
  • 日内策略:利用 09:20 后的挂单失衡 (Order Imbalance) 预测开盘后的短期走势。

6.6 股指期货对冲 (Index Futures Hedging)

对于 Alpha 策略,我们需要剥离市场风险 (Beta),仅保留选股收益 (Alpha)。A 股主要使用四种股指期货进行对冲,它们分别覆盖不同的市值区间。IF (沪深300) 代表大盘蓝筹,流动性最好。IC (中证500) 代表中盘成长,其长期存在贴水 (Backwardation),即期货价格低于现货价格,这意味着做空 IC 对冲需要支付每年 5%-10% 的贴水成本。IM (中证1000) 代表小盘股,波动大,贴水更深。IH (上证50) 则代表超大盘银行地产。

对冲比例计算

\[ N = \beta_P \times \frac{Value_{Portfolio}}{Price_{Future} \times Multiplier} \]

动态调整 \(N\) 以保持组合的中性 (Market Neutral)。

6.7 风格因子投资 (Smart Beta)

A 股市场具有鲜明的风格特征,量化策略往往通过暴露于特定因子获利,其中有四类因子尤为常见。市值因子 (Size) 表现为:长期来看,小盘股跑赢大盘股("小市值效应"),但这一效应在 2017-2020 年核心资产牛市中失效。价值因子 (Value) 关注低市盈率 (PE)、低市净率 (PB) 股票的表现。动量因子 (Momentum) 在 A 股则呈现出反转效应 (Reversal) 强于动量效应的特点:追涨杀跌在短期(1个月)往往亏损,但在中期(3-6个月)可能有效。波动率因子 (Volatility) 表现为低波动股票长期跑赢高波动股票("特质波动率之谜")。

6.8 指数增强策略 (Index Enhancement)

指数增强是目前 A 股机构投资者最主流的策略。目标是在跟踪指数(如中证500)的基础上,通过选股获取超额收益。

6.8.1 目标函数

\[ \max \alpha \quad \text{s.t.} \quad TE < 5\% \]

其中 \(TE\) (Tracking Error) 是跟踪误差。这意味着我们不能偏离指数太远。

6.8.2 组合构建

通常使用 Barra 风险模型 进行优化,其约束可以概括为两点。一是风格中性,即让组合的市值、行业暴露与指数保持一致;二是个股偏离,即允许在成分股内部进行权重微调(超配高分票,低配低分票)。

6.9 事件驱动策略 (Event-Driven)

A 股市场存在大量的事件套利机会。

6.9.1 业绩预喜 (Earnings Surprise)

  • 逻辑:A 股有“盈余公告后漂移” (Post-Earnings Announcement Drift, PEAD) 现象。业绩超预期的股票,在公告后的一段时间内倾向于持续上涨。
  • 信号Actual EPS > Consensus EPS

6.9.2 高送转 (Stock Split)

虽然本质上是数字游戏,但在 A 股历史上,高送转(如 10 送 10)往往能引发填权行情。

本章小结

必须掌握

  • A 股制度约束不是“背景知识”,而是影响回测可信度的核心变量。
  • T+1、涨跌停与成本模型会直接改变成交、持仓与收益曲线。

理解即可

  • 散户占比较高、指令驱动等微观结构特征会影响模型假设与信号解释。

实践提醒

  • 做 A 股回测时先确认制度参数,再解读策略绩效。

主线推进

贯穿全书的那条最小多均线 / 趋势策略,在第 1 章跑通了回测闭环、在第 4、5 章被重写为带止损的事件驱动标准策略类之后,本章把它从“理想市场假设”推进到“A 股真实制度场景”。同一套双均线逻辑一旦落到 A 股,就必须面对 T+1 交收、涨跌停成交判定与印花税/佣金/过户费/滑点等成本约束:金叉买入的股份当日不可卖出,死叉卖出可能因跌停封板而无法成交,频繁换手则会被交易成本侵蚀收益。本章因此为主线策略补齐了制度建模这一关键环节——读者可以把第 5 章的策略原样接入本章的 A 股规则,观察成交路径、持仓与收益曲线如何因制度约束而改变,为后续章节把它扩展到多因子、指数增强与对冲场景打下贴近实盘的基础。

延伸阅读

经典著作

  • O'Hara, M. Market Microstructure Theory,Blackwell, 1995 —— 市场微观结构理论的奠基性专著,系统梳理存货模型与信息模型,可与本章 6.1(市场微观结构概述)对照精读。
  • Harris, L. Trading and Exchanges: Market Microstructure for Practitioners,Oxford University Press, 2002 —— 面向实务的市场微观结构经典,详尽讲解限价订单簿、订单类型、集合竞价与价格优先/时间优先规则,直接对应本章 6.3(涨跌停板)与 6.5(集合竞价机制)。
  • López de Prado, M. Advances in Financial Machine Learning,John Wiley & Sons, 2018 —— 现代金融机器学习的系统之作,涵盖因子构建与回测过拟合治理,延伸本章 6.7(风格因子投资)、6.8(指数增强)与 6.9(事件驱动策略)。

官方文档与工具

  • AKQuant 量化基础指南 —— T+1、涨跌停与交易成本等 A 股制度在引擎中的建模说明,对应本章 6.2、6.3、6.4。
  • AKShare 官方文档 —— 获取 A 股行情、涨跌停价与板块分类等数据的主要来源,支撑本章 6.3、6.6 的实证落地。

本书相关

  • 第 4 章:事件驱动回测原理 —— 本章涨跌停成交判定与 volume_limit 流动性约束(6.3)建立在第 4 章撮合与风控引擎之上。
  • 第 5 章:策略开发实战 —— 本章风格因子(6.7)与事件驱动策略(6.9)所需的下单接口与生命周期回调,承接第 5 章交易接口与策略骨架。

课后练习

基础题

  1. 修改交易成本参数,比较策略收益和回撤变化。

应用题

  1. 构造一个涨停无法买入或跌停无法卖出的回测场景。

综合题

  1. 设计一个同时考虑 T+1 与涨跌停约束的调仓规则。
参考答案要点(先独立思考再展开)

基础题:印花税/佣金/滑点上调后,高换手策略收益显著下滑;A 股卖出单边征收印花税,对频繁交易影响尤其大。

应用题:让某根 Bar 触及涨停(High = LimitUp 且买入受阻)或跌停(卖出受阻),可通过 Bar.extra 携带 can_buy / can_sell 标记辅助策略判断。

综合题:调仓前同时检查 available_position(T+1 可用持仓)与涨跌停标记,仅对可买/可卖标的下单,其余顺延到下一交易日。

常见错误与排查

  1. 交易结果过于理想:检查是否遗漏了手续费、印花税和滑点设置。
  2. 卖单无法成交:确认是否触发 T+1 或可用持仓不足约束。
  3. 成交量异常:核对最小交易单位与成交量限制参数。