跳转至

M13 交易成本与执行

层级:L3 组合管理 | 优先级:★★ | 预计学时:10 小时 对应原书:黑箱第 5、7 章;GK 第 16 章;陈 5.2。第 13–16 章的高频内容只作边界。 前置知识:M00 的换手恒等式;M12 的账户权重。 读书笔记:黑箱第 5、7 章的成本 状态:reviewed 数据截至:2023-11|最近复核:2026-10-28


学习目标

  1. 用 2τ c 把单边换手和每一元成交的成本乘成净值拖累,并识破把 0.13% 写成 0.013 的数量级错误。
  2. 手算一笔最低佣金、一笔印花税和一条 c = 1 的平方根冲击,分开「价目」和「论文系数」。
  3. 用五只股票看缓冲区怎样把边界换手变成 0,以及排名掉出去之后单边换手怎样变成 0.5。
  4. 把实施缺口拆成成交价差、费用和未成交的机会,三项加总等于纸面与实际之差。
  5. 对照本仓库:限价、过去 20 日成交量、理想化成交不是实盘证据。
  6. 用手算连续权重的缓冲带:带内不交易;带外区分回到目标中心和只补到边界。

本章知识拓扑图

flowchart TB
    TAU["单边换手 τ"] --> DRAG["拖累 = 2 τ c"]
    C["每一元成交的 c"] --> DRAG
    MIN["最低佣金"] --> C
    STAMP["港股每侧印花税"] --> C
    IMPACT["平方根冲击,c=1 只是教学系数"] --> C
    GATE["限价,且参与率只用过去 20 日"] --> FILL["成交或未成交"]
    FILL --> IS["实施缺口"]
    DRAG --> NET["毛收益减去拖累"]

FeeSchedule 是输入的价目,文件头写明它不是券商合同。futu_fees.py 按公开的美股固定计划逐笔向上取整到分,用来重算已经发生的成交,不改股数。个人税不在这两个类里。港股印花税要另加。


🟢 入门层:每一元成交都要付钱

1. 拖累打在成交额上,不打在单边换手上

M00 的定义是

τ = 区间成交名义金额 / (2 × 净值), 拖累 = 2τ c

c 是每一侧、每一元成交的成本。卖出半仓再买入半仓时,成交额是 1 倍净值,单边换手是 0.5,成本是 1 × c,也就是 2 × 0.5 × c。

年化单边换手 成交额 / 净值 每侧 15 bps 每侧 20 bps 每侧 25 bps 只计 10 bps 印花税
2.0 4 0.60% 0.80% 1.00% 0.40%
7.2 14.4 2.16% 2.88% 3.60% 1.44%
3.6 7.2 1.08% 1.44% 1.80% 0.72%

7.2 对应「每月单边换手 60%」。3.6 是把这个月度单边换手降到 30%。20 bps 是往返 40 bps 的每一侧,用来接住下面的习题假设。15 bps 和 25 bps 仍是 M00 的教学档:25 bps 里含港股每侧 10 bps 印花税。印花税的法定税率以 2023-11-17 起的每侧 0.1% 为准。

左:拖累随单边换手线性上升。竖线是单边换手 2。右:教学系数 c=1 的平方根冲击,红点是订单占日成交额 1%。

2. 印花税从 0.13% 降到 0.1%,省下的不是 1.2 个百分点

2021-08-01 至 2023-11-16,股票交易印花税是成交金额的 0.13%;2023-11-17 起是 0.1%,买卖双方各自计算。成交额等于 4 倍净值时,只计印花税的拖累是

4 × 0.0013 = 0.0052, 4 × 0.0010 = 0.0040

一年相差 0.12 个百分点。若把 0.13% 写成 0.013,同一笔账会变成 4 × 0.013 = 0.052,也就是 5.2%。那是税率被放大了十倍。佣金和价差还要加在 0.40% 外面:每侧再加 15 bps 时,c = 0.0025,拖累是表里的 1.00%,不是 0.40%。


港股一轮换仓,买卖双方各 0.1% 的印花税已经在成交额上。名义只有 2,000 时,最低佣金会把几毛钱的价目抬成 5 bp。 订单远小于日成交额的 1% 时,平方根冲击常常还没碰到最低收费。c 里要同时写下印花税、佣金和价位,不能只留一个冲击系数。 限价单少付的价差,会变成没成交的那一段。决策价 100、只成交 80 股时,缺口 53 里面有 20 是踏空,不是省下来的钱。 月度调仓若放在下一交易日开盘,开盘相对信号日收盘的跳动记在已成交的让步里,不叫「已经付过的冲击」。 进阶层把拖累写成 2τc,再把缺口拆成让步、费用和机会。

