V5.4 执行层状态机设计
V5.4 Execution State Machine
本文档描述 V5.4 策略的执行层状态机设计,重点是 maker/taker 组合策略,确保在不牺牲入场/出场时效性的前提下,最大程度降低手续费与滑点摩擦。
设计目标:
- 将"信号质量"和"执行质量"解耦,执行层只负责:更便宜、更稳定地实现既定 TP/SL/flip/timeout 规则。
- 入场阶段在不丢失大行情的前提下尽量使用 maker;
- 出场阶段 TP 强制 maker 主路径,同时用 taker 做安全兜底;
- SL 始终使用 taker,优先保证风控。
1. 入场状态机(Entry State Machine)
1.1 状态定义
IDLE:无持仓、无挂单,等待信号。ENTRY_PENDING_MAKER:已下入场限价挂单(post-only),等待成交或超时。ENTRY_FILL:入场成交完成(全仓或部分)。ENTRY_FALLBACK_TAKER:超时后使用 taker 市价单补齐未成交部分。
1.2 关键参数
entry_price_signal:信号引擎给出的入场参考价(通常为最新价或中间价 mid)。tick_size:交易所最小价格步长。entry_offset_ticks:maker 入场挂单相对盘口的偏移(通常为 1–2 个 tick)。entry_timeout_ms:入场 maker 挂单最大等待时间(如 3000–5000ms)。entry_fallback_slippage_bps:fallback taker 允许的最大滑点(基础保护,超出则放弃补仓或缩小仓位)。
1.3 状态机伪代码
state = IDLE
on_signal_open(signal):
if state != IDLE:
return // 避免重复入场
// 计算 maker 挂单价格
side = signal.side // LONG or SHORT
ref_price = best_bid_ask_mid()
if side == LONG:
entry_price_maker = min(ref_price, best_bid() + entry_offset_ticks * tick_size)
else: // SHORT
entry_price_maker = max(ref_price, best_ask() - entry_offset_ticks * tick_size)
// 下 post-only 入场挂单
order_id = place_limit_post_only(side, entry_price_maker, target_size)
entry_start_ts = now()
state = ENTRY_PENDING_MAKER
on_timer():
if state == ENTRY_PENDING_MAKER:
if order_filled(order_id):
filled_size = get_filled_size(order_id)
if filled_size >= min_fill_ratio * target_size:
state = ENTRY_FILL
return
if now() - entry_start_ts >= entry_timeout_ms:
// 超时,取消剩余挂单
cancel_order(order_id)
remaining_size = target_size - get_filled_size(order_id)
if remaining_size <= 0:
state = ENTRY_FILL
return
// 兜底:按容忍滑点发市价单
mkt_price = best_bid_ask_mid()
theoretical_price = ref_price_at_signal
slippage_bps = abs(mkt_price - theoretical_price) / theoretical_price * 10000
if slippage_bps <= entry_fallback_slippage_bps:
place_market_order(side, remaining_size)
state = ENTRY_FILL
else:
// 滑点过大,放弃补仓或缩减仓位
state = ENTRY_FILL // 仅保留已成交部分2. 出场状态机(TP/SL/Flip/Timeout)
出场分为四类:TP(止盈)、SL(止损)、flip(信号翻转)、timeout(超时退出)。
2.1 通用状态
POSITION_OPEN:持仓打开,已根据策略下好 TP/SL 限价单。TP_PENDING_MAKER:TP 限价挂单等待成交。TP_FALLBACK_TAKER:TP 越价未成交时,撤单+市价平仓兜底。SL_PENDING:止损触发,直接发送 taker 单。FLIP_PENDING:翻转触发,先平仓再反向开仓(可复用入场状态机)。TIMEOUT_PENDING:超时触发,按策略规则离场(可偏 maker)。
2.2 关键参数
tp1_r,tp2_r:TP1/TP2 的目标 R 距离(如 1.0R / 2.0R)。sl_r:止损距离(如 -1.0R)。tp_timeout_ms:价格越过 TP 水平后,TP 限价未成交的允许时间窗口。flip_threshold:翻转触发条件(score + OBI + VWAP 等综合判断)。timeout_seconds:最大持仓时间,用于 timeout 出场。
2.3 TP 状态机(maker 主路径 + taker 兜底)
on_position_open(pos):
// 开仓后立即挂 TP1 限价单(maker)
tp1_price = pos.entry_price + pos.side * tp1_r * pos.risk_distance
tp2_price = pos.entry_price + pos.side * tp2_r * pos.risk_distance
// 半仓挂 TP1,半仓挂 TP2
tp1_id = place_limit_post_only(exit_side(pos.side), tp1_price, pos.size * 0.5)
tp2_id = place_limit_post_only(exit_side(pos.side), tp2_price, pos.size * 0.5)
pos.state = POSITION_OPEN
on_timer():
if pos.state == POSITION_OPEN:
current_price = best_bid_ask_mid()
// 检查 TP1 越价兜底
tp1_crossed = (pos.side == LONG and current_price >= tp1_price) or
(pos.side == SHORT and current_price <= tp1_price)
if tp1_crossed and not pos.tp1_cross_ts:
pos.tp1_cross_ts = now()
if pos.tp1_cross_ts:
if order_filled(tp1_id):
pos.tp1_cross_ts = None // 成交,清除计时
elif now() - pos.tp1_cross_ts >= tp_timeout_ms:
cancel_order(tp1_id)
remaining = size_tp1 - get_filled_size(tp1_id)
if remaining > 0:
place_market_order(exit_side(pos.side), remaining)
// 检查 TP2 越价兜底
tp2_crossed = (pos.side == LONG and current_price >= tp2_price) or
(pos.side == SHORT and current_price <= tp2_price)
if tp2_crossed and not pos.tp2_cross_ts:
pos.tp2_cross_ts = now()
if pos.tp2_cross_ts:
if order_filled(tp2_id):
pos.tp2_cross_ts = None
elif now() - pos.tp2_cross_ts >= tp_timeout_ms:
cancel_order(tp2_id)
remaining = size_tp2 - get_filled_size(tp2_id)
if remaining > 0:
place_market_order(exit_side(pos.side), remaining)
// 检查是否已全部平仓
if pos_size(pos) <= 0:
pos.state = CLOSED2.4 SL 状态机(纯 taker)
on_sl_trigger(pos, sl_price):
// 触发条件可以来自价格监控或止损订单触发
// 这里策略层只关心:一旦触发,立即使用 taker
close_size = pos_size(pos)
if close_size > 0:
place_market_order(exit_side(pos.side), close_size)
pos.state = CLOSEDSL 不做 maker 逻辑,避免在极端行情下挂单无法成交。
2.5 Flip 状态机(平旧仓 + 新开仓)
on_flip_signal(pos, new_side, flip_context):
if not flip_condition_met(flip_context):
return
// flip 条件:score < 85 AND OBI 翻转 AND 价格跌破 VWAP(三条件同时满足)
// flip_condition_met 由信号引擎判断
// 1) 先平旧仓(按 SL 逻辑,优先 taker)
close_size = pos_size(pos)
if close_size > 0:
place_market_order(exit_side(pos.side), close_size)
pos.state = CLOSED
// 2) 再按入场状态机开新仓
new_signal = build_signal_from_flip(new_side, flip_context)
entry_state_machine.on_signal_open(new_signal)flip 的关键是:门槛更高(如 score < 85 且 OBI 翻转且价格跌破 VWAP),尽量减少在震荡行情中来回打脸。
2.6 Timeout 状态机(超时出场)
on_timer():
if pos.state == POSITION_OPEN and now() - pos.open_ts >= timeout_seconds:
// 可以偏 maker:先挂限价平仓,超时再 taker
timeout_price = best_bid_ask_mid()
size = pos_size(pos)
oid = place_limit_post_only(exit_side(pos.side), timeout_price, size)
pos.timeout_order_id = oid
pos.timeout_start_ts = now()
pos.state = TIMEOUT_PENDING
if pos.state == TIMEOUT_PENDING:
if order_filled(pos.timeout_order_id):
pos.state = CLOSED
elif now() - pos.timeout_start_ts >= timeout_grace_ms:
cancel_order(pos.timeout_order_id)
remaining = pos_size(pos)
if remaining > 0:
place_market_order(exit_side(pos.side), remaining)
pos.state = CLOSED3. 监控指标(执行层 KPI)
| 指标 | 说明 | 目标 |
|---|---|---|
maker_ratio_entry | 入场成交中 maker 比例 | ≥ 50% |
maker_ratio_tp | TP 成交中 maker 比例 | ≥ 80% |
avg_friction_cost_r | 每笔平均摩擦成本(手续费+滑点,以 R 计) | ≤ 0.15R |
entry_timeout_rate | 入场超时触发 taker 兜底比例 | ≤ 30% |
tp_overshoot_rate | TP 越价后兜底比例 | ≤ 20% |
flip_frequency | 每笔持仓中 flip 次数 | ≤ 1次/持仓 |
4. 设计原则汇总
| 场景 | 主路径 | 兜底 |
|---|---|---|
| 入场 | limit post-only(盘口内侧 1-2 tick) | 超时 → taker(滑点容忍内) |
| TP1 / TP2 | limit post-only(预挂) | 越价 X ms 未成交 → 撤单 + taker |
| SL | — | 纯 taker,立即执行 |
| Flip | 平仓用 taker,新开仓复用入场逻辑 | — |
| Timeout | limit post-only | grace period 后 → taker |
标签:
#EXECUTION-MAKER-TAKER
状态:V5.4 设计文档,待 3/11 A/B test 结束后进入实现阶段
作者:小范(xiaofan)+ 露露(lulu)
日期:2026-03-06