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:
+12
-1
@@ -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__":
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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:])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user