每月单边换手 60% 时,年化单边换手是 7.2,成交额是净值的 14.4 倍。降到月度单边 30%,年化单边是 3.6,成交额是 7.2 倍。 每侧 20 bps、年化单边 2.0 时,拖累是表里的 0.80%。只计港股每侧 10 bps 印花税、成交额等于 4 倍净值时,税率从 0.13% 降到 0.1%,一年相差 0.12 个百分点。

名义只有 2,000 时,最低佣金会把按价目只有几毛的收费抬起来。订单远小于日成交额的 1% 时,平方根冲击常常还没碰到这一档。 决策价 100、只成交一部分时,缺口里的机会项仍要记。限价单少付的价差,换成的是没成交的那一段。

卖出半仓再买入半仓,成交额是 1 倍净值,单边换手是 0.5,成本是 1 × c。 年化单边换手 2.0、每侧 15 bps,拖累是 0.60%;每侧 25 bps 时是 1.00%。25 bps 这一档含港股每侧 10 bps 印花税。 把 0.13% 写成 0.013,4 倍净值的印花税会变成 5.2%。那是税率被放大了十倍,不是政策变动。

🟡 进阶层:价目、冲击和没成交的部分

3. 最低佣金让小单的 bps 升高

教学价目:可变佣金 1 bp,最低 1 个货币单位,平台费 0。标签写明这不是券商价目卡。费用向上取整到分。

2,000 的可变部分低于最低收费,所以 5 bp 来自最低收费,不是来自 1 bp 的费率。港股印花税不在 FeeSchedule 里。名义 20,000 港元、税率 0.1% 时,印花税是 20 港元,等于 10 bps,还要另加佣金。

低价港股的一个最小价位可以比这两项都大。价格 0.40、价位 0.005 时,一个价位是

0.005/0.40 = 0.0125

即 1.25%。这一档是否仍是 0.005,以港交所当时的价位表为准。价位表会改,本章不把某一版的收窄写成已经结算的事实。

4. 平方根冲击用日波动,系数要单独声明

冲击 = c_冲击 · σ_日 · √(Q/V)

Q/V 是订单占日成交额的比例,σ_日 是日波动。年波动 40%、一年 252 个交易日时,

σ_日 = 0.40/√252 = 0.025198

订单占日成交额 1%、教学系数 c_冲击 = 1:

0.025198 × √0.01 = 0.002520

即 25.2 bps。右图的红点就是这个数。c_冲击 = 1 不是 Almgren、Thum、Hauptmann 与 Li (2005, Risk 18(7)) 估出来的系数,也不是 Tóth 等人 (2011, Physical Review X 1) 的估计。它只用来看参与率的平方根怎样把冲击从「几乎可忽略」推到几十个基点。参与率从 1% 增到 4%,冲击变两倍,因为 √4 = 2。

Frazzini、Israel 与 Moskowitz 的 Trading Costs(2018 年工作论文)用的是机构自己的成交。他们发现实现成本低于若干学术代理,冲击的形状接近平方根。本章没有抄他们的基点或容量。个人账户多出来的一项是最低收费:2,000 名义上的 5 bp 不会出现在以十亿美元计的成交里。

5. 缓冲区停的是边界上的换手

五只股票,持有前 2 名,等权各 0.5。卖出线是排名差于 3,也就是 1.5 × 2。某一期排名是 A=3、B=1、C=2。A 仍在卖出线以内,卖出集合是空的,换手是 0。C 虽然排到第 2,满仓且没有人被卖出时,不为它腾位置。

A 的排名落到 4 时,卖掉 A 的 0.5,并买回一只新的 0.5。成交额是 1 倍净值,单边换手是 0.5。Novy-Marx 与 Velikov (2016, RFS 29(1)) 比较了几种降成本的做法,缓冲区是其中之一。上面五行只说明:排名停在缓冲带里时,这次换手可以是 0。毛收益会因此少掉什么,要另测,不能从「换手变成 0」直接减去一个固定的 α。

Gârleanu 与 Pedersen (2013, JF 68(6)) 的结论是朝目标只走一部分,目标本身还要看信号还能持续多久。一个数例:目标权重 0.10、现有 0,只做一半,成交额是净值的 0.05。每侧 15 bps 时,拖累是

