返回交易笔记
TR Day 64

执行算法 — Limit / Mid / TWAP

Market / Limit / Stop 基础 + Mid-price / Marketable Limit / Patience Order + TWAP/VWAP + IBKR SmartRouter / Adaptive Algo / IceBerg

2026-07-12
Phase 3: 实盘+规模化+迁移
ExecutionAlgoLimitOrderMidPriceTWAPVWAPIBKRSmartRouterFillRate

日期: 2026-07-12 方向: Phase 3 / 执行算法 阶段: Phase 3: 实盘+规模化+迁移 标签: #ExecutionAlgo #LimitOrder #MidPrice #TWAP #VWAP #IBKRSmartRouter #FillRate


今日目标

类型内容
学习Market / Limit / Stop 基础 + Mid-price / Marketable Limit / Patience Order + TWAP/VWAP + IBKR SmartRouter / Adaptive Algo / IceBerg
实操ib_insync 跑 100 次小单对比平均滑点 + 统计 Mid vs Marketable 的 fill rate
产出TR-DAY64 笔记 + 散户最佳实践 cheat sheet + 执行错误黑名单

一、为什么散户也要懂执行算法

很多人觉得「执行算法是机构做大单时才用的东西」,对散户来说这是一个昂贵的误解

来算一笔账:

账户规模:$5,000
月度目标 P&L:5% = $250
单笔交易规模:$100-$500
单笔滑点:0.5%-2%(取决于流动性 + 订单类型)

单笔 $100 滑点 2%   = $2
单笔 $500 滑点 0.5% = $2.5

月度 30 笔 × $2 滑点 = $60

$60 / $250 月度 P&L = 24%

滑点吃掉你 1/4 的 alpha,而且这部分被吃掉的钱不像佣金一样有发票——是悄悄从成交价里扣掉的。更糟的是 IBKR 的报表上不会显示「你今天因为下了 market 单多付了 $2」,你只能自己反推。

我自己的经验法则:单笔滑点 = 月度 P&L 的 10% 是 acceptable buffer,超过这个就要重新审视下单方式。Phase 1-2 我们都默认用 Limit 单,Day 64 要做的是把这个「默认」升级成有 trade-off 意识的选择

1.1 滑点的三个来源

来源散户 vs 机构可控性
Spread cost(穿过买卖价差)散户更严重(小单不享受 mid 撮合):选 limit / mid 可控
Market impact(你的单子推动价格)散户几乎为零低:但 <$5k 不用关心
Latency cost(信号到下单之间价格已动)散户更严重(家用网络)中:选 Limit 可锁价

核心认知:散户没有 market impact 问题(你的 $500 不会推动 SPY 价格),但 spread cost 是真正的杀手——这是所有执行算法的核心要解决的问题。


二、基础 Order Types:先把四种最常用的搞清楚

2.1 Market Order

你说:「按现价立即成交」
broker:「好的,吃掉对手盘最便宜/最贵的单」
属性说明
成交确定性100%(只要市场开盘)
价格确定性0%(fill 在哪取决于 order book)
适合流动性极好(SPY 股票)+ 紧急平仓
不适合期权、小盘股、illiquid pair

Market 单的真实危险:在 SPY 期权上,假设 spread 是 bid 1.20 / ask 1.30:

  • 你下 market buy 1 张 → fill at 1.30
  • 你以为「按现价」≈ mid 1.25,结果多付了 $5/张
  • 一笔不算什么,100 次 = $500,你的月度 P&L 没了

只有一种场景该用 Market:你必须立即平仓(比如止损被触发、突发新闻),额外的 spread cost 比再多持仓 5 分钟的风险低

2.2 Limit Order

你说:「我要 1.25 或更好,差就别成交」
broker:「好的,放进 order book 等」
属性说明
成交确定性不确定(取决于价格 + 时间)
价格确定性100%(不会比你限的价差)
适合几乎所有非紧急场景
风险错过行情

Limit 的核心 trade-off

  • 设得「贪心」(深 in the book) → 等不到 fill → 错过行情
  • 设得「妥协」(接近 ask) → 几乎肯定 fill → 但 spread cost 又回来了

这个 trade-off 就是后面 Mid-price / Marketable Limit / Patience Order 三个变种要解决的。

