Enrich WeCom open/close notifies with Chinese qty, margin, and PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 08:58:49 +08:00
parent aab0b9bdf6
commit 0e3b9e53ac
8 changed files with 373 additions and 32 deletions
+158 -25
View File
@@ -27,6 +27,30 @@ _last_fault_key: str | None = None
_last_fault_ms: float = 0.0
_FAULT_DEDUP_SEC = 300.0
CLOSE_REASON_ZH: dict[str, str] = {
"expiry": "到期结算全平",
"target_perp_only": "净盈利达标·只平永续(期权归档到期)",
"fixed_usdt": "固定净盈利达标·双腿全平",
"premium_multiple": "权利金倍数达标·双腿全平",
"emergency": "紧急全平",
"emergency_perp": "紧急·只平永续",
"manual": "手动全平",
"perp_pending_retry": "续平永续",
"liquidity_retry": "等待流动性后全平",
"unknown": "未知原因",
}
BIAS_ZH: dict[str, str] = {
"call_ask_gt_put": "买Call + 永续空",
"put_ask_gt_call": "买Put + 永续多",
"strike_below_spot": "买Call + 永续空",
"strike_above_spot": "买Put + 永续多",
"fixed_long_put": "固定方向·买Put + 永续多",
"fixed_short_call": "固定方向·买Call + 永续空",
"manual_call": "手动·买Call + 永续空",
"manual_put": "手动·买Put + 永续多",
}
def _as_bool(raw: str | None, default: bool = False) -> bool:
if raw is None or raw == "":
@@ -84,6 +108,64 @@ def venue_label() -> str | None:
return "实盘·OKX"
def close_reason_zh(reason: str | None) -> str:
r = str(reason or "").strip()
if not r:
return "未知原因"
return CLOSE_REASON_ZH.get(r, r)
def direction_zh(extra: dict[str, Any]) -> str:
bias = str(extra.get("bias") or "").strip()
if bias in BIAS_ZH:
return BIAS_ZH[bias]
opt = str(extra.get("option_side") or "").strip().lower()
perp = str(extra.get("perp_side") or "").strip().lower()
if opt == "put" and perp == "long":
return "买Put + 永续多"
if opt == "call" and perp == "short":
return "买Call + 永续空"
if opt == "put":
return "买Put"
if opt == "call":
return "买Call"
if bias:
return bias
return ""
def _fmt_num(x: Any, digits: int = 2) -> str:
try:
if x is None or x == "":
return ""
return f"{float(x):.{digits}f}"
except (TypeError, ValueError):
return ""
def _fmt_money(x: Any, *, signed: bool = False) -> str:
try:
if x is None or x == "":
return ""
v = float(x)
if signed:
return f"{v:+.2f}U"
return f"{v:.2f}U"
except (TypeError, ValueError):
return ""
def _pick_float(data: dict[str, Any], *keys: str) -> float | None:
for k in keys:
if k not in data or data[k] is None or data[k] == "":
continue
try:
return float(data[k])
except (TypeError, ValueError):
continue
return None
def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str:
body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or []))
machine = wecom_machine_name()
@@ -175,15 +257,48 @@ def notify_pause() -> None:
def notify_open(*, group_id: str, detail: str = "", extra: dict[str, Any] | None = None) -> None:
extra = extra or {}
extra = dict(extra or {})
perp = extra.get("perp") if isinstance(extra.get("perp"), dict) else {}
option = extra.get("option") if isinstance(extra.get("option"), dict) else {}
perp_qty = _pick_float(extra, "perp_qty_eth") or _pick_float(perp, "qty_eth")
opt_qty = _pick_float(extra, "option_qty_eth") or _pick_float(option, "qty_eth")
premium = _pick_float(extra, "initial_premium", "premium")
margin = _pick_float(extra, "perp_margin", "margin")
leverage = _pick_float(extra, "leverage")
perp_px = _pick_float(extra, "perp_entry_px") or _pick_float(perp, "fill_px")
opt_px = _pick_float(extra, "option_entry_px") or _pick_float(option, "fill_px")
# 缺保证金时用成交价×数量÷杠杆估算
if margin is None and perp_px is not None and perp_qty is not None:
try:
from ..sim.ledger import Ledger
s = get_settings()
lev = float(leverage) if leverage and leverage > 0 else float(
Ledger().get_setting_float("leverage", s.leverage) or s.leverage or 1
)
if lev > 0:
margin = abs(perp_px * perp_qty) / lev
leverage = lev
except Exception:
pass
lines = [
f"**组**: `{group_id}`",
f"**方向**: {extra.get('bias') or extra.get('option_side') or ''}",
f"**期权**: `{extra.get('option_inst_id') or ''}`",
f"**行权/到期**: {extra.get('strike') or ''} / {extra.get('expiry_ymd') or ''}",
f"**组**: `{group_id}`",
f"**方向**: {direction_zh(extra)}",
f"**期权合约**: `{extra.get('option_inst_id') or ''}`",
f"**行权价 / 到期**: {_fmt_num(extra.get('strike'), 0)} / {extra.get('expiry_ymd') or ''}",
f"**开仓数量**: 永续 {_fmt_num(perp_qty, 4)} ETH · 期权 {_fmt_num(opt_qty, 4)} ETH",
f"**成交均价**: 永续 {_fmt_num(perp_px, 4)} · 期权 {_fmt_num(opt_px, 4)}",
f"**权利金占用**: {_fmt_money(premium)}",
f"**保证金占用**: {_fmt_money(margin)}"
+ (f"(杠杆 {_fmt_num(leverage, 0)}x" if leverage else ""),
]
if detail:
lines.append(f"**说明**: {detail}")
# 说明仅在非模板英文码时展示
d = str(detail or "").strip()
if d and d not in ("opened", "opened_live", "ok"):
lines.append(f"**说明**: {d}")
notify_async(build_markdown(tag=TAG_OPEN, title="开仓成功", lines=lines))
@@ -194,26 +309,44 @@ def notify_close(
group_id: str | None = None,
data: dict[str, Any] | None = None,
) -> None:
data = data or {}
reason_zh = {
"expiry": "到期平仓",
"target_perp_only": "目标平仓·只平永续",
"fixed_usdt": "目标平仓·双腿",
"premium_multiple": "目标平仓·双腿",
"emergency": "紧急全平",
"emergency_perp": "紧急·只平永续",
"manual": "手动全平",
"perp_pending_retry": "续平永续",
}.get(reason, reason)
data = dict(data or {})
reason_zh = close_reason_zh(reason)
gid = group_id or data.get("group_id") or ""
perp_pnl = _pick_float(data, "perp_pnl")
opt_pnl = _pick_float(data, "option_pnl", "opt_pnl")
net = _pick_float(data, "net", "net_pnl", "interim_net", "realized_pnl")
# 只平永续时 interim_net 可能是净利口径
if data.get("option_abandoned") and opt_pnl is None:
opt_note = "期权已归档,待到期结算(本组未计入期权最终盈亏)"
else:
opt_note = None
lines = [
f"**原因**: {reason_zh} (`{reason}`)",
f"****: `{group_id or data.get('group_id') or ''}`",
f"**组号**: `{gid}`",
f"**平仓方式**: {reason_zh}",
f"**永续盈亏**: {_fmt_money(perp_pnl, signed=True)}",
f"**期权盈亏**: {_fmt_money(opt_pnl, signed=True)}",
f"**净利润**: {_fmt_money(net, signed=True)}",
]
if detail:
lines.append(f"**说明**: {detail}")
net = data.get("net_pnl")
if net is not None:
lines.append(f"**净盈亏**: {net}")
if opt_note:
lines.append(f"**备注**: {opt_note}")
fees = _pick_float(data, "fees", "fees_total")
if fees is None:
fo = _pick_float(data, "fees_open")
fc = _pick_float(data, "fees_close")
if fo is not None or fc is not None:
fees = (fo or 0.0) + (fc or 0.0)
if fees is not None:
lines.append(f"**手续费合计**: {_fmt_money(fees)}")
d = str(detail or "").strip()
if d and d not in (
"closed",
"perp_closed_option_residual",
"ok",
"manual",
):
lines.append(f"**说明**: {d}")
notify_async(build_markdown(tag=TAG_CLOSE, title=f"平仓 · {reason_zh}", lines=lines))