diff --git a/backend/app/sim/matcher.py b/backend/app/sim/matcher.py index aaa73c9..5e6373b 100644 --- a/backend/app/sim/matcher.py +++ b/backend/app/sim/matcher.py @@ -270,6 +270,7 @@ class Matcher: """ 全平一组。默认校验期权买一深度 + 买一/标记偏差(默认≤30%)。 bypass_liquidity=True:紧急全平可绕过(仍需有可用买一价才能成交;无买一时用标记近似)。 + 成交顺序:先平期权(薄)→ 再瞬时平永续(对冲先留着);永续盘口失败则回滚期权入账。 """ s = get_settings() pos = self.current_position() @@ -329,14 +330,7 @@ class Matcher: perp_entry = float(pos["perp_entry_px"]) opt_entry = float(pos["option_entry_px"]) - pf = perp_fill( - side=perp_side, - action="close", - bid=float(snap.perp.bid), - ask=float(snap.perp.ask), - qty_eth=perp_qty, - fee_rate=fee_rate, - ) + # 1) 先平期权(买一流动性差);永续对冲暂留 of = option_fill( action="close", bid=float(close_bid), @@ -344,50 +338,53 @@ class Matcher: qty_eth=opt_qty, fee_rate=fee_rate, ) + opt_pnl = (of.fill_px - opt_entry) * opt_qty + opt_cash = of.notional - of.fee + self.ledger.apply_cash( + opt_cash, + kind="close_option", + group_id=group_id, + note=f"close option {reason}", + ) - # 永续盈亏 + # 2) 期权确认后再瞬时平永续(重取盘口) + snap2 = sess.snapshot() + if not snap2.perp or snap2.perp.bid is None or snap2.perp.ask is None: + self.ledger.apply_cash( + -opt_cash, + kind="close_option_rollback", + group_id=group_id, + note=f"rollback option close {group_id}: perp book missing", + ) + return CloseResult( + ok=False, + detail="期权已平但永续盘口不可用,已回滚期权入账", + ) + + pf = perp_fill( + side=perp_side, + action="close", + bid=float(snap2.perp.bid), + ask=float(snap2.perp.ask), + qty_eth=perp_qty, + fee_rate=fee_rate, + ) if perp_side == "long": perp_pnl = (pf.fill_px - perp_entry) * perp_qty else: perp_pnl = (perp_entry - pf.fill_px) * perp_qty - # 期权多头盈亏 - opt_pnl = (of.fill_px - opt_entry) * opt_qty - cash_in = of.notional - of.fee + pf.fee * 0 # 收回权利金(扣卖出费);永续平仓费另扣 - # 永续平仓:实现盈亏入账并扣平仓手续费 - net = perp_pnl + opt_pnl - pf.fee - of.fee - # 更清晰:现金变动 = 期权卖出净额 + 永续盈亏 - 永续平仓费 - # 开仓已付期权权利金+开仓费;平仓收回 of.notional 并付 of.fee;永续只记 pnl 与 fee - cash_delta = (of.notional - of.fee) + perp_pnl - pf.fee - self.ledger.apply_cash( - cash_delta, - kind="close_settle", + perp_pnl - pf.fee, + kind="close_perp", group_id=group_id, - note=f"close {reason}", + note=f"close perp {reason}", ) + net = perp_pnl + opt_pnl - pf.fee - of.fee + now = int(time.time() * 1000) with self.db._lock: - self.db._conn.execute( - """INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth, qty_contracts, - base_px, fill_px, fee, slip, notional, ts_ms) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)""", - ( - group_id, - "perp", - "close", - "flat", - s.perp_inst_id, - perp_qty, - None, - pf.base_px, - pf.fill_px, - pf.fee, - pf.slip, - pf.notional, - now, - ), - ) + # 成交顺序:期权先、永续后 self.db._conn.execute( """INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth, qty_contracts, base_px, fill_px, fee, slip, notional, ts_ms) @@ -408,6 +405,26 @@ class Matcher: now, ), ) + self.db._conn.execute( + """INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth, qty_contracts, + base_px, fill_px, fee, slip, notional, ts_ms) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)""", + ( + group_id, + "perp", + "close", + "flat", + s.perp_inst_id, + perp_qty, + None, + pf.base_px, + pf.fill_px, + pf.fee, + pf.slip, + pf.notional, + now + 1, + ), + ) g = self.db._conn.execute( "SELECT fees, slip_cost FROM groups WHERE group_id=?", (group_id,) ).fetchone() @@ -436,7 +453,8 @@ class Matcher: "perp_pnl": perp_pnl, "option_pnl": opt_pnl, "net": net, - "cash_delta": cash_delta, + "close_sequence": ["option", "perp"], + "cash_delta": opt_cash + perp_pnl - pf.fee, }, ) diff --git a/docs/策略说明.md b/docs/策略说明.md index 40988fc..8b4be37 100644 --- a/docs/策略说明.md +++ b/docs/策略说明.md @@ -32,6 +32,8 @@ **开仓顺序**:先成交期权 → 确认后再市价成交永续。永续盘口失败则回滚期权扣款,不留半边仓。 +**平仓顺序**:同样先平期权(买一薄)→ 再瞬时平永续。平期权期间永续对冲先留着;永续盘口失败则回滚期权入账。 + 永续杠杆默认 **3×**(可配)。同时最多 **1 组**仓,禁止叠仓开下一组。 ### 2.1 开仓方向(ATM 相对现价优先) @@ -99,7 +101,7 @@ ## 4. 平仓机制 -任一触发 → **该组永续 + 期权全部平掉**。 +任一触发 → **该组永续 + 期权全部平掉**(先期权、后永续)。 ### 4.1 净盈利达标(主出场) @@ -254,3 +256,4 @@ | 日期 | 说明 | |------|------| | 2026-07-25 | 初稿:对齐当前开平仓、周末跳过、到期全平、净盈利口径与资金建议 | +| 2026-07-25 | 平仓顺序改为先期权后永续(与开仓同理:薄腿优先) |