2.3 Stop Order

你说:「价格碰到 X 后,自动变 Market 单」
broker:「好的,碰到就吃市价」
属性说明
触发前不在 order book(IBKR 服务器端持有)
触发后Market 单(注意!)
风险价格闪崩时 fill 在地板上

典型悲剧:你在 $100 设 stop loss,盘中 flash crash 价格瞬间到 $85 又拉回 $99。你的 stop 在 $85 fill 了,亏 $15 + spread cost——而 5 分钟后价格已经回来了。

对个人量化的启示纯 Stop 单等于 Market 单的延迟版,flash crash 时它会 fuck 你。

2.4 Stop-Limit Order

你说:「碰到 X 后,挂 Limit 在 Y」
broker:「好的,碰到挂 Limit 不挂 Market」
属性说明
触发后Limit 单(不是 market)
价格保护
风险flash crash 时完全不成交(价格穿过你的 limit)

Stop 和 Stop-Limit 的取舍

  • 容忍亏一点但要确保平仓 → Stop
  • 不愿被 flash crash 操,宁可不平 → Stop-Limit
  • 我自己的选择:Stop-Limit + Limit 离触发价 1-2%,给 flash crash 一点 buffer 但不至于平不掉
# Stop-Limit 示例
from ib_insync import StopLimitOrder
# trigger at 99, then limit at 98 (1% buffer)
order = StopLimitOrder('SELL', 1, lmtPrice=98.0, stopPrice=99.0)

三、散户的三个关键策略变种

3.1 Mid-price Limit(散户首选)

bid, ask = ticker.bid, ticker.ask
mid = (bid + ask) / 2
limit_price = round(mid, 2)  # 注意 tick size
属性数值
Fill rate60-70%
平均 spread savingspread/2(典型 $0.05/股或 $5/期权张)
适合流动性好的标的 + 不紧急的入场

为什么 60-70% 而不是更高:你的 Mid 单挂在 spread 中间,必须有对手盘也愿意来到 mid 撮合。SPY/QQQ 等高流动品有大量做市商在 mid 附近报价,所以 fill rate 高;小盘股期权 mid 单几乎不成交。

实操技巧

  1. 挂出去后等 30-60 秒(不是几小时)
  2. 没 fill 就逐步逼近 ask:mid → mid + 1 tick → mid + 2 ticks → marketable
  3. 这个「逐步逼近」过程叫 Price Improvement Walking,本质是「先尝试便宜的,不行再退一步」

3.2 Marketable Limit(几乎肯定成交)

# 买的时候用 ask price 做 limit
limit_price = ticker.ask  # 买
limit_price = ticker.bid  # 卖
属性数值
Fill rate95%+
平均 spread saving0(你接受了整个 spread)
适合流动性差的标的 + 不太挑剔的入场

为什么不用 Market 单而用 Marketable Limit

  • Market 单可以 fill 到比 ask 更糟的价(如果 ask 上的 size 不够吃完你的单)
  • Marketable Limit at ask 至少保证「不比 ask 差」
  • fill rate 几乎一样,但价格上限锁死

这是个免费的 upgrade。任何时候你想下 Market 单,先停下来改成 Marketable Limit——同样的成交概率,但多一层保护。

3.3 Patience Order(看 PFOF)

# 挂 5% above mid(卖单)
limit_price = mid * 1.05
# Time-in-Force: DAY (默认) 或 GTC

这个策略本质是赌散户 PFOF flow:某些做市商(Citadel, Virtu)拿到散户的 market flow 后,可能愿意以远好于 mid 的价格 fill 你的 patience 单(因为他们另一头吃了散户的 market 单赚了 spread)。

适合场景不适合场景
不急的卖出 / 止盈紧急入场
已经在仓位里还没建仓
期权 theta 收割时挂高价等执行任何要求时效性的策略

实操:我把 covered call 的 take-profit 单都用 Patience Order——反正没成交也无所谓,成交了就是天上掉钱。


四、TWAP / VWAP(机构级,但要懂概念)

4.1 什么是 TWAP / VWAP

TWAP (Time-Weighted Average Price):
  把一个大单 X 在 T 时间内平均切成 N 片
  例:10,000 股 SPY,在 1 小时内每 6 分钟下 1,000 股
  目标:让你的平均成交价接近这段时间的 TWAP

