From e9cc9a5eb11b71a6ff0e10ba16e2c82f361112f3 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 00:41:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=BA=E6=9E=84=E8=90=BD?= =?UTF-8?q?=E5=9C=B0=E9=A1=B5=E6=9E=84=E5=BB=BA=E5=A4=B1=E8=B4=A5=E5=B9=B6?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=20Polymarket=20=E5=B8=82=E5=9C=BA=E6=89=AB?= =?UTF-8?q?=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstitutionalLandingPage.tsx 补充 "use client" 指令以通过 Next.js 15 构建。 polymarket_readonly.py 集成 WebSocket 报价缓存加速价格获取。 city_payloads.py 复用 Polymarket 层构建真实市场扫描数据替代空返回。 Constraint: 市场扫描需在无 Polymarket 价格 UI 的前提下提供数据 Tested: npm run build 通过 --- .../landing/InstitutionalLandingPage.tsx | 2 + src/data_collection/polymarket_readonly.py | 13 ++++ web/services/city_payloads.py | 60 +++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/frontend/components/landing/InstitutionalLandingPage.tsx b/frontend/components/landing/InstitutionalLandingPage.tsx index e11affe5..44a941eb 100644 --- a/frontend/components/landing/InstitutionalLandingPage.tsx +++ b/frontend/components/landing/InstitutionalLandingPage.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useState } from "react"; import Link from "next/link"; import { diff --git a/src/data_collection/polymarket_readonly.py b/src/data_collection/polymarket_readonly.py index 07c7048e..a520b0c8 100644 --- a/src/data_collection/polymarket_readonly.py +++ b/src/data_collection/polymarket_readonly.py @@ -4,6 +4,7 @@ Polymarket read-only market layer. P0 scope: - Market discovery from Gamma REST - Price / midpoint / spread / orderbook read from CLOB REST +- Optional WebSocket quote acceleration via PolymarketWsQuoteCache - No signing, no order placement """ @@ -23,6 +24,7 @@ import httpx from loguru import logger from src.data_collection.city_registry import ALIASES, CITY_REGISTRY +from src.data_collection.polymarket_ws_cache import PolymarketWsQuoteCache def _safe_float(value: Any) -> Optional[float]: @@ -448,6 +450,9 @@ class PolymarketReadOnlyLayer: self._price_cache: Dict[str, Dict[str, Any]] = {} self._lock = threading.Lock() + self._ws_cache = PolymarketWsQuoteCache.from_env() + self._ws_cache.start() + def _market_scan_debug_enabled(self) -> bool: return ( str(os.getenv("POLYMARKET_MARKET_SCAN_DEBUG", "false")).strip().lower() @@ -1769,6 +1774,14 @@ class PolymarketReadOnlyLayer: if cached and now - cached.get("t", 0) < self.price_cache_ttl: return cached.get("data", {}) + ws_data = self._ws_cache.get_market_data(token_id) + if ws_data: + with self._lock: + self._price_cache[token_id] = {"data": ws_data, "t": now} + return ws_data + + self._ws_cache.subscribe([token_id]) + data = self._fetch_token_market_data(token_id) with self._lock: diff --git a/web/services/city_payloads.py b/web/services/city_payloads.py index 1748311a..f0819ec2 100644 --- a/web/services/city_payloads.py +++ b/web/services/city_payloads.py @@ -8,6 +8,26 @@ from web.core import _is_excluded_model_name TURKISH_MGM_CITIES = {"ankara", "istanbul"} +_polymarket_layer = None + + +def _get_polymarket_layer(): + global _polymarket_layer + if _polymarket_layer is None: + from src.data_collection.polymarket_readonly import PolymarketReadOnlyLayer + + _polymarket_layer = PolymarketReadOnlyLayer() + return _polymarket_layer + + +def _top_probability_bucket(distribution: Any) -> Optional[Dict[str, Any]]: + if not isinstance(distribution, list): + return None + candidates = [row for row in distribution if isinstance(row, dict)] + if not candidates: + return None + return max(candidates, key=lambda row: float(row.get("probability") or -1.0)) + def build_city_summary_payload(data: Dict[str, Any]) -> Dict[str, Any]: return { @@ -46,11 +66,41 @@ def build_city_market_scan_payload( requested_date = str(target_date or "").strip() selected_date = requested_date or local_date - return { - "market_scan": {"available": False}, - "selected_date": selected_date, - "fetched_at": data.get("updated_at"), - } + try: + layer = _get_polymarket_layer() + probabilities = data.get("probabilities") or {} + distribution = probabilities.get("distribution") or [] + top_bucket = _top_probability_bucket(distribution) + model_probability = ( + float(top_bucket.get("probability")) + if (isinstance(top_bucket, dict) and top_bucket.get("probability") is not None) + else None + ) + + scan = layer.build_market_scan( + city=data.get("name"), + target_date=selected_date, + temperature_bucket=top_bucket, + model_probability=model_probability, + probability_distribution=distribution, + temp_symbol=str(data.get("temp_symbol") or ""), + forced_market_slug=market_slug, + include_related_buckets=not lite, + scan_filters=scan_filters, + ) + return { + "market_scan": scan, + "selected_date": selected_date, + "fetched_at": data.get("updated_at"), + } + except Exception: + import traceback + traceback.print_exc() + return { + "market_scan": {"available": False}, + "selected_date": selected_date, + "fetched_at": data.get("updated_at"), + } def build_city_detail_payload(