From 1e927521b737528a732cddfb0f1aadc914bddda0 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Mon, 18 May 2026 08:35:28 +0800 Subject: [PATCH] 1 --- app/db/paper_trading.py | 41 +++++++++++++++++++++++++++ app/integrations/push_orchestrator.py | 29 +++++++------------ app/services/price_tracker.py | 6 ++-- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/app/db/paper_trading.py b/app/db/paper_trading.py index 2f2c89b..a357065 100644 --- a/app/db/paper_trading.py +++ b/app/db/paper_trading.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from app.config.system_config import paper_trading_config from app.db.schema import get_conn +from app.integrations.feishu_push import push_card def _now() -> str: @@ -174,6 +175,43 @@ def _record_event(conn, trade_id: int, rec_id: int, symbol: str, event_type: str ) +def _push_event_card(event_type: str, trade: dict, result: dict, event_time: str = "") -> None: + try: + symbol = str(trade.get("symbol") or "") + title = { + "open": f"📒 模拟交易开仓 — {symbol.replace('/USDT', '')}", + "close": f"🏁 模拟交易平仓 — {symbol.replace('/USDT', '')}", + "trailing_activate": f"🛡️ 模拟交易移动止盈启动 — {symbol.replace('/USDT', '')}", + "trailing_move": f"🛡️ 模拟交易移动止盈上移 — {symbol.replace('/USDT', '')}", + }.get(event_type) + if not title: + return + card = { + "config": {"wide_screen_mode": True}, + "header": { + "template": "blue" if event_type == "open" else ("yellow" if event_type.startswith("trailing") else "red"), + "title": {"tag": "plain_text", "content": title}, + }, + "elements": [ + { + "tag": "div", + "text": { + "tag": "lark_md", + "content": ( + f"**币种**: {symbol}\n" + f"**事件**: {event_type}\n" + f"**成交价**: ${result.get('entry_price') or result.get('exit_price') or result.get('trailing_stop') or trade.get('current_price') or 0}\n" + f"**时间**: {event_time or ''}" + ), + }, + } + ], + } + push_card(card) + except Exception: + pass + + def _open_trade(conn, rec: dict, current_price: float, event_time: str) -> dict: rec_id = _safe_int(rec.get("id")) symbol = str(rec.get("symbol") or "").strip().upper() @@ -246,6 +284,7 @@ def _open_trade(conn, rec: dict, current_price: float, event_time: str) -> dict: }, now, ) + _push_event_card("open", {"symbol": symbol}, {"entry_price": entry_price}, now) return { "opened": True, "trade_id": trade_id, @@ -307,6 +346,7 @@ def _close_trade(conn, trade: dict, current_price: float, reason: str, event_tim {"realized_pnl_usdt": pnl_usdt, "fee_usdt": total_fee}, now, ) + _push_event_card("close", trade, {"exit_price": exit_price}, now) return {"closed": True, "trade_id": trade["id"], "exit_reason": reason, "pnl_pct": pnl_pct, "pnl_usdt": pnl_usdt} @@ -352,6 +392,7 @@ def _update_trailing_stop(conn, trade: dict, current_price: float, pnl_pct: floa }, event_time, ) + _push_event_card(event_type, trade, {"trailing_stop": new_trail}, event_time) return new_trail, { "activated": activated, "moved": moved, diff --git a/app/integrations/push_orchestrator.py b/app/integrations/push_orchestrator.py index fc7bf8d..f67a4dc 100644 --- a/app/integrations/push_orchestrator.py +++ b/app/integrations/push_orchestrator.py @@ -4,27 +4,14 @@ Separates eligibility / cooldown decisions from payload rendering and transport. """ from app.db.recommendation_queries import log_push, should_push -from app.integrations.feishu_push import push_altcoin_tp_sl_alert, push_recommendation_state_alert +from app.integrations.feishu_push import build_trade_action_card, push_card def push_mainline_state_update(symbol: str, rec_id: int, mainline_item: dict, title_prefix: str | None = None, entry_push_type: str = "entry", watch_push_type: str = "watch_pool") -> bool: - if not mainline_item or mainline_item.get("execution_status") not in ("buy_now", "wait_pullback"): - status = mainline_item.get("execution_status") if mainline_item else "missing" - print(f"[push] skip {symbol}: mainline_status={status}") - return False - - push_type = entry_push_type if mainline_item.get("execution_status") == "buy_now" else watch_push_type - action = mainline_item.get("action_status", "") - if not should_push(symbol, push_type, action): - print(f"⏭ 跳过推送({symbol}): {push_type}/{action} 12h冷却中") - return False - - ok, resp = push_recommendation_state_alert(mainline_item, title_prefix=title_prefix) - if ok: - log_push(symbol, push_type, action, rec_id=rec_id) - return True - - print(f"[push] failed {symbol}: {resp}") + """主链路状态只记录,不再飞书推送。""" + status = mainline_item.get("execution_status") if mainline_item else "missing" + action = mainline_item.get("action_status", "") if mainline_item else "" + print(f"[push] skip {symbol}: mainline notifications disabled (status={status}, action={action})") return False @@ -34,7 +21,7 @@ def push_trade_action_update(symbol: str, rec_id: int, state_decision: dict, fin if not should_push(symbol, push_type, final_action): print(f"⏭ 跳过推送({symbol}): {push_type}/{final_action} 12h冷却中") return False - ok, resp = push_altcoin_tp_sl_alert( + card = build_trade_action_card( state_decision["push_symbol"], state_decision["push_current_price"], state_decision["push_entry_price"], @@ -45,6 +32,10 @@ def push_trade_action_update(symbol: str, rec_id: int, state_decision: dict, fin state_decision.get("tp1", 0), state_decision.get("tp2", 0), ) + if isinstance(card, tuple): + ok, resp = card + else: + ok, resp = push_card(card) if ok: log_push(symbol, push_type, final_action, rec_id=rec_id) return True diff --git a/app/services/price_tracker.py b/app/services/price_tracker.py index 6942289..64ab586 100644 --- a/app/services/price_tracker.py +++ b/app/services/price_tracker.py @@ -30,13 +30,14 @@ from app.db.altcoin_db import ( update_latest_price_cache, ) from app.db.paper_trading import sync_recommendation as sync_paper_trade +from app.integrations.push_orchestrator import push_trade_action_update from app.core.pa_engine import ( calc_atr, full_pa_analysis, detect_trend_exhaustion, analyze_entry_point, ) -from app.integrations.push_orchestrator import push_trade_action_update from app.config.config_loader import load_rules from app.core.opportunity_lifecycle import apply_entry_quality_gate +from app.db.paper_trading import sync_recommendation as sync_paper_trade exchange = ccxt.binance({"enableRateLimit": True}) REPO_ROOT = Path(__file__).resolve().parents[2] @@ -337,7 +338,8 @@ def track_prices(): current_price, event_time=datetime.now().isoformat(), ) - push_trade_action_update(symbol, rec["id"], state_decision, final_action, push_type="entry") + if paper_result.get("opened") or paper_result.get("closed") or paper_result.get("activated") or paper_result.get("moved"): + print(f" {symbol}: paper trading event -> {paper_result}") results.append({ "symbol": symbol,