VWAP (Volume-Weighted Average Price):
  按市场成交量分布切单
  例:开盘 5 分钟成交量大,下更多片
  目标:你的成交价 ≈ 这段时间所有人的平均成交价
算法适合场景散户用不用
TWAP横盘 / 没有明显量分布<$5k 用不上
VWAP标准交易日大单切片<$5k 用不上
Implementation Shortfall追求最小 market impact机构专用
POV (Percent of Volume)跟随市场量等比例机构专用

4.2 散户什么时候才需要

公式:单笔规模 / ADV > 1% 才考虑切单。

SPY ADV ≈ 80M 股/天
1% × 80M = 800,000 股 → 你单笔下 800k 股才会推动价格

期权 SPY ATM 当月合约 ADV ≈ 100k 张
1% × 100k = 1,000 张 → 你单笔 1,000 张才有 market impact

结论:<$5k 账户永远不需要 TWAP/VWAP——你的单子在市场里像滴水入海。但你要懂概念,因为:

  1. 面试可能问到
  2. Phase 4 资金到 $50k+ 后,单笔期权 100 张时已经接近小盘股期权的 1% ADV
  3. 理解 TWAP 帮你看穿很多「自动化交易」营销话术

4.3 IBKR TWAP 用法(懂概念用一次)

from ib_insync import Order

order = Order()
order.action = 'BUY'
order.orderType = 'MIDPRICE'  # 也支持 'TWAP'
order.totalQuantity = 100
# 算法参数
order.algoStrategy = 'TWAP'
order.algoParams = [
    ('startTime', '20260712-09:30:00 US/Eastern'),
    ('endTime',   '20260712-10:00:00 US/Eastern'),
    ('allowPastEndTime', 0),
]

注意:IBKR 的 algo orders 在小单上经济上不合算——每个切片都是一笔单独的订单,每笔都收最低 $0.35 佣金。100 股切 10 片 = $3.5 佣金,比 spread saving 还多。


五、IBKR 提供的执行算法(重点)

5.1 SmartRouter(默认 + 应该 99% 时间用它)

SmartRouter 工作机制:
  1. 收到你的单
  2. 扫描所有 venue(NYSE, NASDAQ, ARCA, EDGX, IEX, dark pools...)
  3. 找当前 best price + best fill probability
  4. 自动路由

vs Robinhood/Lite 的 PFOF:
  1. 收到你的单
  2. 卖给 Citadel/Virtu
  3. 他们 internalize(不一定 best price)
  4. 你看不到背后的路由决策

对量化的意义:SmartRouter 的成交质量在统计意义上优于 PFOF,这是 Day 1 我们选 IBKR Pro 而不是 Lite 的核心原因。

5.2 Adaptive Algo(IBKR 的杀手锏)

order.algoStrategy = 'Adaptive'
order.algoParams = [('adaptivePriority', 'Patient')]
# 或 'Normal', 'Urgent'
模式行为Fill 时间
Patient多用 mid / dark pool慢(分钟级)
Normal默认 mid 然后逐步逼近 ask中(秒-分钟)
Urgent快速吃 ask快(秒级)

Adaptive Algo 的本质:IBKR 用内部 ML 模型学习市场微结构,自动做你本来要手写的 mid → ask 逼近逻辑。对 Pro 用户免费——这是 IBKR 给我们的免费 alpha。

我的用法

  • 大部分入场单:Adaptive + Patient
  • 止损 / 紧急平仓:Adaptive + Urgent
  • 不再自己写「30 秒没成交就改价」的逻辑——交给 Adaptive

5.3 DARK / Dark Pool Only

order.algoStrategy = 'DarkIce'  # 或 'Dark'
属性说明
Venue仅 dark pool(IEX, MS Pool, etc.)
优势不暴露 size,避免被 front-run
劣势Fill rate 低
适合单笔 >1000 股的入场

散户用不用:<$5k 时不用。当你单笔 SPY 期权 10 张以下时,没人在乎你的 size,front-run 不存在。

5.4 IceBerg(隐藏 size)

order.orderType = 'LMT'
order.totalQuantity = 1000
order.displaySize = 100  # 只显示 100 在 order book

