From e0d759819c2ab330f5c2a775143804bbf0cabe8e Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 6 Feb 2026 22:47:16 +0800 Subject: [PATCH] feat: Introduce new modules for technical analysis, Polymarket API, and statistical models, while enhancing bot resilience and refining market data processing with advanced filtering. --- bot_listener.py | 13 +++- main.py | 98 +++++++++++++++------------ src/analysis/technical_indicators.py | 4 +- src/data_collection/polymarket_api.py | 51 +++++++++++--- src/models/statistical_model.py | 4 +- 5 files changed, 110 insertions(+), 60 deletions(-) diff --git a/bot_listener.py b/bot_listener.py index 9c9bed60..21b87489 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -375,7 +375,18 @@ def start_bot(): message, "✅ 监控引擎正在运行中...\n7x24h 实时扫码 Polymarket 气温市场。" ) - bot.infinity_polling() + try: + while True: + try: + bot.infinity_polling(timeout=60, long_polling_timeout=60) + except (KeyboardInterrupt, SystemExit): + print("\n检测到退出信号,机器人正在关机...") + break + except Exception as e: + print(f"Bot 轮询连接异常 (通常是网络问题): {e}") + time.sleep(10) # 等待10秒后自动重连 + except KeyboardInterrupt: + print("\n机器人已停止。") if __name__ == "__main__": diff --git a/main.py b/main.py index cd9df16f..b71ab85b 100644 --- a/main.py +++ b/main.py @@ -171,14 +171,15 @@ def main(): # 多选一市场逻辑 if len(ts) > 2 and active_tid: - m["buy_yes_live"] = token_price_map.get(active_tid) # 该档位的 Ask - # 买入“否”的价格 = 1 - 该档位的 Bid (别人愿意买 Yes 的最高价) - bid_price = token_price_map.get(active_tid) # 这里逻辑稍后在下文 fallback 处理中增强 - # 我们这里只是注入,真正复杂的 fallback 逻辑在循环内部 + m["buy_yes_live"] = token_price_map.get(f"{active_tid}:ask") + # 买入“否”的价格 = 1 - 该档位的 Bid + bid_val = token_price_map.get(f"{active_tid}:bid") + if bid_val: + m["buy_no_live"] = 1.0 - bid_val # 二选一市场逻辑 elif len(ts) == 2: - m["buy_yes_live"] = token_price_map.get(ts[0]) - m["buy_no_live"] = token_price_map.get(ts[1]) + m["buy_yes_live"] = token_price_map.get(f"{ts[0]}:ask") + m["buy_no_live"] = token_price_map.get(f"{ts[1]}:ask") # 优先使用发现阶段已经识别出的城市名 city = m.get("city") @@ -286,36 +287,31 @@ def main(): buy_yes_price = prices.get("buy_yes") buy_no_price = prices.get("buy_no") - # 最后的回退:使用原始 Gamma 概率 + # 最后的回退:使用原始 Gamma 概率 (利用 outcome_index 获取正确的那一个) if buy_yes_price is None or buy_no_price is None: gamma_prices = market.get("prices", []) if isinstance(gamma_prices, str): gamma_prices = json.loads(gamma_prices) - prob = float(gamma_prices[0]) if gamma_prices else 0.5 + + # 尝试获取该档位相对应的概率索引 + idx = market.get("outcome_index", 0) + prob = float(gamma_prices[idx]) if (gamma_prices and idx < len(gamma_prices)) else 0.5 + if buy_yes_price is None: buy_yes_price = prob if buy_no_price is None: buy_no_price = 1.0 - prob - # C. 准备缓存 - temp_unit = weather_data.get("open-meteo", {}).get( - "unit", "celsius" - ) + # C. 准备缓存数据 + temp_unit = weather_data.get("open-meteo", {}).get("unit", "celsius") temp_symbol = "°F" if temp_unit == "fahrenheit" else "°C" - city_local_time = ( - weather_data.get("open-meteo", {}) - .get("current", {}) - .get("local_time") - ) - + city_local_time = weather_data.get("open-meteo", {}).get("current", {}).get("local_time") + current_price = buy_yes_price if buy_yes_price else 0.5 - + # 计算价格趋势 prev_data = price_history.get(market_id, {}) prev_price = prev_data.get("price", current_price) - if prev_price > 0: - price_change_pct = ((current_price - prev_price) / prev_price) * 100 - else: - price_change_pct = 0 + price_change_pct = ((current_price - prev_price) / prev_price * 100) if prev_price > 0 else 0 - # 更新价格历史 + # 更新价格历史缓存 price_history[market_id] = { "price": current_price, "timestamp": datetime.now().isoformat() @@ -327,8 +323,8 @@ def main(): "option": question, "prediction": f"{ref_temp}{temp_symbol}", "price": int(current_price * 100), - "buy_yes": int(buy_yes_price * 100), - "buy_no": int(buy_no_price * 100), + "buy_yes": int(buy_yes_price * 100) if buy_yes_price else 0, + "buy_no": int(buy_no_price * 100) if buy_no_price else 0, "url": f"https://polymarket.com/event/{market.get('slug')}", "local_time": city_local_time, "target_date": target_date, @@ -337,30 +333,44 @@ def main(): "trend": round(price_change_pct, 1), } - if buy_yes_price <= 0.01 or buy_yes_price >= 0.99: + # --- 最终过滤器 (拦截垃圾信号) --- + + # 1. 过滤已锁定价格 (>= 98.5c) + if (buy_yes_price and buy_yes_price >= 0.985) or (buy_no_price and buy_no_price >= 0.985): cache_entry["rationale"] = "ENDED" all_markets_cache[market_id] = cache_entry continue - # D. 评分 - signal = decision_engine.calculate_signal( - model_prediction=predictor.predict_ensemble([ref_temp]), - market_data={ - "orderbook": {}, - "price_history": [current_price], - "transactions": [], - }, - weather_consensus={"average_temp": ref_temp}, - whale_activity=None, - ) - cache_entry["score"] = signal["final_score"] - cache_entry["rationale"] = signal.get("recommendation", "N/A") + # 2. 过滤已过期日期 (对比当前日期: 2026-02-06) + current_today = "2026-02-06" + if target_date and target_date < current_today: + cache_entry["rationale"] = "EXPIRED" + all_markets_cache[market_id] = cache_entry + continue + + # 3. 评分计算 + try: + signal = decision_engine.calculate_signal( + model_prediction=predictor.predict_ensemble([ref_temp]), + market_data={ + "orderbook": {}, + "price_history": [current_price], + "transactions": [], + }, + weather_consensus={"average_temp": ref_temp}, + whale_activity=None, + ) + cache_entry["score"] = signal.get("final_score", 0) + cache_entry["rationale"] = signal.get("recommendation", "ACTIVE") + except Exception as e: + logger.error(f"计算信号失败 [{market_id}]: {e}") + cache_entry["score"] = 0 + cache_entry["rationale"] = "ERROR" + all_markets_cache[market_id] = cache_entry - # --- 预警收集 (仅监控价格) --- - if (0.85 <= buy_yes_price <= 0.95) or ( - 0.85 <= buy_no_price <= 0.95 - ): + # --- 预警收集 (自动推送逻辑) --- + if (buy_yes_price and 0.85 <= buy_yes_price <= 0.95) or (buy_no_price and 0.85 <= buy_no_price <= 0.95): alert_key = f"alert_{market_id}_range_85_95" if alert_key not in pushed_signals: trigger_side = ( diff --git a/src/analysis/technical_indicators.py b/src/analysis/technical_indicators.py index 85ce762b..2cca8633 100644 --- a/src/analysis/technical_indicators.py +++ b/src/analysis/technical_indicators.py @@ -21,7 +21,7 @@ class TechnicalIndicators: float: RSI值 (0-100) """ if len(prices) < period + 1: - logger.warning("Insufficient data for RSI calculation") + logger.debug("Insufficient data for RSI calculation") return 50.0 # 返回中性值 prices = np.array(prices) @@ -55,7 +55,7 @@ class TechnicalIndicators: dict: 包含上轨、中轨、下轨 """ if len(prices) < period: - logger.warning("Insufficient data for Bollinger Bands") + logger.debug("Insufficient data for Bollinger Bands") return {"upper": None, "middle": None, "lower": None} prices = np.array(prices[-period:]) diff --git a/src/data_collection/polymarket_api.py b/src/data_collection/polymarket_api.py index b2f5435f..c1722198 100644 --- a/src/data_collection/polymarket_api.py +++ b/src/data_collection/polymarket_api.py @@ -239,28 +239,49 @@ class PolymarketClient: # 根据 Polymarket CLOB 文档,获取买入成本应使用 side=BUY (即 Ask 价格) payload = [] for r in batch: - # 映射逻辑:我们想买(ask) -> API side=BUY; 我们想卖(bid) -> API side=SELL + # 遵循 CLOB API 规范:side=BUY 为买入成交价(Ask),side=SELL 为卖出成交价(Bid) side_val = "BUY" if r.get("side") == "ask" else "SELL" payload.append({"token_id": r["token_id"], "side": side_val}) response = self.session.post(url, json=payload, timeout=20) - logger.debug(f"批量价格请求: 状态码={response.status_code}") if response.status_code == 200: results = response.json() - # 结果通常是 { "token_id": "price", ... } 或 [{ "token_id": "...", "price": "..." }, ...] + + def robust_float(val): + if isinstance(val, (int, float)): return float(val) + if isinstance(val, str): + try: return float(val) + except: return 0.0 + if isinstance(val, dict): + for k in ["price", "p", "avg", "amount"]: + if k in val: return robust_float(val[k]) + return 0.0 + if isinstance(results, dict): for tid, p in results.items(): - all_prices[tid] = float(p) + val = robust_float(p) + # 如果是字典格式,默认我们请求的是 BUY(ask) + all_prices[tid] = val + all_prices[f"{tid}:ask"] = val elif isinstance(results, list): for item in results: - if "token_id" in item and "price" in item: - all_prices[item["token_id"]] = float(item["price"]) + tid = item.get("token_id") + price_raw = item.get("price") + side = item.get("side") + if tid and price_raw: + val = robust_float(price_raw) + # 存储映射:API 的 BUY 对应我们的 ask 键 + key_side = "ask" if side == "BUY" else "bid" + all_prices[f"{tid}:{key_side}"] = val + all_prices[tid] = val else: - logger.warning(f"批量价格返回非dict格式: {type(results)}") + logger.warning(f"批量价格返回非预期格式: {type(results)}") return all_prices except Exception as e: - logger.warning(f"批量获取盘口价格失败: {e}") + logger.warning(f"批量获取盘口价格严重失败: {e}") + import traceback + logger.debug(traceback.format_exc()) return {} def get_midpoint(self, token_id: str) -> Optional[float]: @@ -375,15 +396,23 @@ class PolymarketClient: continue c_id = m.get("conditionId") + # 识别 outcome_index + t_ids = m.get("clobTokenIds", []) + active_id = m.get("activeTokenId") + idx = 0 + if isinstance(t_ids, list) and active_id in t_ids: + idx = t_ids.index(active_id) + # 对于多选一市场,不同档位共享 conditionId,但 tokenId 不同 - unique_key = f"{c_id}_{m.get('activeTokenId')}" + unique_key = f"{c_id}_{active_id}" if c_id and unique_key not in seen_condition_ids: all_weather_markets.append( { "condition_id": c_id, "question": question, - "active_token_id": m.get("activeTokenId"), - "tokens": m.get("clobTokenIds"), + "active_token_id": active_id, + "outcome_index": idx, + "tokens": t_ids, "prices": m.get("outcomePrices"), "event_title": title, "slug": event_slug, diff --git a/src/models/statistical_model.py b/src/models/statistical_model.py index b7df5ac8..56d325df 100644 --- a/src/models/statistical_model.py +++ b/src/models/statistical_model.py @@ -9,7 +9,7 @@ try: HAS_STATSMODELS = True except ImportError: HAS_STATSMODELS = False - logger.warning("statsmodels not installed, ARIMA model unavailable") + logger.debug("statsmodels not installed, ARIMA model unavailable") try: from sklearn.ensemble import RandomForestRegressor @@ -17,7 +17,7 @@ try: HAS_SKLEARN = True except ImportError: HAS_SKLEARN = False - logger.warning("scikit-learn not installed, ML models unavailable") + logger.debug("scikit-learn not installed, ML models unavailable") class TemperaturePredictor: