Notify funds short once; check trading only; drop duplicate plan title.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -114,11 +114,6 @@ class StrategyEngine:
|
|||||||
"funds_ok": False,
|
"funds_ok": False,
|
||||||
"leverage": leverage,
|
"leverage": leverage,
|
||||||
}
|
}
|
||||||
if open_cap.get("perp_can_open") is False or open_cap.get("option_can_open") is False:
|
|
||||||
try:
|
|
||||||
maybe_notify_funds_short(open_cap)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return {
|
return {
|
||||||
"running": bool(row["running"]),
|
"running": bool(row["running"]),
|
||||||
"phase": row["phase"],
|
"phase": row["phase"],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""开仓资金可开判定:永续保证金 + 期权权利金。"""
|
"""开仓资金可开判定:只看交易账户(永续 USDT / 期权 USDC)。"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -16,9 +16,8 @@ logger = logging.getLogger(__name__)
|
|||||||
_live_bal_cache: dict[str, Any] = {"ts": 0.0, "data": None}
|
_live_bal_cache: dict[str, Any] = {"ts": 0.0, "data": None}
|
||||||
_LIVE_BAL_TTL_SEC = 8.0
|
_LIVE_BAL_TTL_SEC = 8.0
|
||||||
|
|
||||||
_last_notify_key: str | None = None
|
# 不足期间只推一次;资金恢复后清零,下次再不足可再推一次
|
||||||
_last_notify_ms: float = 0.0
|
_notified_while_short: bool = False
|
||||||
_NOTIFY_DEDUP_SEC = 600.0 # 同状态 10 分钟内不重复推
|
|
||||||
|
|
||||||
|
|
||||||
def _f(v: Any) -> float | None:
|
def _f(v: Any) -> float | None:
|
||||||
@@ -57,9 +56,7 @@ def _live_balances() -> dict[str, float | None]:
|
|||||||
if _live_bal_cache["data"] is not None and now - float(_live_bal_cache["ts"]) < _LIVE_BAL_TTL_SEC:
|
if _live_bal_cache["data"] is not None and now - float(_live_bal_cache["ts"]) < _LIVE_BAL_TTL_SEC:
|
||||||
return dict(_live_bal_cache["data"])
|
return dict(_live_bal_cache["data"])
|
||||||
out: dict[str, float | None] = {
|
out: dict[str, float | None] = {
|
||||||
"funding_usdt": None,
|
|
||||||
"trading_usdt": None,
|
"trading_usdt": None,
|
||||||
"funding_usdc": None,
|
|
||||||
"trading_usdc": None,
|
"trading_usdc": None,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
@@ -68,8 +65,8 @@ def _live_balances() -> dict[str, float | None]:
|
|||||||
client = OkxFundsClient()
|
client = OkxFundsClient()
|
||||||
try:
|
try:
|
||||||
bal = client.fetch_balances()
|
bal = client.fetch_balances()
|
||||||
for k in out:
|
out["trading_usdt"] = _f(bal.get("trading_usdt"))
|
||||||
out[k] = _f(bal.get(k))
|
out["trading_usdc"] = _f(bal.get("trading_usdc"))
|
||||||
finally:
|
finally:
|
||||||
client.close()
|
client.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -80,31 +77,21 @@ def _live_balances() -> dict[str, float | None]:
|
|||||||
|
|
||||||
|
|
||||||
def _sim_balances(db: Database) -> dict[str, float]:
|
def _sim_balances(db: Database) -> dict[str, float]:
|
||||||
|
"""只看交易账户,不看资金账户。"""
|
||||||
w = SimFundsWallets(db).view()
|
w = SimFundsWallets(db).view()
|
||||||
led = Ledger(db).snapshot()
|
|
||||||
avail = float(led.get("available") or 0)
|
|
||||||
trading_usdt = float(w.get("trading_usdt") or 0)
|
|
||||||
trading_usdc = float(w.get("trading_usdc") or 0)
|
|
||||||
# 未划转到交易账户时回退账本可用(兼容旧 SIM)
|
|
||||||
if trading_usdt < 1e-9 and trading_usdc < 1e-9 and avail > 1e-9:
|
|
||||||
return {
|
|
||||||
"perp_usdt": avail,
|
|
||||||
"option_usdc": avail,
|
|
||||||
"ledger_available": avail,
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
"perp_usdt": trading_usdt,
|
"perp_usdt": float(w.get("trading_usdt") or 0),
|
||||||
"option_usdc": trading_usdc,
|
"option_usdc": float(w.get("trading_usdc") or 0),
|
||||||
"ledger_available": avail,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
|
def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
返回永续/期权是否有足够资金开新仓。
|
返回永续/期权是否有足够交易账户资金开新仓。
|
||||||
- 永续:需 USDT >= 名义/杠杆
|
- 永续:交易账户 USDT >= 名义/杠杆
|
||||||
- 期权:需 USDC(或 SIM 回退可用) >= 卖一×名义×(1+费率)
|
- 期权:交易账户 USDC >= 卖一×名义×(1+费率)
|
||||||
"""
|
"""
|
||||||
|
global _notified_while_short
|
||||||
db = db or get_db()
|
db = db or get_db()
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
ledger = Ledger(db)
|
ledger = Ledger(db)
|
||||||
@@ -127,7 +114,6 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
|
|||||||
have_opt = float(bal["option_usdc"])
|
have_opt = float(bal["option_usdc"])
|
||||||
else:
|
else:
|
||||||
live = _live_balances()
|
live = _live_balances()
|
||||||
# OKX:永续与期权都在交易账户
|
|
||||||
t_usdt = live.get("trading_usdt")
|
t_usdt = live.get("trading_usdt")
|
||||||
t_usdc = live.get("trading_usdc")
|
t_usdc = live.get("trading_usdc")
|
||||||
have_perp = float(t_usdt) if t_usdt is not None else None
|
have_perp = float(t_usdt) if t_usdt is not None else None
|
||||||
@@ -145,6 +131,11 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
|
|||||||
else:
|
else:
|
||||||
opt_ok = have_opt + 1e-9 >= premium_need
|
opt_ok = have_opt + 1e-9 >= premium_need
|
||||||
|
|
||||||
|
funds_ok = perp_ok is True and opt_ok is True
|
||||||
|
# 资金恢复后允许下次不足再通知一次
|
||||||
|
if funds_ok:
|
||||||
|
_notified_while_short = False
|
||||||
|
|
||||||
lev_i = int(round(lev)) if abs(lev - round(lev)) < 1e-9 else lev
|
lev_i = int(round(lev)) if abs(lev - round(lev)) < 1e-9 else lev
|
||||||
if perp_ok is True:
|
if perp_ok is True:
|
||||||
perp_label = f"永续{lev_i}x 可开"
|
perp_label = f"永续{lev_i}x 可开"
|
||||||
@@ -174,43 +165,43 @@ def assess_open_capacity(db: Database | None = None) -> dict[str, Any]:
|
|||||||
"option_can_open": opt_ok,
|
"option_can_open": opt_ok,
|
||||||
"perp_label": perp_label,
|
"perp_label": perp_label,
|
||||||
"option_label": opt_label,
|
"option_label": opt_label,
|
||||||
"funds_ok": (perp_ok is True and opt_ok is True),
|
"funds_ok": funds_ok,
|
||||||
|
"source": "trading",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def maybe_notify_funds_short(cap: dict[str, Any] | None = None) -> None:
|
def maybe_notify_funds_short(cap: dict[str, Any] | None = None) -> None:
|
||||||
"""资金不足时企业微信推送(去重)。"""
|
"""交易账户资金不足时企业微信推送:不足期间只推一次。"""
|
||||||
global _last_notify_key, _last_notify_ms
|
global _notified_while_short
|
||||||
cap = cap or assess_open_capacity()
|
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:
|
||||||
|
_notified_while_short = False
|
||||||
|
return
|
||||||
|
if _notified_while_short:
|
||||||
|
return
|
||||||
|
_notified_while_short = True
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
if cap.get("perp_can_open") is False:
|
if cap.get("perp_can_open") is False:
|
||||||
parts.append(
|
parts.append(
|
||||||
f"永续不足:需约 {cap.get('perp_need_usdt')}U,现有 {cap.get('perp_have_usdt')}U"
|
f"交易账户 USDT 不足:需约 {cap.get('perp_need_usdt')}U,现有 {cap.get('perp_have_usdt')}U"
|
||||||
)
|
)
|
||||||
if cap.get("option_can_open") is False:
|
if cap.get("option_can_open") is False:
|
||||||
parts.append(
|
parts.append(
|
||||||
f"期权不足:需约 {cap.get('option_need_usdc')}U,现有 {cap.get('option_have_usdc')}U"
|
f"交易账户 USDC 不足:需约 {cap.get('option_need_usdc')}U,现有 {cap.get('option_have_usdc')}U"
|
||||||
)
|
)
|
||||||
if not parts:
|
|
||||||
return
|
|
||||||
key = "|".join(parts)
|
|
||||||
now = time.time()
|
|
||||||
if key == _last_notify_key and now - _last_notify_ms < _NOTIFY_DEDUP_SEC:
|
|
||||||
return
|
|
||||||
_last_notify_key = key
|
|
||||||
_last_notify_ms = now
|
|
||||||
try:
|
try:
|
||||||
from ..notify import wecom
|
from ..notify import wecom
|
||||||
|
|
||||||
wecom.notify_async(
|
wecom.notify_async(
|
||||||
wecom.build_markdown(
|
wecom.build_markdown(
|
||||||
tag=wecom.TAG_FAULT,
|
tag=wecom.TAG_FAULT,
|
||||||
title="资金不足 · 无法开新仓",
|
title="交易账户资金不足 · 无法开新仓",
|
||||||
lines=[
|
lines=[
|
||||||
f"**永续**: {cap.get('perp_label')}",
|
f"**永续**: {cap.get('perp_label')}",
|
||||||
f"**期权**: {cap.get('option_label')}",
|
f"**期权**: {cap.get('option_label')}",
|
||||||
*[f"**详情**: {p}" for p in parts],
|
*[f"**详情**: {p}" for p in parts],
|
||||||
"请划转/兑换后重试。",
|
"请从资金账户划转到交易账户后重试。",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,16 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-29 — 可开判定只看交易账户;不足只通知一次;去重标题
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 权益/杠杆可开判定仅看**交易账户** USDT/USDC(不再回退资金账户)。
|
||||||
|
2. 资金不足企业微信:**不足期间只推一次**,恢复后再次不足才再推;计划页轮询不再触发推送。
|
||||||
|
3. 去掉计划页「自动对冲计划」标题(与导航重复)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-29 — 资金条:USDT/USDC 两行 + 居中对齐
|
## 2026-07-29 — 资金条:USDT/USDC 两行 + 居中对齐
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
@@ -166,10 +166,6 @@ export default function PlanPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="plan-page">
|
<div className="plan-page">
|
||||||
<div className="plan-desktop-head">
|
|
||||||
<h2 style={{ marginTop: 0 }}>自动对冲计划</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 手机端一屏监控卡:状态 / 净利 / 倒计时 */}
|
{/* 手机端一屏监控卡:状态 / 净利 / 倒计时 */}
|
||||||
<section className="plan-mobile-hero" aria-label="监控摘要">
|
<section className="plan-mobile-hero" aria-label="监控摘要">
|
||||||
<div className="plan-mobile-hero-top">
|
<div className="plan-mobile-hero-top">
|
||||||
|
|||||||
Reference in New Issue
Block a user