"""对冲计划企业微信推送(起止必发,幂等落库标记).""" from __future__ import annotations from typing import Any, Callable, Optional from lib.hedge_plan.hedge_plan_db import update_plan def _fmt(v: Any, d: int = 2) -> str: try: if v is None or v == "": return "—" return f"{float(v):.{d}f}" except (TypeError, ValueError): return str(v) def _type_label(plan_type: str) -> str: return "永期对冲" if (plan_type or "") == "perp_options" else "期期对冲" def _dir_label(direction: str) -> str: d = (direction or "").lower() if d == "long": return "做多" if d == "short": return "做空" return "—" def build_hedge_start_message(plan: dict[str, Any], *, legs: Optional[list[dict[str, Any]]] = None) -> str: pt = plan.get("plan_type") or "" lines = [ f"🟢 对冲计划启动 #{plan.get('id')}", f"📌 类型:{_type_label(pt)}", f"🪙 标的:{plan.get('underlying') or '—'}", ] if pt == "perp_options": lines.extend( [ f"📈 方向:{_dir_label(plan.get('direction') or '')}", f"💵 开仓参考:{_fmt(plan.get('entry_mark'))}", f"🎯 止盈:{_fmt(plan.get('tp'))}|止损:{_fmt(plan.get('sl'))}", f"📦 永续张数:{_fmt(plan.get('perp_size'), 4)}|杠杆:{_fmt(plan.get('leverage'), 0)}x", f"💎 期权保费合计:{_fmt(plan.get('premium_total'), 4)} USDC", ] ) else: rr = plan.get("profit_rr") if rr not in (None, ""): lines.extend( [ f"🎯 盈亏比:{_fmt(rr)} (盈利金额/总权利金)", f"💎 期权保费合计:{_fmt(plan.get('premium_total'), 4)} USDC", ] ) else: lines.extend( [ f"🎯 上破:{_fmt(plan.get('target_price_up') or plan.get('target_price'))}" f"|下破:{_fmt(plan.get('target_price_down') or plan.get('target_price'))}", f"💎 期权保费合计:{_fmt(plan.get('premium_total'), 4)} USDC", ] ) if legs: for leg in legs: role = leg.get("leg_role") or "" if role == "perp": lines.append(f"· 永续腿 {leg.get('symbol') or ''} ×{_fmt(leg.get('size'), 4)}") else: lines.append( f"· {role} {(leg.get('opt_type') or '')} K{_fmt(leg.get('strike'), 0)} " f"×{_fmt(leg.get('size'), 0)}张 {leg.get('inst_id') or ''}" ) lines.append("📎 独立模块推送,不进普通交易复盘") return "\n".join(lines) def build_hedge_end_message(plan: dict[str, Any]) -> str: reason = plan.get("close_reason") or "—" total = plan.get("realized_pnl_total") try: tv = float(total) if total is not None else None except (TypeError, ValueError): tv = None head = "🔴" if (tv is not None and tv < 0) else "🟢" reason_map = { "perp_tp": "永续止盈(期权默认不平)", "perp_sl": "永续止损(期权强制平)", "target_win_leg": "期期已平盈利腿(中间态)", "target_up_win_leg": "期期上破·已平盈利腿", "target_down_win_leg": "期期下破·已平盈利腿", "profit_rr_win_leg": "期期盈亏比达标·已平盈利腿", "oo_rest_closing": "期期残值平·清亏损腿中", "oo_rest_closed": "期期残值平·两腿已平", "oo_expiry_loss": "期期到期无盈利·总亏损", "oo_expiry_win": "期期到期仍盈利", "expiry": "到期收口", "manual": "人工结束", "partial_fail": "半腿失败收尾", "cancelled": "已取消", } lines = [ f"{head} 对冲计划结束 #{plan.get('id')}", f"📌 类型:{_type_label(plan.get('plan_type') or '')}", f"🪙 标的:{plan.get('underlying') or '—'}", f"📎 原因:{reason_map.get(reason, reason)}", f"💰 合计≈U:{_fmt(total)}", f"· 永续分项:{_fmt(plan.get('realized_pnl_perp'))} USDT", f"· 期权分项:{_fmt(plan.get('realized_pnl_options'))} USDC(≈U 1:1)", f"⏱ 开仓:{plan.get('opened_at') or '—'}|结束:{plan.get('closed_at') or '—'}", ] return "\n".join(lines) def build_hedge_alert_message( *, title: str, plan_id: Any = None, detail: str = "", ) -> str: lines = [f"⚠️ 对冲计划告警{(' #' + str(plan_id)) if plan_id else ''}", f"📌 {title}"] if detail: lines.append(str(detail)[:800]) return "\n".join(lines) def notify_hedge( cfg: dict[str, Any], content: str, ) -> bool: send: Optional[Callable[[str], Any]] = cfg.get("send_wechat") if not callable(send): return False try: send(content) return True except Exception: return False def notify_plan_start( cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: Optional[list[dict[str, Any]]] = None, ) -> bool: if int(plan.get("wechat_start_sent") or 0): return False ok = notify_hedge(cfg, build_hedge_start_message(plan, legs=legs)) if ok and plan.get("id") is not None: update_plan(conn, int(plan["id"]), wechat_start_sent=1) plan["wechat_start_sent"] = 1 return ok def notify_plan_end(cfg: dict[str, Any], conn: Any, plan: dict[str, Any]) -> bool: if int(plan.get("wechat_end_sent") or 0): return False # 中间态 target_win_leg 不算正式结束推送(用告警) if (plan.get("close_reason") or "") in ( "target_win_leg", "target_up_win_leg", "target_down_win_leg", "profit_rr_win_leg", "oo_rest_closing", ) and (plan.get("status") or "") != "closed": cr = str(plan.get("close_reason") or "") if "profit_rr" in cr: side = "盈亏比达标" elif "up" in cr: side = "上破" elif "down" in cr: side = "下破" else: side = "目标" mode = (plan.get("oo_close_mode") or "").strip().lower() if mode in ("close_all", "全平", "残值平"): rest_txt = "另一腿残值平(本合约权利金≤20%且有买一,失败重试)" else: rest_txt = "另一腿到期平(持有至到期结算)" rr = plan.get("profit_rr") if rr not in (None, ""): detail = f"盈亏比 {_fmt(rr)} (盈利金额/总权利金)" else: detail = ( f"上破 {_fmt(plan.get('target_price_up') or plan.get('target_price'))}" f"|下破 {_fmt(plan.get('target_price_down') or plan.get('target_price'))}" ) notify_hedge( cfg, build_hedge_alert_message( title=f"期期{side}已平盈利腿 · {rest_txt}", plan_id=plan.get("id"), detail=detail, ), ) return True ok = notify_hedge(cfg, build_hedge_end_message(plan)) if ok and plan.get("id") is not None: update_plan(conn, int(plan["id"]), wechat_end_sent=1) plan["wechat_end_sent"] = 1 return ok def notify_partial_fail(cfg: dict[str, Any], *, plan_type: str, msg: str, results: Any = None) -> bool: detail = msg if results: try: detail = f"{msg}\n路径结果:{results}"[:800] except Exception: pass return notify_hedge( cfg, build_hedge_alert_message(title=f"{_type_label(plan_type)}半腿失败", detail=detail), )