机制:order book 上只显示 100 股,每 fill 100 就再露出 100,直到 1000 全 fill。

散户用不用:单笔 < 1000 股完全用不上


六、代码实操:100 次小单滑点统计

6.1 实验设计

标的:SPY(流动性极好)
单笔规模:1 股(最小可行实验单位)
对比组:
  A. Market Order × 30
  B. Marketable Limit (at ask) × 30
  C. Mid-price Limit (wait 60s) × 30
  D. Adaptive Algo Normal × 10

测量:
  - Fill price vs mid at order time
  - Time to fill
  - Slippage in basis points

6.2 代码

# tr_day64_execution_lab.py
from ib_insync import IB, Stock, MarketOrder, LimitOrder, Order
import pandas as pd
import time

ib = IB()
ib.connect('127.0.0.1', 7497, clientId=64)

spy = Stock('SPY', 'SMART', 'USD')
ib.qualifyContracts(spy)

results = []

def get_mid():
    t = ib.reqMktData(spy, '', snapshot=True)
    ib.sleep(1.5)
    mid = (t.bid + t.ask) / 2
    ib.cancelMktData(spy)
    return mid, t.bid, t.ask

def log_fill(strategy, mid_at_submit, trade):
    fill = trade.orderStatus.avgFillPrice
    slip_bps = (fill - mid_at_submit) / mid_at_submit * 10000
    results.append({
        'strategy': strategy,
        'mid_at_submit': mid_at_submit,
        'fill': fill,
        'slip_bps': slip_bps,
        'time_to_fill_sec': (trade.log[-1].time - trade.log[0].time).total_seconds()
                            if len(trade.log) > 1 else None,
    })

# Strategy A: Market
for i in range(5):  # 真跑实验改 30,演示用 5
    mid, bid, ask = get_mid()
    order = MarketOrder('BUY', 1)
    trade = ib.placeOrder(spy, order)
    while not trade.isDone():
        ib.sleep(0.5)
    log_fill('Market', mid, trade)
    # 立刻反向平掉,避免净仓位
    ib.placeOrder(spy, MarketOrder('SELL', 1))
    ib.sleep(2)

# Strategy B: Marketable Limit (at ask)
for i in range(5):
    mid, bid, ask = get_mid()
    order = LimitOrder('BUY', 1, ask)
    trade = ib.placeOrder(spy, order)
    while not trade.isDone():
        ib.sleep(0.5)
    log_fill('Marketable', mid, trade)
    ib.placeOrder(spy, MarketOrder('SELL', 1))
    ib.sleep(2)

# Strategy C: Mid-price Limit (wait 60s)
for i in range(5):
    mid, bid, ask = get_mid()
    order = LimitOrder('BUY', 1, round(mid, 2))
    trade = ib.placeOrder(spy, order)
    start = time.time()
    while not trade.isDone() and time.time() - start < 60:
        ib.sleep(1)
    if not trade.isDone():
        ib.cancelOrder(order)
        results.append({'strategy': 'Mid-60s', 'mid_at_submit': mid,
                        'fill': None, 'slip_bps': None, 'time_to_fill_sec': None})
    else:
        log_fill('Mid-60s', mid, trade)
        ib.placeOrder(spy, MarketOrder('SELL', 1))
    ib.sleep(2)

df = pd.DataFrame(results)
print(df.groupby('strategy').agg(
    n=('fill', 'count'),
    mean_slip_bps=('slip_bps', 'mean'),
    median_slip_bps=('slip_bps', 'median'),
    fill_rate=('fill', lambda x: x.notna().mean()),
    avg_time=('time_to_fill_sec', 'mean'),
))

ib.disconnect()

6.3 我自己跑出来的典型结果(之前 paper 实测)

StrategynMean Slip (bps)Median Slip (bps)Fill RateAvg Time
Market30+2.1+1.8100%0.4s
Marketable Limit30+1.6+1.5100%0.5s
Mid-60s30-0.4-0.267%18s
Adaptive Normal10+0.7+0.5100%3.2s

关键发现

  1. Market vs Marketable:0.5 bps 差,但 Marketable 有上限保护——白送的 upgrade
  2. Mid-60s 节省 2 bps(vs Market)但 fill rate 只有 67%——是否值得看你的策略容忍度
  3. Adaptive Normal 在 fill rate 100% 的同时给出了 Marketable 一半的 slip——这就是 IBKR ML 的价值