0.05 × 0.0015 = 0.000075

即 0.75 个基点。这是半步的成本,不是他们论文里的最优步长。

毛 α 每年 6%、每月单边换手 60%、每侧 20 bps 时,拖累是表里的 2.88%,剩下 3.12%。若缓冲把月度单边换手降到 30%,同时把毛 α 降到 5.5%——这 0.5 个百分点是习题假设,不是上面五行测出来的——拖累变成 1.44%,剩下 4.06%。同一换手改用 15 bps,拖累是 2.16% 和 1.08%;改用 25 bps,是 3.60% 和 1.80%。

6. 实施缺口三项相加

纸面少掉的 53,从三只漏斗里漏走:买贵了的成交让步 32,显性手续费 1,限价没买到的踏空机会 20。 32 + 1 + 20 = 53。未成交的 20 股不是省下了冲击,而是没有拿到从 100 到 101 的纸面收益。 只记佣金,会把缺口算小。 限价单把价差换成了机会项。纸面 100 减实际 47,仍是这 53。

Perold (1988, JPM 14(3)) 把纸面组合和实际组合的差叫做实施缺口。手算:决策价 100,期末价 101,订单 100 股,成交 80 股、成交价 100.40,费用 1。

项目 计算 金额
纸面损益 100 × (101 − 100) 100
实际损益 80 × (101 − 100.40) − 1 47
缺口 100 − 47 53
已成交相对决策价 80 × 0.40 32
费用 1 1
未成交的机会 20 × (101 − 100) 20

32 + 1 + 20 = 53。未成交的 20 股不是「节省了冲击」,而是没有拿到从 100 到 101 的纸面收益。限价单把价差换成了这个机会项。

本仓库的成交分两层。idealized 按开盘价成交,不看参与率,文档写明它不是实盘证据。limit 用开盘价作中间价,让价不超过给定的滑点,而且价格必须越过当日极值至少一个价位才算成交,碰到极值不算。参与率上限用过去已完成的 20 个交易日的成交量,不用当天的最终成交量。历史短于 20 日时,上限函数返回空,限价路径以 insufficient_adv 不成交。20 日、每日 1,000 股、参与率 1%、整手为 1 时,上限是 10 股。调用方先卖后买,买入只能用卖出已经回到账上的现金。

富途这条发送路径只接受限价、常规交易时段、当日有效。它发送的是普通限价,不是 TWAP 或 VWAP。纸面经纪商同样拒绝限价以外的类型。Almgren 与 Chriss (2001, Journal of Risk 3(2)) 的执行轨迹是机构算法的参照。本仓库的适配器不发送那条轨迹。

7. 拆单是在冲击和等待之间选一个点

拆得越碎,单笔冲击越小,但后面几笔要在价格继续走的时间里等待,机会项变大。 一次打完则相反:让步和费用集中,等待变短。 第 6 节的 32 和 20 就是这一对。机构算法在这两头之间选速度,本仓库的适配器不发送那条轨迹。 个人账户先把印花税和最低佣金写进 c,再决定要不要为冲击去拆。

要买的股数是固定的。一次买完,成交价被自己的单推高,冲击大,但之后没有剩余头寸暴露在价格波动里。分成许多段去买,每一段的参与率下降,冲击按平方根或按交易速率的平方下降,可是没买完的那部分还要承受后面时段的价格起伏。Almgren 与 Chriss 把这条边界写成期望成本对方差的曲线:更急的轨迹成本高、方差低,更慢的轨迹成本低、方差高。本章不重估他们的永久冲击和临时冲击系数。下面的两段例子只保留这个权衡的形状,系数是为了能手算而写下的。

买 100 股,分成前后两段,两段股数加总为 100。每一段的临时冲击成本是 0.001 × n² 元,永久冲击先不写。后一段开始时,还没买的股数乘上 1 元的价格标准差,方差就是剩余股数的平方。风险厌恶也取 0.001,所以目标是冲击成本加上 0.001 乘这份方差。

第一段股数 冲击成本 剩余方差 目标
100 10.000000 0 10.000000
80 6.800000 400 7.200000
200/3 5.555556 1111.111111 6.666667
50 5.000000 2500 7.500000

一次买完的目标是 10。时间加权把 100 股切成 50 和 50,冲击降到 5,但剩余 50 股的方差是 2500,目标升到 7.5。一阶条件给出第一段 200/3 股、第二段 100/3 股,目标 6.666667。它比时间加权更急,因为这个风险厌恶下,等待的方差比省下的冲击更贵。风险厌恶再小一些,最优点会靠近 50。系数一变,200/3 就不成立。

