diff --git a/backend/app/strategy/engine.py b/backend/app/strategy/engine.py index dbf9a0d..f0010ee 100644 --- a/backend/app/strategy/engine.py +++ b/backend/app/strategy/engine.py @@ -93,6 +93,8 @@ class StrategyEngine: max_atm_off = self.ledger.get_setting_float( "max_atm_open_offset", s.max_atm_open_offset ) + perp_qty = self.ledger.get_setting_float("perp_qty_eth", s.perp_qty_eth) + opt_qty = self.ledger.get_setting_float("option_qty_eth", s.option_qty_eth) rest_until = row["rest_until_ms"] rest_left = 0 if rest_until: @@ -128,6 +130,8 @@ class StrategyEngine: "premium_exit_multiple": prem_mult, "exit_target_usdt": exit_amt, "leverage": leverage, + "perp_qty_eth": perp_qty, + "option_qty_eth": opt_qty, "min_option_hours": min_hours, "min_option_leverage": min_opt_lev, "atm_open_offset_enabled": atm_off_on, diff --git a/backend/app/strategy/open_capacity.py b/backend/app/strategy/open_capacity.py index 990976c..7800bb9 100644 --- a/backend/app/strategy/open_capacity.py +++ b/backend/app/strategy/open_capacity.py @@ -171,13 +171,16 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]: def maybe_notify_funds_short(cap: dict[str, Any] | None = None) -> None: - """交易账户资金不足时企业微信推送:不足期间只推一次。""" + """仅在「不能开」时推送一次;能开绝不通知。""" global _notified_while_short cap = cap or assess_open_capacity() - short = cap.get("perp_can_open") is False or cap.get("option_can_open") is False - if not short: + # 能开 / 未知:不通知;资金恢复则重置,便于下次不足再提醒一次 + if cap.get("funds_ok") is True: _notified_while_short = False return + cannot = cap.get("perp_can_open") is False or cap.get("option_can_open") is False + if not cannot: + return if _notified_while_short: return _notified_while_short = True diff --git a/docs/更新说明.md b/docs/更新说明.md index f22810e..82a9886 100644 --- a/docs/更新说明.md +++ b/docs/更新说明.md @@ -5,6 +5,16 @@ --- +## 2026-07-29 — 规则说明展示策略数值;导航加大;不可开才通知 + +### 变更 + +1. 能开不通知;不能开才企业微信提醒一次(恢复后再次不足才再推)。 +2. 导航字号加大;规则说明正文字号缩小。 +3. 规则说明按当前策略设置展示开仓/平仓/节奏参数。 + +--- + ## 2026-07-29 — 可开判定只看交易账户;不足只通知一次;去重标题 ### 变更 diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index d85982e..b88abb3 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -228,6 +228,8 @@ export type PlanState = { premium_exit_multiple: number; exit_target_usdt: number; leverage: number; + perp_qty_eth?: number; + option_qty_eth?: number; min_option_hours: number; min_option_leverage: number; atm_open_offset_enabled?: boolean; diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index b820bd5..ac92285 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -149,6 +149,13 @@ export default function PlanPage() { ? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}` : `固定 ${fmt(plan?.net_profit_target ?? 15)} U`; const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—"; + const atmRule = plan?.atm_open_offset_enabled + ? `ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}` + : "ATM偏差关"; + const exitDetail = + exitMode === "premium_multiple" + ? `净盈利≥初始权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}` + : `净盈利≥${fmt(plan?.net_profit_target ?? 15)} U`; const expiryMs = pos?.expiry_ms ?? expiryMsFromYmd(pos?.expiry_ymd) ?? null; const countdown = @@ -213,10 +220,28 @@ export default function PlanPage() {
规则说明 -

- 行情 {String(plan?.exchange || "okx").toUpperCase()} · ETH-USDT-SWAP · - 目标平仓(双腿/远虚只平永续) · 未达标则到期结算 -

+
+

+ 行情 + {String(plan?.exchange || "okx").toUpperCase()} · ETH-USDT-SWAP +

+

+ 开仓 + 永续 {fmt(plan?.perp_qty_eth ?? 1, 2)} ETH / {fmt(plan?.leverage, 0)}x + · 期权 {fmt(plan?.option_qty_eth ?? 2, 2)} ETH 名义 · 剩余≥ + {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥ + {fmt(plan?.min_option_leverage, 0)}x · {atmRule} + {plan?.skip_weekends ? " · 周末跳过开仓" : ""} +

+

+ 平仓 + {exitDetail} → 双腿全平;期权远虚则只平永续、期权到期结算;未达标拖到期 +

+

+ 节奏 + 组间休息 {plan?.rest_seconds ?? "—"}s · 可开判定看交易账户 +

+
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 292caee..876300e 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -149,13 +149,14 @@ input { } .top-nav a { - padding: 6px 10px; + padding: 8px 14px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elev); color: var(--muted); text-decoration: none; - font-size: 13px; + font-size: 15px; + font-weight: 600; white-space: nowrap; } @@ -307,7 +308,7 @@ input { .plan-rules-fold summary { cursor: pointer; color: var(--muted); - font-size: 13px; + font-size: 12px; user-select: none; } @@ -317,7 +318,26 @@ input { .plan-rules-body { margin: 8px 0 0; - line-height: 1.45; + font-size: 11px; + line-height: 1.5; + color: var(--muted); +} + +.plan-rules-body p { + margin: 0 0 4px; +} + +.plan-rules-body p:last-child { + margin-bottom: 0; +} + +.plan-rules-k { + display: inline-block; + min-width: 2.2em; + margin-right: 6px; + color: var(--text); + font-weight: 600; + opacity: 0.85; } .open-cap-row { @@ -485,7 +505,7 @@ input { min-height: 48px; text-decoration: none; color: var(--muted); - font-size: 13px; + font-size: 14px; font-weight: 600; border-radius: 8px; }