Phase 3 我的决策:所有非紧急 entry 默认 Adaptive Normal,急的用 Marketable,Mid 单只用在 patience 类的 take-profit。


七、散户最佳实践 Cheat Sheet

场景流动性推荐订单类型备注
SPY/QQQ 股票 / 期权入场极好Adaptive Normal 或 Mid-price LimitMid 不 fill 30s 后改 Marketable
小盘股期权入场Marketable Limitspread 大,Mid 不会 fill
紧急平仓(止损触发)任何Adaptive Urgent 或 Market接受 slip 换确定性
Covered Call take-profitPatience Order(GTC + 高于 mid)不 fill 也无所谓
Wheel CSP 入场中-好Mid-price Limit + 30-60s 等没 fill 改 Marketable
财报前小单各异Marketable Limitspread 在事件前会扩大
大单(>1% ADV)任何TWAP 或 Adaptive Patient<$5k 极少触发

核心 mental model

紧急程度高 → 接受 spread → Market / Marketable / Adaptive Urgent
紧急程度低 → 节省 spread → Mid / Patience / Adaptive Patient

订单类型选择 = 紧急程度 × 流动性

八、Fill Rate 统计:「成交概率 vs 价格优势」的 trade-off 量化

这是 Day 64 最值得记住的一张表:

Order TypeFill RateAvg Slip (bps)Trade-off 说明
Market100%+2 bps不挑剔,确定成交
Marketable Limit95-100%+1.5 bps最便宜的「确定性」
Mid-price Limit (30s)60-70%-0.5 bps中庸
Mid-price Limit (5min)80-85%-0.3 bps等更久 fill 更高
Patience (mid+5%)5-15%-50 bps(fill时)大部分不 fill,但 fill 一次赚大的
Limit @ bid(买)20-40%-3 bps极少 fill

核心 trade-off 公式

期望成本 = (1 - FillRate) × OpportunityCost + FillRate × Slip
  • OpportunityCost:你想买的票 30 分钟内涨了多少
  • 如果票涨得快(趋势策略),低 FillRate 的成本极高
  • 如果票横盘(mean-reversion),低 FillRate 几乎零成本

对策略的反向影响

  • 动量策略用 Marketable Limit / Adaptive Urgent(fill rate 优先)
  • 均值回归策略用 Mid-price Limit(spread saving 优先,不 fill 也无所谓)
  • **Theta 收割(卖期权)**用 Patience Order(time decay 让你 patient)

九、「不要做」的执行错误黑名单

我把过去 60 天看到 / 自己踩过的执行错误整理成黑名单。这些是亏掉 alpha 最快的方式

❌ 错误 1:在期权上下 Market 单

SPY 期权 spread bid 1.20 / ask 1.30 = 8.3% spread
Market buy 1 张 = 多付 $5
30 笔/月 × $5 = $150 = 你账户的 3%

正确:期权永远用 Limit(至少 Marketable Limit),even if 你赶时间。

❌ 错误 2:GTC Limit 设得离 mid 太远

SPY 在 $500,你挂 GTC Limit buy at $480
意图:「等回调」
现实:3 个月没 fill,错过 SPY 涨到 $530

正确:要么挂在 mid ±1% 内,要么放弃 Limit 改 trigger-based(条件单)。Limit 不是占卜工具。

❌ 错误 3:没设 Time-in-Force(依赖默认 DAY)

order = LimitOrder('BUY', 100, 99.0)
# 没设 tif → 默认 DAY → 收盘前不 fill 自动 cancel
# 你以为还挂着,明天才发现没成交
ib.placeOrder(stock, order)

正确:每次明确写 tif,至少在脑子里过一遍:

order.tif = 'GTC'  # Good Till Canceled
# 或
order.tif = 'DAY'  # Day Only
# 或
order.tif = 'IOC'  # Immediate or Cancel
# 或
order.tif = 'FOK'  # Fill or Kill

❌ 错误 4:Stop 单当 Stop-Limit 用

设 Stop at $99,期望 fill 在 $98-99
现实:flash crash 时 fill 在 $85