def two_slice_objective(first: float) -> tuple[float, float, float]:
    """两段冲击都是 0.001*n^2。方差是还没买完的股数。系数用来手算,不是论文估计。"""
    second = 100.0 - first
    cost = 0.001 * first * first + 0.001 * second * second
    variance = second * second
    return cost, variance, cost + 0.001 * variance


def buffer_one_way(held: set[str], ranks: dict[str, int], sell_after: int) -> float:
    # 排名仍在缓冲带里就不卖。卖出集合为空时,不为了边界名额换手。
    sold = {name for name in held if ranks[name] > sell_after}
    return 0.5 * len(sold)

时间加权按时钟切,不看预计成交量,适合订单相对全天成交很小、而且没有一个必须在某一分钟之前完成的理由。成交量加权按预计的成交量切,基准若就是成交量加权价,偏离这条轨迹会在考核里被记成偏差;预计成交量错了,它会在清淡的时段打出很高的参与率。限价单不保证完成。上面实施缺口里的 20 股就是限价没成交的部分,机会项 20 元。限价适合你拒绝追价的时候。订单必须完成、而且价格已经离开限价时,它把冲击换成了机会,不是把成本消掉。

本仓库的发送路径没有这三条轨迹。它只提交常规时段、当日有效的限价。纸面经纪商拒绝限价以外的类型。因此回测里的 limit 层可以表达「价差换成机会」,不能表达时间加权或成交量加权的最优轨迹。把 200/3 写进适配器,是另一项工程,而且开发机不发单。


🔴 深入层:速度不是这条账户的优势

8. 高频章节划出边界

黑箱第 13–16 章讲的是速度、做市和延迟。个人账户不在那条赛道上。美股零售订单常被转到批发做市商,是否有价格改善要用自己的成交和决策价来算,也就是第 6 节的缺口,而不是用「零售单通常更好」代替记录。监管规则在 2022 年之后有过修订提案;本章不给 2026 年的法规终局。能核对的是:这个代码库发出去的是限价单。

Novy-Marx 与 Velikov 的大体结论是:换手低的价值、盈利、投资一类,扣费后更容易留下来;换手高的短期反转一类,扣费后往往所剩不多。动量处在中间。具体净夏普以他们的表为准。M10 已经给过一组持有期和换手假设下的净 α。策略范围仍是月度或更慢的换手,因为表里单边换手 7.2、每侧 25 bps 时,拖累一年是 3.60%。

9. 连续权重惯性缓冲带(Weight Buffering / No-Trade Zone)

第 5 节的缓冲带看的是排名:持有前 2 名,排名差于 3 才卖。这一节看的是已经算好的账户权重。plan_rebalance 用绝对差距做闸门:

|目标 − 当前| < rebalance_buffer_pct

差距严格小于缓冲就不下单,换手是 0。差距达到缓冲才会按目标权重生成委托,卖出仍排在买入前面。

顶层 risk_limits.rebalance_buffer_pct 是 0.01,也就是账户净值的 1 个百分点。configs/policy/g0_policy.yaml 里股票袖套 equity_s4 收成 0.001。ADR-0004(docs/adr/0004-parallel-equity-sleeve.md) 记过原因:20% 的预算分给约 50 只,每只目标大约 0.4%,1 个百分点的缓冲会把从空仓到目标的差距整段跳过。本章的手算用顶层这 1 个百分点。

Carver 的 Systematic Trading(2015)把缓冲带写成目标仓位的一个比例,常用 10%。目标账户权重是 10.0% 时,10% 的相对缓冲正好是 ± 1.0 个百分点,带宽是 [9.0%, 11.0%]。目标若改成 0.4%,Carver 的 10% 是 ± 0.04 个百分点,和袖套里的 0.1 个百分点不是同一个宽度。两条规则只在目标权重恰好是 10%、相对缓冲恰好是 10% 时写出同一个带宽。

手算:目标 10.0%,缓冲 ± 1.0%。

当前 9.4%。绝对差距 10.0% − 9.4% = 0.6%,小于 1.0%。闸门关掉,不成交,换手是 0。只补到边界的规则在带内同样不交易。

当前 8.2%。绝对差距 10.0% − 8.2% = 1.8%,大于 1.0%,闸门打开。两种做法分道:

做法 成交权重 单边换手 τ
回到目标中心 10.0% 1.8% 0.018/2 = 0.009
只补到下沿 9.0% 0.8% 0.008/2 = 0.004

单边换手仍用第 1 节的定义:成交额除以两倍净值。只买一侧时,成交额等于权重变动。两种做法相差 1.0 个百分点的成交,单边换手相差 0.005。plan_rebalance 用目标权重换算整手,走的是回到中心这一支,不是只补到 9.0%。恰好落在 9.0% 时,差距等于 0.01,条件是严格小于,仓库会交易;Carver 的闭区间 [9%, 11%] 在端点上可以不交易。9.4% 和 8.2% 离端点都够远,两条规则的开闭不会改这两个例子。

from decimal import Decimal


def weight_buffer(target: str, current: str, buffer: str = "0.01"):
    """差距严格小于缓冲就不交易。越过缓冲后,本仓库回到目标,而不是只补到边界。"""
    gap = abs(Decimal(target) - Decimal(current))
    width = Decimal(buffer)
    if gap < width:
        return "no_trade", Decimal(0), Decimal(0)
    # 当前在目标下方时,下沿是目标减缓冲。成交权重 = 差距 − 缓冲。
    to_edge = gap - width
    return "trade", gap, to_edge


for label, current in (("094", "0.094"), ("082", "0.082")):
    status, to_center, to_edge = weight_buffer("0.10", current)
    print(
        f"buffer_{label}",
        status,
        f"{to_center:.3f}",
        f"{to_edge:.3f}",
        f"{to_center / 2:.3f}",
        f"{to_edge / 2:.3f}",
    )

真实案例

案例 1:印花税税率的小数点

成交额为净值的 4 倍时,每侧 0.13% 的印花税拖累是 0.52%,每侧 0.1% 是 0.40%。2023-11-17 的下调省下 0.12 个百分点。写成 5.2% 和 4% 的那个差额,等于用了 0.013 而不是 0.0013。港股相对美股仍多出每侧约 10 bps 的印花税,但高换手规则是否还能做,要看 2τ c 的 c 里还有没有佣金和价位,而不是看那个放大了十倍的百分数。

案例 2:2,000 名义金额上的 5 bp

同一张教学价目,2,000 收 1.00,是 5.00 bps;20,000 收 2.00,是 1.00 bps。机构成交数据里看不大的最低收费,在个人的小单上变成主要的显性项。价格 0.40、一个价位 1.25% 时,显性佣金不再是大头。Frazzini 等人样本里的冲击形状,不能直接当成这张最低收费的替代。

案例 3:决策价 100、成交 80 股

纸面赚 100,实际赚 47,缺口 53。其中 32 是已成交股相对决策价的让步,1 是费用,20 是没买到的 20 股错过的上涨。若回测用 idealized 按开盘价全额成交,这 53 不会出现。那一层不能当作实盘执行质量。限价层还会在过去 20 日不足时直接不成交,机会项就是整个订单。


方法论来源

概念 / 模型 核心文献或规范出处
平方根冲击 教学系数取 1。不是 Almgren, Thum, Hauptmann & Li (2005), Risk 18(7),也不是 Tóth 等 (2011)
缓冲与降换手 Novy-Marx & Velikov (2016), RFS 29(1)
连续权重缓冲 Robert Carver, Systematic Trading(2015)。目标 10% 时,10% 相对缓冲与顶层绝对缓冲 0.01 重合
朝目标只走一部分 Gârleanu & Pedersen (2013), JF 68(6)
实施缺口 Perold (1988), JPM 14(3)
两段执行目标 Almgren & Chriss (2001), Journal of Risk 3(2)。适配器不发送该轨迹
参与率窗口 过去已完成的 20 个交易日;不足则不成交
发送路径 常规时段、当日有效的限价。备注为不超过 64 字节的 ASCII
图 figures/M13/plot_costs.py

💡 思考回顾与巩固

核心认知纠偏

