This commit is contained in:
aaron 2026-05-31 23:04:18 +08:00
parent 7299f0259b
commit 0f52cbe0be

View File

@ -481,6 +481,14 @@ def _fmt_pct(value) -> str:
return f"{sign}{pct:.2f}%" return f"{sign}{pct:.2f}%"
def _side_label(side: str) -> str:
return "" if normalize_side(side) == "short" else ""
def _side_action(side: str, long_text: str, short_text: str) -> str:
return short_text if normalize_side(side) == "short" else long_text
def _card_field(label: str, value) -> dict: def _card_field(label: str, value) -> dict:
return { return {
"tag": "div", "tag": "div",
@ -549,13 +557,16 @@ def _push_custom_paper_card(card: dict) -> tuple[bool, object]:
def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str = "") -> None: def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str = "") -> None:
symbol = str(trade.get("symbol") or "") symbol = str(trade.get("symbol") or "")
short_symbol = symbol.replace("/USDT", "") short_symbol = symbol.replace("/USDT", "")
side = normalize_side(result.get("side") or trade.get("side"))
side_label = _side_label(side)
if event_type == "open": if event_type == "open":
_push_paper_card( _push_paper_card(
event_type, event_type,
symbol, symbol,
f"交易开仓 - {short_symbol}", f"交易开仓({side_label}) - {short_symbol}",
"blue", "blue",
[ [
("方向", side_label),
("成交价", _fmt_price(result.get("entry_price"))), ("成交价", _fmt_price(result.get("entry_price"))),
("名义仓位", f"{_safe_float(result.get('notional_usdt')):.2f} USDT"), ("名义仓位", f"{_safe_float(result.get('notional_usdt')):.2f} USDT"),
("杠杆/保证金", f"{_safe_float(result.get('leverage')):.1f}x / {_safe_float(result.get('margin_usdt')):.2f} USDT"), ("杠杆/保证金", f"{_safe_float(result.get('leverage')):.1f}x / {_safe_float(result.get('margin_usdt')):.2f} USDT"),
@ -570,9 +581,10 @@ def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str
_push_paper_card( _push_paper_card(
event_type, event_type,
symbol, symbol,
f"{title_prefix} - {short_symbol}", f"{title_prefix}({side_label}) - {short_symbol}",
"red" if _safe_float(result.get("pnl_usdt")) < 0 else "green", "red" if _safe_float(result.get("pnl_usdt")) < 0 else "green",
[ [
("方向", side_label),
("退出价", _fmt_price(result.get("exit_price"))), ("退出价", _fmt_price(result.get("exit_price"))),
("收益率", _fmt_pct(result.get("pnl_pct"))), ("收益率", _fmt_pct(result.get("pnl_pct"))),
("收益额", f"{_safe_float(result.get('pnl_usdt')):.2f} USDT"), ("收益额", f"{_safe_float(result.get('pnl_usdt')):.2f} USDT"),
@ -586,12 +598,13 @@ def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str
_push_paper_card( _push_paper_card(
event_type, event_type,
symbol, symbol,
f"移动止盈{'启动' if event_type == 'trailing_activate' else '上移'} - {short_symbol}", f"移动止盈{'启动' if event_type == 'trailing_activate' else '调整'}({side_label}) - {short_symbol}",
"yellow", "yellow",
[ [
("方向", side_label),
("保护价", _fmt_price(result.get("trailing_stop"))), ("保护价", _fmt_price(result.get("trailing_stop"))),
("当前收益", _fmt_pct(result.get("pnl_pct"))), ("当前收益", _fmt_pct(result.get("pnl_pct"))),
("动作", "启动保护" if event_type == "trailing_activate" else "上移保护价"), ("动作", "启动保护" if event_type == "trailing_activate" else _side_action(side, "上移保护价", "下移保护价")),
], ],
"移动止盈用于锁定浮盈。", "移动止盈用于锁定浮盈。",
event_time, event_time,
@ -600,39 +613,45 @@ def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str
def _push_order_created_card(order: dict, event_time: str = "") -> None: def _push_order_created_card(order: dict, event_time: str = "") -> None:
symbol = str(order.get("symbol") or "") symbol = str(order.get("symbol") or "")
side = normalize_side(order.get("side"))
side_label = _side_label(side)
target = _safe_float(order.get("target_price")) target = _safe_float(order.get("target_price"))
current = _safe_float(order.get("current_price_at_create")) current = _safe_float(order.get("current_price_at_create"))
distance = round((current / target - 1) * 100, 2) if target and current else 0 distance = order_distance_pct(side, current, target) if target and current else 0
_push_paper_card( _push_paper_card(
"paper_order_create", "paper_order_create",
symbol, symbol,
f"挂单创建 - {symbol.replace('/USDT', '')}", f"挂单创建({side_label}) - {symbol.replace('/USDT', '')}",
"wathet", "wathet",
[ [
("方向", side_label),
("目标价", _fmt_price(target)), ("目标价", _fmt_price(target)),
("当前价", _fmt_price(current)), ("当前价", _fmt_price(current)),
("距目标", _fmt_pct(distance)), ("距目标", _fmt_pct(distance)),
("有效期", order.get("expires_at") or "--"), ("有效期", order.get("expires_at") or "--"),
], ],
"等回踩机会已进入挂单,触价后进入持仓。", _side_action(side, "等回踩机会已进入挂单,触价后进入持仓。", "等反抽机会已进入挂单,触价后进入持仓。"),
event_time, event_time,
) )
def _push_order_filled_card(order: dict, result: dict, event_time: str = "") -> None: def _push_order_filled_card(order: dict, result: dict, event_time: str = "") -> None:
symbol = str(order.get("symbol") or "") symbol = str(order.get("symbol") or "")
side = normalize_side(result.get("side") or order.get("side"))
side_label = _side_label(side)
_push_paper_card( _push_paper_card(
"paper_order_fill", "paper_order_fill",
symbol, symbol,
f"挂单成交并开仓 - {symbol.replace('/USDT', '')}", f"挂单成交并开仓({side_label}) - {symbol.replace('/USDT', '')}",
"green", "green",
[ [
("方向", side_label),
("挂单价", _fmt_price(order.get("target_price"))), ("挂单价", _fmt_price(order.get("target_price"))),
("成交价", _fmt_price(result.get("entry_price") or order.get("fill_price"))), ("成交价", _fmt_price(result.get("entry_price") or order.get("fill_price"))),
("名义仓位", f"{_safe_float(result.get('notional_usdt')):.2f} USDT"), ("名义仓位", f"{_safe_float(result.get('notional_usdt')):.2f} USDT"),
("来源", order.get("source_status") or "wait_pullback"), ("来源", order.get("source_status") or "wait_pullback"),
], ],
"价格触达理想入场位,挂单已转为持仓。", _side_action(side, "价格触达理想回踩位,挂单已转为持仓。", "价格触达理想反抽位,挂单已转为持仓。"),
event_time, event_time,
) )
@ -808,6 +827,7 @@ def _open_trade(conn, rec: dict, current_price: float, event_time: str, config:
result = { result = {
"opened": True, "opened": True,
"trade_id": trade_id, "trade_id": trade_id,
"side": side,
"entry_price": entry_price, "entry_price": entry_price,
"qty": qty, "qty": qty,
"notional_usdt": notional, "notional_usdt": notional,
@ -1258,7 +1278,7 @@ def _close_trade(conn, trade: dict, current_price: float, reason: str, event_tim
_push_event_card( _push_event_card(
"close", "close",
trade, trade,
{"exit_price": exit_price, "exit_reason": reason, "pnl_pct": pnl_pct, "pnl_usdt": pnl_usdt}, {"side": side, "exit_price": exit_price, "exit_reason": reason, "pnl_pct": pnl_pct, "pnl_usdt": pnl_usdt},
now, now,
) )
return {"closed": True, "trade_id": trade["id"], "exit_reason": reason, "pnl_pct": pnl_pct, "pnl_usdt": pnl_usdt} return {"closed": True, "trade_id": trade["id"], "exit_reason": reason, "pnl_pct": pnl_pct, "pnl_usdt": pnl_usdt}
@ -1313,6 +1333,7 @@ def _apply_position_health_guard(
def _update_trailing_stop(conn, trade: dict, current_price: float, pnl_pct: float, event_time: str) -> tuple[float, dict]: def _update_trailing_stop(conn, trade: dict, current_price: float, pnl_pct: float, event_time: str) -> tuple[float, dict]:
cfg = _trailing_config() cfg = _trailing_config()
side = normalize_side(trade.get("side"))
current_trail = _safe_float(trade.get("trailing_stop")) current_trail = _safe_float(trade.get("trailing_stop"))
decision = evaluate_trailing_stop(position=trade, current_price=current_price, pnl_pct=pnl_pct, config=cfg).as_dict() decision = evaluate_trailing_stop(position=trade, current_price=current_price, pnl_pct=pnl_pct, config=cfg).as_dict()
if not decision.get("activated") and not decision.get("moved"): if not decision.get("activated") and not decision.get("moved"):
@ -1358,7 +1379,7 @@ def _update_trailing_stop(conn, trade: dict, current_price: float, pnl_pct: floa
}, },
event_time, event_time,
) )
_push_event_card(event_type, trade, {"trailing_stop": new_trail, "pnl_pct": pnl_pct}, event_time) _push_event_card(event_type, trade, {"side": side, "trailing_stop": new_trail, "pnl_pct": pnl_pct}, event_time)
return new_trail, { return new_trail, {
"activated": bool(decision.get("activated")), "activated": bool(decision.get("activated")),
"moved": bool(decision.get("moved")), "moved": bool(decision.get("moved")),