""" 飞书复盘报告推送模块 推送三类卡片: 1. push_review_report — 策略复盘报告(蓝色主题) 2. push_reverse_analysis_report — 逆向分析报告(紫色主题) 3. push_rule_update_notification — 新规律通知(绿色主题) 复用 feishu_push.py 的认证模式(load_feishu_creds → get_token → push_card) """ import os import sys import json sys.path.insert(0, os.path.dirname(__file__)) from feishu_push import push_card CHAT_ID = "oc_2c597ad94167102922de142928e2917a" # ==================== 1. 策略复盘报告 ==================== def push_review_report(review_results): """ 推送策略复盘报告卡片 — 📊 蓝色主题 Section 1: 推荐命中统计 (hit/fail/flat counts, hit rate %) Section 2: 信号绩效TOP5 (best performing signals) Section 3: 遗漏爆炸 (missed coins, why, what features) Section 4: 权重调整 (weight changes) """ reviews = review_results.get("review_details", []) weight_adj = review_results.get("weight_adjustments", []) missed = review_results.get("missed_explosions", []) # Section 1: 命中统计 hit_count = sum(1 for r in reviews if r.get("outcome") == "爆发") fail_count = sum(1 for r in reviews if r.get("outcome") == "失败") flat_count = sum(1 for r in reviews if r.get("outcome") == "横盘") total = len(reviews) hit_rate_pct = round(hit_count / total * 100, 1) if total > 0 else 0 # 命中统计文案 hit_emoji = "🔥" if hit_rate_pct >= 50 else "⚠️" if hit_rate_pct >= 30 else "❌" stats_line = ( f"本次复盘 **{total}** 条推荐:\n" f" • 爆发(命中): **{hit_count}** ({hit_emoji})\n" f" • 横盘: **{flat_count}**\n" f" • 失败: **{fail_count}**\n" f" • 命中率: **{hit_rate_pct}%**" ) # Section 2: 信号绩效TOP5 # 从review_results中提取信号绩效信息 from altcoin_db import get_signal_weights weights = get_signal_weights() sig_perf_list = sorted( [(sig, data) for sig, data in weights.items() if data.get("total_count", 0) >= 3], key=lambda x: x[1].get("hit_rate", 0), reverse=True, )[:5] sig_lines = "" if sig_perf_list: for sig, data in sig_perf_list: hr = data.get("hit_rate", 0) w = data.get("weight", 0) total_n = data.get("total_count", 0) cat = data.get("category", "") emoji = "✅" if hr >= 50 else "⚠️" if hr >= 30 else "❌" sig_lines += f"\n • {emoji} **{sig}**({cat}): 命中率{hr}% | 权重{w} | 样本{total_n}" else: sig_lines = "\n • 样本不足,暂无绩效数据" # Section 3: 遗漏爆炸 missed_lines = "" if missed: for m in missed[:5]: # 最多展示5只 symbol = m.get("symbol", "") gain = m.get("gain_pct", 0) reason = m.get("reason_missed", m.get("reason", "")) features = m.get("features_detected", []) if isinstance(features, str): try: features = json.loads(features) except: features = [features] feat_str = ", ".join(str(f) for f in features[:3]) if features else "无" missed_lines += f"\n • 💥 **{symbol}** 涨{gain}% | 原因: {reason} | 特征: {feat_str}" else: missed_lines = "\n • ✅ 无遗漏爆炸" # Section 4: 权重调整 adj_lines = "" if weight_adj: for adj in weight_adj: adj_lines += f"\n • {adj}" else: adj_lines = "\n • 无权重调整" # 构建卡片 card = { "config": {"wide_screen_mode": True}, "header": { "template": "blue", "title": {"tag": "plain_text", "content": "📊 山寨币策略复盘报告"}, }, "elements": [ { "tag": "div", "text": { "tag": "lark_md", "content": ( f"**=== 推荐命中统计 ===**\n{stats_line}\n\n" f"**=== 信号绩效TOP5 ===**\n{sig_lines}\n\n" f"**=== 遗漏爆炸 ===**\n{missed_lines}\n\n" f"**=== 权重调整 ===**\n{adj_lines}" ), }, }, { "tag": "action", "actions": [ { "tag": "button", "text": {"tag": "plain_text", "content": f"📊 命中率{hit_rate_pct}%"}, "type": "primary" if hit_rate_pct >= 50 else "warning" if hit_rate_pct >= 30 else "danger", } ], }, ], } return push_card(card) # ==================== 2. 逆向分析报告 ==================== def push_reverse_analysis_report(reverse_results): """ 推送逆向分析报告卡片 — 🔍 紫色主题 Section 1: 今日涨幅榜TOP10 (symbol, gain%, sector) Section 2: 起爆前共性特征 (pattern summary with percentages) Section 3: 新发现规律 (any new rules) """ top_gainers = reverse_results.get("top_gainers", []) pattern_summary = reverse_results.get("pattern_summary", []) new_rules = reverse_results.get("new_rules", []) total_unrecommended = reverse_results.get("total_unrecommended", 0) total_analyzed = reverse_results.get("total_analyzed", 0) # Section 1: 涨幅榜TOP10 gainer_lines = "" for i, g in enumerate(top_gainers[:10], 1): symbol = g.get("symbol", "").replace("/USDT", "") gain = g.get("gain_pct", 0) sector = g.get("sector", []) sector_str = sector[0] if isinstance(sector, list) and sector else (sector if sector else "未知") volume = g.get("volume_24h", 0) vol_str = f"${volume / 1e6:.1f}M" if volume > 0 else "" gainer_lines += f"\n {i}. **{symbol}** +{gain}% | {sector_str} | {vol_str}" if not gainer_lines: gainer_lines = "\n • 今日无明显涨幅" # Section 2: 起爆前共性特征 pattern_lines = "" for p in pattern_summary[:8]: # 最多展示8个特征 label = p.get("label", p.get("feature", "")) pct = p.get("percentage", 0) count = p.get("count", 0) total = p.get("total", 0) bar = "█" * int(pct / 10) + "░" * (10 - int(pct / 10)) emoji = "🔥" if pct >= 60 else "✅" if pct >= 40 else "⚠️" if pct >= 20 else "❌" pattern_lines += f"\n • {emoji} **{label}**: {pct}%({count}/{total}) {bar}" if not pattern_lines: pattern_lines = "\n • 分析样本不足" # Section 3: 新发现规律 rule_lines = "" if new_rules: for r in new_rules: rule_id = r.get("rule_id", "") desc = r.get("description", "") score_adj = r.get("score_adjust", 0) rule_type = r.get("type", "") rule_lines += f"\n • 🧠 **{rule_id}**: {desc} → 评分{score_adj}({rule_type})" else: rule_lines = "\n • 暂无新规律达到显著性阈值" # 分析概况 overview = ( f"涨幅榜共{len(top_gainers)}只 ≥10%\n" f"未被推荐: {total_unrecommended}只\n" f"已做PA分析: {total_analyzed}只" ) # 构建卡片(紫色主题用 "violet" — 飞书卡片没有purple,用indigo近似) card = { "config": {"wide_screen_mode": True}, "header": { "template": "indigo", "title": {"tag": "plain_text", "content": "🔍 逆向分析报告 — 涨幅榜复盘"}, }, "elements": [ { "tag": "div", "text": { "tag": "lark_md", "content": ( f"**{overview}**\n\n" f"**=== 今日涨幅榜TOP10 ===**\n{gainer_lines}\n\n" f"**=== 起爆前共性特征 ===**\n{pattern_lines}\n\n" f"**=== 新发现规律 ===**\n{rule_lines}" ), }, }, { "tag": "action", "actions": [ { "tag": "button", "text": {"tag": "plain_text", "content": f"🔍 分析{total_analyzed}只暴涨币"}, "type": "primary", } ], }, ], } return push_card(card) # ==================== 3. 新规律通知 ==================== def push_rule_update_notification(rule_id, description, status="候选规则,未生效"): """ 推送新规律学习通知 — 🧠 绿色主题 简洁卡片,告知策略自动迭代 """ card = { "config": {"wide_screen_mode": True}, "header": { "template": "green", "title": {"tag": "plain_text", "content": "🧠 策略自学习 — 候选规则发现"}, }, "elements": [ { "tag": "div", "text": { "tag": "lark_md", "content": ( f"规则ID: **{rule_id}**\n\n" f"状态: **{status}**\n\n" f"描述: {description}\n\n" f"说明: 该规则仅进入候选池/灰度评估,未通过发布闸门前不会写入正式规则库,也不会影响下次选币。" ), }, }, { "tag": "action", "actions": [ { "tag": "button", "text": {"tag": "plain_text", "content": f"🧠 {rule_id}"}, "type": "primary", } ], }, ], } return push_card(card) # ==================== 测试 ==================== if __name__ == "__main__": print("测试复盘报告推送...") # 测试review report test_review = { "review_details": [ {"symbol": "FET/USDT", "outcome": "爆发", "pnl_48h": 12.5}, {"symbol": "ARB/USDT", "outcome": "横盘", "pnl_48h": 1.2}, {"symbol": "PEPE/USDT", "outcome": "失败", "pnl_48h": -4.5}, ], "weight_adjustments": ["量价齐飞: 3→4.0 (命中率67%)"], "missed_explosions": [ {"symbol": "INJ/USDT", "gain_pct": 25, "reason_missed": "细筛淘汰(score=4)", "features_detected": ["ignition_point", "Q7_zone"]}, ], } ok1, r1 = push_review_report(test_review) print(f"复盘报告: ok={ok1}") # 测试rule notification ok2, r2 = push_rule_update_notification("rule_20260429_001", "涨幅榜60%有起爆点 → 起爆点是爆发前必现信号") print(f"规律通知: ok={ok2}")