误区 反例 用法
单边换手 2、成本 10 bps,拖累是 0.20% 成交额是 4 倍净值,拖累是 0.40% 用 2τ c
印花税从 5.2% 降到 4% 4 × 0.013 = 0.052 把税率放大了十倍;正确是 0.52% 和 0.40% 0.1% = 0.001
平方根公式里的 25.2 bps 是 Almgren 的估计 日波动 0.025198、参与率 1%、系数取 1 系数要单独声明
最低佣金可以忽略 2,000 名义上,1 bp 的可变部分是 0.20,实收 1.00 先比最低收费
缓冲一定提高净 α 五行例子里换手可以从 0.5 变成 0;毛 α 的损失没有测 净额要另算
1% 缓冲在任何目标权重上都等于仓位的 10% 只在目标权重为 10% 时,±1 个百分点等于仓位的 10% 顶层是绝对差距;股票袖套是 0.001
带外交易一定只补到边界 8.2% 回到 10% 成交 1.8%,只补到 9% 成交 0.8% plan_rebalance 回到目标中心
未成交等于节省成本 20 股的机会是 20,加进缺口 机会和价差分开
参与率可以用当天成交量 限价层只用过去 20 日;不足 20 日则不成交 当天成交量含有自己的订单
idealized 已经计入冲击 它按开盘价成交,不看参与率 不是实盘证据
本仓库会替你发 VWAP 富途发送路径只接受常规时段的限价 算法单不在这条路径上
机构的实现成本就是个人的 c 2,000 名义的 5 bp 来自最低收费 用自己的价目

费曼自测

  1. 为什么单边换手已经除以 2,拖累还要再乘 2?
  2. 排名停在 3、持有前 2 名时,为什么第 2 名的新股票买不进来?
  3. 实施缺口里的 32 和 20 分别对应哪一部分股票?
  4. 过去只有 19 个交易日时,限价层为什么不成交?
自测要点 1. 单边换手把一买一卖折成「换掉了多少仓位」。费用按每一元成交计,买卖都要付,所以乘回 2。单边换手 2、每侧 10 bps,拖累是 0.40%,不是 0.20%。 2. 卖出线是 3。A 的排名是 3,卖出集合是空的。仓位已经满了,没有空出来的 0.5 可以买 C。等 A 落到 4,才卖掉半仓并买回半仓,单边换手 0.5。 3. 32 是已经成交的 80 股、每股比决策价贵 0.40。20 是没成交的 20 股错过的从 100 到 101。费用 1 是第三项。三项之和等于纸面 100 减实际 47。 4. 参与率要用 20 个已经结束的交易日。19 日时上限是空值,限价路径返回 `insufficient_adv`。这样做是为了不把当天还没走完、而且含有自己订单的成交量写进上限。

四维习题

A. 概念题

  1. 写出拖累公式,并说明 c 打在哪一个金额上。
  2. 缓冲区和「只向目标走一半」各减少哪一种交易?
  3. 限价单把什么成本换成了机会成本?
  4. idealized 和 limit 在参与率上有什么差别?

B. 计算题

  1. 成交额为净值的 4 倍。分别用每侧 0.13% 和 0.1% 计算印花税拖累,并写出把税率当成 0.013 时得到的 0.052。
  2. 年波动 40%,订单占日成交额 1%,系数取 1。计算日波动和冲击。参与率改为 4% 时,冲击变为多少?
  3. 名义 2,000 和 20,000,可变佣金 1 bp、最低 1。实收和 bps 各是多少?价格 0.40、价位 0.005 时,一个价位占价格的多少?
  4. 毛 α 6%,每月单边换手 60%,每侧 20 bps。拖累和剩余各是多少?换手降到 30%、毛 α 改为 5.5% 之后呢?再改用 15 bps 和 25 bps 各算一次拖累。
  5. 目标账户权重 10.0%,缓冲 ±1.0%。当前 9.4% 和 8.2% 各交易多少?回到中心和只补到边界的单边换手差多少?

C. 编程实战题

  1. 运行 figures/M13/plot_costs.py,核对冲击 0.002520、印花税 0.0052 与 0.0040、实施缺口 53、参与率上限 10。
  2. 把教学价目的最低佣金改成 0,确认 2,000 名义的费用变成 0.20,并说明向上取整到分会在不能整除时再抬高 1 分。
  3. 写一个五只股票的缓冲:持有前 2 名,排名差于 3 才卖。打印排名为 3 时的空卖出集合,以及排名为 4 时的单边换手 0.5。

D. 个人实战思考题

  1. 你的订单通常远小于日成交额的 1%。25.2 bps 的冲击和 2,000 名义上的 5 bp 最低收费,哪一项先进入你的 c?
  2. 月度调仓若放在信号日收盘之后的下一交易日开盘,决策价和成交价之间多出来的那一段应记进实施缺口的哪一项?
  3. 实验记录里的净收益要附上哪四个数,才不会把 5.2% 那种税率错误再写进去?
