diff --git a/backend/app/api/sim.py b/backend/app/api/sim.py index a66a868..d719a57 100644 --- a/backend/app/api/sim.py +++ b/backend/app/api/sim.py @@ -133,9 +133,14 @@ async def sim_close_group(_user: Annotated[str, Depends(require_user)]) -> dict: status_code=403, detail="未开启「显示手动开仓」;请在策略设置中开启后再用", ) + from ..strategy import get_engine + r = get_executor().close_group(reason="manual") if not r.ok and not r.liquidity_wait: raise HTTPException(status_code=400, detail=r.detail) + if r.ok: + # 与自动/紧急全平一致:成功全平后进入组间休息 + get_engine().enter_rest_after_close() return { "ok": r.ok, "liquidity_wait": r.liquidity_wait, diff --git a/backend/app/live/live_pnl.py b/backend/app/live/live_pnl.py index 34fe95f..d9d56ea 100644 --- a/backend/app/live/live_pnl.py +++ b/backend/app/live/live_pnl.py @@ -72,7 +72,7 @@ def enrich_live_unrealized( logger.warning("live unrealized exchange overlay failed: %s", e) option_upl = float(base.get("option_upl") or 0.0) # 期权净盈亏(本地) - # 保守预估平仓费:按现有名义×费率×2 腿,避免「刚达标、扣费后不够」 + # LIVE 达标口径:必须扣预估平仓手续费(与 SIM / 策略说明一致) try: from ..config import get_settings @@ -89,7 +89,7 @@ def enrich_live_unrealized( + abs(float(base.get("option_qty_eth") or 0.0)) ) est_close = max(0.0, notional_est * fr * 2.0) - # 资金费 signed:付出为负,直接加总 + # 资金费 signed:付出为负;再减估平仓费 net_pnl = perp_upl + option_upl - fees_paid + funding - est_close out = dict(base) diff --git a/backend/app/strategy/engine.py b/backend/app/strategy/engine.py index 65b0827..a0a9153 100644 --- a/backend/app/strategy/engine.py +++ b/backend/app/strategy/engine.py @@ -183,7 +183,7 @@ class StrategyEngine: detail = r.detail close_data = r.data if r.ok: - self._after_close() + self.enter_rest_after_close() elif st in ("open", "option_closed_perp_pending"): # A:双腿(或续平永续) r = self.matcher.close_group(reason="emergency", bypass_liquidity=True) @@ -201,7 +201,7 @@ class StrategyEngine: detail = r.detail close_data = r.data if r.ok: - self._after_close() + self.enter_rest_after_close() residuals = self.matcher.settle_all_residuals_now() return { @@ -214,6 +214,10 @@ class StrategyEngine: "state": self.state(), } + def enter_rest_after_close(self) -> None: + """全平成功后进入组间休息(自动 / 手动 / 紧急共用)。""" + self._after_close() + def _after_close(self) -> None: s = get_settings() row = self.db.fetchone("SELECT * FROM strategy_state WHERE id=1") diff --git a/backend/tests/test_rest_after_close.py b/backend/tests/test_rest_after_close.py new file mode 100644 index 0000000..856b04a --- /dev/null +++ b/backend/tests/test_rest_after_close.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import time + +from app.models.db import Database, set_db +from app.strategy.engine import StrategyEngine + + +def test_enter_rest_after_close_sets_resting(tmp_path) -> None: + db = Database(tmp_path / "rest.db") + set_db(db) + db.set_setting("rest_seconds", "120") + eng = StrategyEngine() + before = int(time.time() * 1000) + eng.enter_rest_after_close() + row = db.fetchone("SELECT * FROM strategy_state WHERE id=1") + assert row is not None + assert row["phase"] == "resting" + assert int(row["rounds_done"] or 0) >= 1 + until = int(row["rest_until_ms"] or 0) + assert until >= before + 100_000 + db.close() diff --git a/docs/实盘策略说明.md b/docs/实盘策略说明.md index 27f92df..ff58451 100644 --- a/docs/实盘策略说明.md +++ b/docs/实盘策略说明.md @@ -24,7 +24,7 @@ | 平仓类 | **目标平仓** A 双腿 / B 只平永续(远虚残留);**到期平仓** | | 模式 | 设置页 SIM/LIVE;切 LIVE 输入 `LIVE`;密钥写入 `.env` | | 同时仓 | 最多 1 组活跃;残留期权不挡新开 | -| **LIVE 盈亏** | 手续费/永续 UPL/已实现/资金费以**交易所**为准;期权持仓浮盈用本地买一算法(期权净盈亏);USDC **1:1** 折 USDT;达标看组净盈亏(含资金费,不含估平仓费) | +| **LIVE 盈亏** | 手续费/永续 UPL/已实现/资金费以**交易所**为准;期权持仓浮盈用本地买一算法(期权净盈亏);USDC **1:1** 折 USDT;达标看组净盈亏(含资金费,**含估平仓手续费**,与 SIM 盯盘一致) | --- diff --git a/docs/更新说明.md b/docs/更新说明.md index 7c53ad3..90dce19 100644 --- a/docs/更新说明.md +++ b/docs/更新说明.md @@ -5,6 +5,19 @@ --- +## 2026-07-26 — LIVE 达标含估平仓费;手动/紧急全平进休息 + +### 变更 + +1. **LIVE 达标口径**:盯盘净利继续扣预估平仓手续费(与策略说明 / SIM 一致);实盘索引改为「含估平仓费」。 +2. **组间休息**:手动全平成功后调用 `enter_rest_after_close()`;紧急全平成功路径同样进入休息(与自动全平共用)。 + +### 测试 + +- `pytest` 全量 + +--- + ## 2026-07-26 — LIVE 对账 / 手动开仓 UI / Token 自动换发 ### 变更 diff --git a/docs/策略说明.md b/docs/策略说明.md index c073b09..31d2e81 100644 --- a/docs/策略说明.md +++ b/docs/策略说明.md @@ -91,7 +91,7 @@ | 开仓时间窗 | **已取消**,全天可尝试(仍受选约约束) | | 周六/周日 | 默认 **跳过新开仓**(上海时区,`skip_weekends`);持仓仍可平 | | 每日轮次上限 | **不限制**;波动大可能多轮,波动小可能一轮都难 | -| 组间休息 | 全平后默认休息 **300 秒**(`rest_seconds`)再开下一组 | +| 组间休息 | 全平后默认休息 **300 秒**(`rest_seconds`)再开下一组;**自动达标/到期、手动全平、紧急全平**成功后均进入休息 | | 同时持仓 | 最多 1 组 | ### 3.4 费用(SIM) @@ -173,7 +173,7 @@ **4.1.B / 到期 / 紧急全平**:不适用「必须卖掉期权买一」这套闸门(到期与残留按内在价值;紧急可绕过)。 -**紧急全平**:活跃组尽量双腿平掉;残留期权一并按内在价值结算。 +**紧急全平**:活跃组尽量双腿平掉;残留期权一并按内在价值结算;成功后进入组间休息(与手动全平相同)。 ### 4.4 其它平仓入口 / 原因码