feat: Introduce new modules for technical analysis, Polymarket API, and statistical models, while enhancing bot resilience and refining market data processing with advanced filtering.

This commit is contained in:
2569718930@qq.com
2026-02-06 22:47:16 +08:00
parent 7d733a7ae8
commit e0d759819c
5 changed files with 110 additions and 60 deletions
+12 -1
View File
@@ -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__":
+54 -44
View File
@@ -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 = (
+2 -2
View File
@@ -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:])
+40 -11
View File
@@ -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,
+2 -2
View File
@@ -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: