跳转至

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

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

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

市场微观结构 (Market Microstructure) 是研究金融资产交易机制、价格形成过程以及市场设计对价格发现和流动性影响的学科。A 股市场作为典型的新兴市场,其微观结构具有以下显著特征:

  1. 指令驱动 (Order-Driven):价格主要由买卖双方的限价指令(Limit Orders)撮合形成,而非做市商报价(Quote-Driven)。
  2. T+1 交收 (T+1 Settlement):买入的股票当日不可卖出,次日方可卖出。
  3. 价格限制 (Price Limits):设有每日涨跌幅限制,旨在抑制过度投机。
  4. 散户为主 (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 规则。尝试在买入当日立即卖出会被引擎拒绝。

"""
第 5 章: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("开始运行第 5 章示例策略...")

    # 启用 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 引擎处理逻辑

在回测中,处理涨跌停板的关键在于成交判定akquant 引擎遵循以下原则:

  1. 价格检查

    • \(High = LimitUp\)(涨停),则无法以涨停价买入(除非有特殊逻辑模拟排队)。
    • \(Low = LimitDown\)(跌停),则无法以跌停价卖出。
  2. 流动性检查

    • 即使价格未封板,如果当前 Bar 的成交量 (Volume) 极小,大额订单也可能无法完全成交。引擎会根据 volume_limit 参数(如 Bar 成交量的 10%)限制最大成交量。
  3. 特殊状态

    • 一字板:如果 \(Open = High = Low = Close = LimitUp/LimitDown\),且 \(Volume \approx 0\),则全天无法成交。

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(5.0, fill_price * fill_quantity * commission_rate)

6.4.3 过户费 (Transfer Fee)

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

6.4.4 滑点 (Slippage)

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

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

6.5 集合竞价机制 (Call Auction)

A 股每个交易日有两次集合竞价时间,这是发现价格的关键时刻。

  1. 开盘集合竞价 (09:15 - 09:25)

    • 09:15 - 09:20:可申报,可撤单。此时主力经常挂假单试盘,制造虚假价格。
    • 09:20 - 09:25:可申报,不可撤单。此时的价格才具有参考意义。
    • 09:25:产生开盘价。最大成交量原则。
  2. 收盘集合竞价 (14:57 - 15:00)

    • 全市场实行收盘集合竞价(部分板块如科创板、创业板早已实行,主板后来跟进)。
    • 目的:防止尾盘操纵价格("做收盘价")。

策略启示

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

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

对于 Alpha 策略,我们需要剥离市场风险 (Beta),仅保留选股收益 (Alpha)。A 股主要使用四种股指期货进行对冲:

  1. IF (沪深300):代表大盘蓝筹。流动性最好。
  2. IC (中证500):代表中盘成长。长期存在贴水 (Backwardation),即期货价格低于现货价格。这意味着做空 IC 对冲需要支付每年 5%-10% 的贴水成本。
  3. IM (中证1000):代表小盘股。波动大,贴水更深。
  4. IH (上证50):代表超大盘银行地产。

对冲比例计算

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

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

6.7 风格因子投资 (Smart Beta)

A 股市场具有鲜明的风格特征,量化策略往往通过暴露于特定因子获利。

  1. 市值因子 (Size):长期来看,小盘股跑赢大盘股("小市值效应")。但在 2017-2020 年核心资产牛市中失效。
  2. 价值因子 (Value):低市盈率 (PE)、低市净率 (PB) 股票的表现。
  3. 动量因子 (Momentum):A 股反转效应 (Reversal) 强于动量效应。追涨杀跌在短期(1个月)往往亏损,但在中期(3-6个月)可能有效。
  4. 波动率因子 (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 风险模型 进行优化:

  1. 风格中性:市值、行业暴露与指数保持一致。
  2. 个股偏离:允许在成分股内部进行权重微调(超配高分票,低配低分票)。

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 交收、涨跌停板、集合竞价以及复杂的费用模型。这些规则构成了策略运行的“物理环境”。理解并尊重这些规则,是策略从回测走向实盘的关键一步。