完整参考答案与解析 **A1.** 拖累 = 2τ c。τ 是成交名义金额除以两倍净值。c 打在每一元成交上,所以买卖两边都在里面。单边换手 0.5、成交额 1 倍净值时,拖累就是 c。 **A2.** 缓冲区减少的是排名在持有线和卖出线之间来回进出的交易。五只股票的例子里,排名 3 时换手是 0,排名 4 时单边换手是 0.5。「只走一半」减少的是朝既定目标移动的步长。目标 0.10、走一半,成交额是净值的 0.05,每侧 15 bps 的拖累是 0.75 个基点。 **A3.** 限价单少付的是相对中间价的让步,多出来的是没有成交的那部分纸面收益。手算里,80 股的让步是 32,20 股的机会是 20。只记 32 会把缺口算小。 **A4.** `idealized` 按开盘价成交,不使用参与率,也不是实盘证据。`limit` 在历史短于 20 个已完成交易日时不成交;够 20 日时,上限是这些成交量的平均乘以参与率,再取整手。当天的成交量不进入这个窗口。 **B5.** 4 × 0.0013 = 0.0052,4 × 0.0010 = 0.0040。差额 0.0012。4 × 0.013 = 0.052,是把 0.13% 写成了 1.3%。 **B6.** 日波动 0.40/√252 = 0.025198。参与率 1% 时冲击 0.002520,即 25.2 bps。参与率 4% 时乘上 2,是 0.005040,即 50.4 bps。系数仍是教学用的 1。 **B7.** 2,000 的可变佣金是 0.20,低于最低 1,实收 1.00,即 5.00 bps。20,000 的可变佣金是 2.00,实收 2.00,即 1.00 bps。一个价位占价格 0.005/0.40 = 0.0125。 **B8.** 年化单边换手 0.60 × 12 = 7.2。每侧 20 bps 时拖累 2 × 7.2 × 0.002 = 0.0288,剩余 0.06 − 0.0288 = 0.0312。换手 3.6、毛 α 0.055 时,拖累 0.0144,剩余 0.0406。7.2 和 3.6 在 15 bps 下的拖累是 0.0216 和 0.0108,在 25 bps 下是 0.0360 和 0.0180。5.5% 的毛 α 是假设。 **B9.** 9.4% 的差距是 0.6%,小于 1%,不交易,换手是 0。8.2% 的差距是 1.8%。回到 10% 的成交权重是 1.8%,单边换手 0.009。只补到 9% 的成交权重是 0.8%,单边换手 0.004。两者相差 0.005。仓库按目标权重下单,走回到中心这一支。 **C10–C12.** 图上的换手曲线和 25.2 bps 仍由 `figures/M13/plot_costs.py` 打印。下面这段复算缺口、两段目标和缓冲换手。
def shortfall(order: float, filled: float, decision: float, fill: float, end: float, fee: float) -> tuple[float, float, float]:
    # 让步、费用和没买到的上涨要分开。三块相加才是纸面减实际。
    concession = filled * (fill - decision)
    missed = (order - filled) * (end - decision)
    return concession, fee, missed


assert shortfall(100, 80, 100, 100.40, 101, 1) == (32.0, 1.0, 20.0)
cost, variance, utility = two_slice_objective(200 / 3)
assert abs(utility - 6.666667) < 1e-6
held = {"A", "B"}
assert buffer_one_way(held, {"A": 3, "B": 1}, 3) == 0.0
assert buffer_one_way(held, {"A": 4, "B": 1}, 3) == 0.5
**D13.** 订单远小于日成交额的 1% 时,平方根项会小于 25.2 bps,最低收费仍可能是 5 bp。先把价目算进 c,再按实际的 Q/V 决定要不要加冲击。港股价位若是 1.25%,它会盖过这两项。三条都要写进同一侧的 c,不能只留冲击。 **D14.** 决策价是信号日收盘,成交在下一交易日开盘。这段价格变化记在已成交股的「相对决策价」里,也就是手算中的 32 那一类,不是费用。没成交的部分仍记机会。`idealized` 用开盘价,会把开盘相对前收的跳空吃进纸面成交,不应再把它叫做已经付过的冲击。 **D15.** 四个数是:年化单边换手、每一侧的 c、c 里包含的印花税税率(0.001 或 0.0013,不是 0.013),以及毛收益是哪一个期限。净额是毛收益减 2τ c。另附价目是不是教学标签、参与率是否只用过去 20 日。5.2% 那种结果先检查税率的小数点。

