diff --git a/docs/arbitrage-engine/execution-state-machine.md b/docs/arbitrage-engine/execution-state-machine.md new file mode 100644 index 0000000..90aa513 --- /dev/null +++ b/docs/arbitrage-engine/execution-state-machine.md @@ -0,0 +1,251 @@ +# 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 状态机伪代码 + +```pseudo +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 兜底) + +```pseudo +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 = CLOSED +``` + +### 2.4 SL 状态机(纯 taker) + +```pseudo +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 = CLOSED +``` + +SL 不做 maker 逻辑,避免在极端行情下挂单无法成交。 + +### 2.5 Flip 状态机(平旧仓 + 新开仓) + +```pseudo +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 状态机(超时出场) + +```pseudo +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 = CLOSED +``` + +--- + +## 3. 监控指标(执行层 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