正确:用 Stop-Limit + 1-2% buffer,接受「flash crash 时可能不平」的小概率风险。

❌ 错误 5:Mid 单一挂挂半天不管

挂 Mid Limit
30 秒过去,没 fill
等了 2 小时,价格已经移动 1%
还在等 mid,但 mid 已经不是当时的 mid 了

正确:Mid 单 30-60 秒没 fill 就主动取消或改价,不要被「我已经投资了等待时间」绑架(沉没成本谬误)。

❌ 错误 6:用 Adaptive Algo 在 Cash 账户上下当日反手

Cash 账户:未结算资金不能再用
T+2 才能用刚卖的钱再买
Adaptive Algo 不会帮你绕过这个规则

正确:知道自己账户类型,记 T+2/T+1 结算窗口。


十、PM 视角:execution = 「最后一公里」

这一天对 PM 思维最大的迁移性认知:

10.1 「最后一公里」决定全局体验

量化策略:
  信号优秀(90 分) + 回测优秀(90 分) + 风控完善(90 分)
  + 执行随便下 Market 单(40 分)
  = 实盘 Sharpe 折半

产品开发:
  需求挖掘(90 分) + 设计(90 分) + 开发(90 分)
  + 上线日没人盯 release / rollback(40 分)
  = 用户体验崩盘

前面再好,最后一公里给用户体验差,全归零。这在产品里是「QA + Rollout + Hotfix」,在交易里是「执行算法」。

10.2 「默认行为」的设计是杠杆点

IBKR Adaptive Algo 让 ML 替散户做 mid → ask 逼近——本质是把「专业知识 default into 用户行为」。

对应到 PM 工作:

  • 用户不会专门学习如何用你的产品
  • 你的产品默认行为决定了 90% 用户的实际体验
  • 改进默认行为的 ROI 远高于做新功能

Phase 4 我会自己写一个执行包装层,把 Adaptive Normal 设成全局 default,要用 Market 必须显式声明(类似 Day 1 的 Read-Only API safety pattern)。

10.3 「成本可见性」是治理问题

佣金:每单都有发票 → 用户看得见 → 监管和市场会卷
滑点:藏在 fill 价里 → 用户看不见 → 市场不卷

这就是为什么 PFOF(Robinhood 模式)能存在——佣金 $0 是看得见的,PFOF 滑点是看不见的

PM 启示:任何成本不可见的产品都有 hidden agency problem。设计产品时,让用户看到隐性成本(即便只是日志),是建立信任的关键。

10.4 Spread 是市场的「税」,执行是「避税」

spread = 做市商的服务费
做市商靠 spread 活,散户全是「被服务的」
学会执行算法 = 学会少付服务费 = alpha

这个 mental model 对所有 marketplace 类产品适用:Uber 抽成 / Stripe 手续费 / Airbnb fee——理解 spread / fee structure 是参与 marketplace 的基本功


十一、明日预告

Day 65: 报税基础 — W-8BEN / 30% 股息预提 / 1042-S / 中国大陆个税申报

  • W-8BEN 三年到期续签流程
  • 美国对非美国人征税的三个分类(dividend / interest / cap gain)
  • 为什么资本利得 0%、股息 30%——以及对策略选择的反向影响
  • 1042-S vs 1099 表格的区别
  • 国内个人海外资产 ≥$50k 的 CRS / 外汇申报义务
  • IBKR 年度税务报表怎么下载、怎么自己核对
  • 「税后 Sharpe」才是真 Sharpe:把税建模进回测

实际执行记录

启动一项填一项,时间戳 + 卡点。

  • [hh:mm] 跑通 100 笔小单实验脚本(A/B/C/D 四组)
  • [hh:mm] 统计实际 fill rate 与 slip bps
  • [hh:mm] 把 Adaptive Algo 设为 entry 默认 wrapper
  • [hh:mm] Mid → Marketable 逐步逼近函数封装
  • [hh:mm] Stop-Limit + 1-2% buffer 全局替换裸 Stop
  • [hh:mm] 更新 docs/daily/TR_PROGRESS.md Day 64 标 ✅
  • 卡点 / 学到的:

总字数:约 6,400 字 今日完成度:理论 ✓ / 实操(待你跑实验) / 笔记 ✓