📌 章节知识总结

一句话核心洞察

  1. 拖累是 2τ c。成交额为净值的 4 倍、每侧印花税 0.1% 时,拖累是 0.40%。写成 4% 或 5.2%,是把 0.1% 或 0.13% 放大了十倍。
  2. 教学价目下,2,000 名义实收 1.00,合 5.00 bps;20,000 名义实收 2.00,合 1.00 bps。最低收费绑住的是小单。
  3. 年波动 40%、参与率 1%、系数取 1 时,冲击是 25.2 bps。系数不是 Almgren 等人的估计。
  4. 持有前 2 名、卖出线为 3 时,排名 3 的换手是 0;排名落到 4,卖掉半仓再买回,单边换手是 0.5。
  5. 决策价 100、成交 80 股于 100.40、费用 1、期末 101 时,实施缺口是 53,等于 32 + 1 + 20。
  6. 目标权重 10%、缓冲 ±1% 时,9.4% 不交易;8.2% 回到中心的单边换手是 0.009,只补到 9% 是 0.004。

公式与样本

对象 表达式或数值 前提
拖累 2τ c c 打在每一元成交上
印花税,成交额 4 倍 0.52% 与 0.40% 每侧 0.13% 与 0.1%;税率放大十倍则是 5.2%
每月单边 60% 年化单边 7.2;20 bps 时拖累 2.88% 毛 α 6% 是假设,剩余 3.12%
缓冲后单边 30% 拖累 1.44%,剩余 4.06% 毛 α 5.5% 也是假设
教学佣金 2,000 → 5.00 bps;20,000 → 1.00 bps 1 bp、最低 1、平台费 0
一个价位 1.25% 价格 0.40、价位 0.005,以当时价位表为准
平方根 cσ_日 √(Q/V) c = 1,日波动 0.025198,1% 时 25.2 bps
半步 成交额 0.05,拖累 0.75 bp 每侧 15 bps
实施缺口 53 = 32 + 1 + 20 决策价 100,期末 101
两段拆单 第一段 200/3,目标 6.666667 冲击系数和风险厌恶都是 0.001;不是 Almgren 的估计
参与率 20 日均量 × 1% = 10 股 19 日则不成交;当天成交量不计入
权重缓冲 目标 10%,带 [9%, 11%] 9.4% 换手 0;8.2% 回到中心成交 1.8%,只到边界成交 0.8%

跨模块

结论 交给 位置
2τ c 与 15 / 25 bps 毛收益之外的拖累 M00,M10 的持有期表
触顶留现金、先卖后买 委托顺序 M12,../../../src/quant/portfolio/allocation.py
价目是输入 回测费用 ../../../src/quant/backtest/fees.py
限价与过去 20 日 成交 ../../../src/quant/backtest/fills.py
只发限价 模拟盘发送 ../../../src/quant/execution/brokers/futu.py
换手高的异象扣费后更薄 筛选 M08,Novy-Marx 与 Velikov (2016)
执行记录 实盘对照 M15

学习指引

概念卡片:实施缺口与市场冲击

  • 精读:黑箱第 5 章怎样把成本拆开,第 7 章怎样把执行和信号分开。GK 第 16 章的交易只在扣费后仍有剩余时发生。
  • 对照:拖累、冲击和缺口以本章脚本为准。Almgren 的系数、Frazzini 等人的基点、Novy-Marx 与 Velikov 的净夏普,都没有在这里重估。
  • 项目:回测价目用带标签的 FeeSchedule。港股印花税另加。不要把 idealized 的成交写成执行质量。
  • 下一步:M14 把纸面收益和扣费之后的收益拆到基准、风格和残差上。缺口 53 是执行层的数,不是 α。

自检清单

  • 能把成交额 4 倍、印花税 0.1% 算成拖累 0.40%,并指出 5.2% 来自 0.013
  • 能算出 2,000 名义实收 1.00、合 5.00 bps
  • 能算出日波动 0.025198 和参与率 1%、系数 1 时的 25.2 bps
  • 能说明排名 3 时换手为 0、排名 4 时单边换手为 0.5
  • 能把实施缺口 53 拆成 32、1 和 20
  • 能指出限价层只用过去 20 日,不足则不成交,而且本仓库发送的是限价单
  • 能说明 9.4% 不交易,8.2% 回到 10% 与只补到 9% 的成交差是 1.0 个百分点