From afcfb6434a660ca766ad4fb90c058ea0c51c3687 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 6 Feb 2026 20:59:56 +0800 Subject: [PATCH] feat: Revamp signal retrieval to prioritize earliest settling markets by opportunity score and enhance portfolio reporting with HTML generation for large portfolios. --- bot_listener.py | 270 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 208 insertions(+), 62 deletions(-) diff --git a/bot_listener.py b/bot_listener.py index c649b3db..eab877d3 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -45,7 +45,7 @@ def start_bot(): @bot.message_handler(commands=["signal"]) def get_signals(message): - bot.send_message(message.chat.id, "š ę£åØę£ē“¢å½åęå¼å¾å ³ę³Øē天ę°äæ”å·...") + bot.send_message(message.chat.id, "š ę£åØę£ē“¢ęę©ē»ē®ēåøåŗäæ”å·...") try: if not os.path.exists("data/active_signals.json"): @@ -63,26 +63,83 @@ def start_bot(): ) return - # ęåę°ęåŗå¹¶åå 3 äøŖ - sorted_signals = sorted( - signals.values(), key=lambda x: x.get("score", 0), reverse=True - )[:3] + # čæę»¤ęå·²ē»ęēåøåŗļ¼ä»·ę ¼ę„čæ0ę100ļ¼åę ę„ęē + active_signals = [] + for s in signals.values(): + price = s.get("price", 50) + if 5 <= price <= 95 and s.get("target_date"): + active_signals.append(s) + + if not active_signals: + bot.send_message(message.chat.id, "š å½åę²”ęå¼å¾å ³ę³Øēę“»č·åøåŗć") + return - for s in sorted_signals: - notifier.send_signal( - market_name=s["city"], - full_title=s["full_title"], - option=s["option"], - score=round(s.get("score", 0) * 5, 1), - prediction=s["prediction"], - confidence=int(s.get("score", 0) * 100), - analysis_list=[f"å差解ę: {s.get('rationale', 'N/A')}"], - price=s["price"], - market_url=s["url"], - local_time=s["local_time"], - target_date=s["target_date"], + # ęę„ęęåŗļ¼ä¼å ęę©ē»ē®ē + active_signals.sort(key=lambda x: x.get("target_date", "9999-99-99")) + + # č·åęę©ēę„ę + earliest_date = active_signals[0].get("target_date") + + # åŖåęę©ę„ęēåøåŗ + earliest_markets = [s for s in active_signals if s.get("target_date") == earliest_date] + + # ę"ęŗä¼ä»·å¼"ęåŗļ¼ę„čæéå®åŗé“ļ¼85-95Ā¢ļ¼ēä¼å + def opportunity_score(s): + price = s.get("price", 50) + buy_yes = s.get("buy_yes", price) + buy_no = s.get("buy_no", 100 - price) + # č®”ē®č·ē¦»éå®åŗé“ēč·ē¦» + max_price = max(buy_yes, buy_no) + if 85 <= max_price <= 95: + return 100 + max_price # å·²åØéå®åŗé“ļ¼ęé«ä¼å + elif max_price > 70: + return max_price # ę„čæéå® + else: + return max_price / 2 # čæē¦»éå® + + earliest_markets.sort(key=opportunity_score, reverse=True) + top_markets = earliest_markets[:3] + + # ę建ę¶ęÆ + msg_lines = [ + f"šÆ å³å°ē»ē®åøåŗ ({earliest_date})\n", + f"å ±åē° {len(earliest_markets)} äøŖę“»č·é锹ļ¼ä»„äøäøŗęå¼å¾å ³ę³Øēļ¼\n" + ] + + for i, s in enumerate(top_markets, 1): + city = s.get("city", "Unknown") + option = s.get("option", "Unknown") + prediction = s.get("prediction", "N/A") + buy_yes = s.get("buy_yes", s.get("price", 50)) + buy_no = s.get("buy_no", 100 - s.get("price", 50)) + + # å¤ęęä½³ę¹å + if buy_no >= 85: + direction = f"š Buy No {buy_no}Ā¢ (ę„čæéå®)" + confidence = "š„" if buy_no >= 90 else "ā" + elif buy_yes >= 85: + direction = f"š Buy Yes {buy_yes}Ā¢ (ę„čæéå®)" + confidence = "š„" if buy_yes >= 90 else "ā" + elif buy_no >= 70: + direction = f"š Buy No {buy_no}Ā¢ (č§ę)" + confidence = "š”" + elif buy_yes >= 70: + direction = f"š Buy Yes {buy_yes}Ā¢ (č§ę)" + confidence = "š”" + else: + direction = f"āļø åč””ē Yes:{buy_yes}Ā¢ No:{buy_no}Ā¢" + confidence = "š" + + msg_lines.append( + f"{confidence} {i}. {city} {option}\n" + f" é¢ęµ: {prediction}\n" + f" {direction}\n" ) - time.sleep(0.5) + + local_time = top_markets[0].get("local_time", "N/A") if top_markets else "N/A" + msg_lines.append(f"\nš å½å°ę¶é“: {local_time}") + + bot.send_message(message.chat.id, "\n".join(msg_lines), parse_mode="HTML") except Exception as e: bot.send_message(message.chat.id, f"ā č·åäæ”å·ę¶åŗé: {e}") @@ -110,77 +167,166 @@ def start_bot(): ) return - msg_lines = ["š 樔ęäŗ¤ęę„å\n" + "ā" * 20] + # å¦ęęä»č¶ čæ20äøŖļ¼ēę HTML ęä»¶ + if len(positions) > 20: + html_path = generate_portfolio_html(data) + with open(html_path, "rb") as f: + bot.send_document( + message.chat.id, + f, + caption=f"š å®ę“ęä»ę„å ({len(positions)}äøŖęä»)\nš³ ä½é¢: ${balance:.2f}" + ) + return + + # ē²¾ē®ēę¶ęÆ + msg_lines = ["š 樔ęäŗ¤ęę„å"] - # 1. ę“»č·ęä» - ęē®ę ę„ęåē» if positions: - # ęē®ę ę„ęåē» positions_by_date = {} for pid, pos in positions.items(): - target_date = pos.get("target_date") or "ęŖē„ę„ę" + target_date = pos.get("target_date") or "ęŖē„" if target_date not in positions_by_date: - positions_by_date[target_date] = [] - positions_by_date[target_date].append(pos) + positions_by_date[target_date] = {"count": 0, "pnl": 0, "cost": 0} + positions_by_date[target_date]["count"] += 1 + positions_by_date[target_date]["pnl"] += pos.get("pnl_usd", 0) + positions_by_date[target_date]["cost"] += pos.get("cost_usd", 0) - # ęę„ęęåŗę¾ē¤ŗ + msg_lines.append(f"\nš ęä»ę¦č§ (å ±{len(positions)}äøŖ)") for target_date in sorted(positions_by_date.keys()): - date_positions = positions_by_date[target_date] - date_pnl = sum(p.get("pnl_usd", 0) for p in date_positions) - date_icon = "š" if date_pnl >= 0 else "š" - - msg_lines.append(f"\n{date_icon} ć{target_date}ć å°č®”: {date_pnl:+.2f}$") - msg_lines.append("ā" * 18) - - for pos in date_positions: - pnl_usd = pos.get("pnl_usd", 0) - icon = "š¢" if pnl_usd >= 0 else "š“" - entry_price = pos.get("entry_price", 0) - current_price = pos.get("current_price", entry_price) - predicted_temp = pos.get("predicted_temp") - - # ę ¼å¼ļ¼ååø é锹 | ę¹å å „åŗāå½å | é¢ęµęø©åŗ¦ | ēäŗ - pred_text = f"é¢ęµ:{predicted_temp}" if predicted_temp else "" - msg_lines.append( - f"{icon} {pos['city']} {pos['option']}\n" - f" {pos['side']} {entry_price}Ā¢ā{current_price}Ā¢ {pred_text} | {pnl_usd:+.2f}$" - ) + info = positions_by_date[target_date] + icon = "š" if info["pnl"] >= 0 else "š" + msg_lines.append(f"{icon} {target_date}: {info['count']}ē¬ ${info['cost']:.0f}ęå „ {info['pnl']:+.2f}$") total_pnl = sum(p.get("pnl_usd", 0) for p in positions.values()) - msg_lines.append(f"\nš° ęä»ę»č®”: {total_pnl:+.2f}$") + total_cost = sum(p.get("cost_usd", 0) for p in positions.values()) + msg_lines.append(f"š° åč®”: ${total_cost:.0f}ęå „ {total_pnl:+.2f}$") + + msg_lines.append("\nš ęę°ęä»:") + recent_positions = list(positions.values())[-5:] + for pos in reversed(recent_positions): + pnl = pos.get("pnl_usd", 0) + icon = "š¢" if pnl >= 0 else "š“" + pred = pos.get("predicted_temp", "") + pred_text = f"é¢ęµ:{pred}" if pred else "" + msg_lines.append(f"{icon} {pos['city']} {pos['option']} {pred_text} {pnl:+.2f}$") - # 2. ęčæäŗ¤ęč®°å½ (ęę° 5 ē¬) trades = data.get("trades", []) if trades: msg_lines.append("\nš ęčæęä½:") - # åę«å°¾ 5 ē¬äŗ¤ęå¹¶å±ē¤ŗ - recent_trades = trades[-5:] - for t in reversed(recent_trades): + for t in reversed(trades[-3:]): t_type = "š" if t["type"] == "BUY" else "š°" - t_time = t.get("time", "").split(" ")[1] if " " in t.get("time", "") else t.get("time", "") - msg_lines.append( - f"⢠{t_time} {t_type} {t['city']} {t['option']} ({t['price']}Ā¢)" - ) + t_time = t.get("time", "").split(" ")[1] if " " in t.get("time", "") else "" + msg_lines.append(f"⢠{t_time} {t_type} {t['city']} {t['option']}") - # 3. åå²ę±ę»ē»č®” if history: total_trades = len(history) wins = sum(1 for p in history if p.get("pnl_usd", 0) > 0) total_cost = sum(p.get("cost_usd", 0) for p in history) total_profit = sum(p.get("pnl_usd", 0) for p in history) win_rate = (wins / total_trades) * 100 if total_trades > 0 else 0 - roi = (total_profit / total_cost * 100) if total_cost > 0 else 0 - msg_lines.append("\nš åå²ę绩:") - msg_lines.append(f"瓯讔ęäŗ¤: {total_trades}ē¬ | čē: {win_rate:.1f}%") - msg_lines.append(f"å·²ęå „: ${total_cost:.2f} | ēäŗ: {total_profit:+.2f}$ ({roi:+.1f}%)") + msg_lines.append(f"\nš åå²: {total_trades}ē¬ čē{win_rate:.0f}% ēäŗ{total_profit:+.2f}$") - footer = "\n" + "ā" * 20 + "\n" + f"š³ 蓦ę·ä½é¢: ${balance:.2f}" - msg_lines.append(footer) + msg_lines.append(f"\nš³ ä½é¢: ${balance:.2f}") bot.reply_to(message, "\n".join(msg_lines), parse_mode="HTML") except Exception as e: bot.reply_to(message, f"ā č·åęä»å¤±č“„: {e}") + + def generate_portfolio_html(data): + """ēęę¼äŗ®ē HTML ęä»ę„å""" + from datetime import datetime, timedelta + + positions = data.get("positions", {}) + history = data.get("history", []) + balance = data.get("balance", 1000.0) + + # ęę„ęåē» + positions_by_date = {} + for pid, pos in positions.items(): + target_date = pos.get("target_date") or "ęŖē„" + if target_date not in positions_by_date: + positions_by_date[target_date] = [] + positions_by_date[target_date].append(pos) + + total_pnl = sum(p.get("pnl_usd", 0) for p in positions.values()) + total_cost = sum(p.get("cost_usd", 0) for p in positions.values()) + + # ēę HTML + now_bj = (datetime.utcnow() + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M") + + html = f""" + +
+ +| ååø | é锹 | ę¹å | å „åŗ | å½å | é¢ęµ | ēäŗ |
|---|---|---|---|---|---|---|
| {pos.get('city', '-')} | +{pos.get('option', '-')} | +{pos.get('side', '-')} | +{pos.get('entry_price', 0)}Ā¢ | +{pos.get('current_price', 0)}Ā¢ | +{pred} | +{pnl:+.2f}$ | +