From 42d4a4e198b1a529fbb5e26ba19abbe77a1f6962 Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Fri, 24 Apr 2026 14:26:47 +0800
Subject: [PATCH] Simplify scan opportunity list and speed quotes
---
.../components/dashboard/Dashboard.module.css | 67 +----------------
.../components/dashboard/OpportunityTable.tsx | 40 +----------
src/data_collection/polymarket_readonly.py | 72 +++++++++++++++----
tests/test_polymarket_readonly.py | 30 ++++++++
4 files changed, 92 insertions(+), 117 deletions(-)
diff --git a/frontend/components/dashboard/Dashboard.module.css b/frontend/components/dashboard/Dashboard.module.css
index a0202ee2..9586f9a5 100644
--- a/frontend/components/dashboard/Dashboard.module.css
+++ b/frontend/components/dashboard/Dashboard.module.css
@@ -9572,7 +9572,7 @@
.root :global(.scan-table-header) {
display: grid;
- grid-template-columns: 46px minmax(180px, 1.08fr) minmax(132px, 0.72fr) minmax(220px, 1.08fr) minmax(150px, 0.82fr) 92px 82px;
+ grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
gap: 10px;
align-items: center;
padding: 14px 18px;
@@ -9786,7 +9786,7 @@
.root :global(.scan-table-row) {
display: grid;
- grid-template-columns: 46px minmax(180px, 1.08fr) minmax(132px, 0.72fr) minmax(220px, 1.08fr) minmax(150px, 0.82fr) 92px 82px;
+ grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
gap: 10px;
align-items: center;
min-height: 132px;
@@ -9813,38 +9813,12 @@
box-shadow: inset 0 0 0 1px rgba(23, 217, 139, 0.48);
}
-.root :global(.scan-rank-cell),
-.root :global(.scan-score-cell),
.root :global(.scan-edge-cell) {
display: flex;
align-items: center;
justify-content: center;
}
-.root :global(.scan-rank-circle) {
- width: 34px;
- height: 34px;
- font-size: 16px;
- font-weight: 800;
- color: #06150d;
- background: linear-gradient(180deg, #24e495, #11b86f);
-}
-
-.root :global(.scan-rank-circle.yellow) {
- color: #281a00;
- background: linear-gradient(180deg, #ffd95f, #ffb61c);
-}
-
-.root :global(.scan-rank-circle.gray) {
- color: #08131f;
- background: linear-gradient(180deg, #d6e3f7, #94a7c4);
-}
-
-.root :global(.scan-rank-circle.red) {
- color: #fff3f3;
- background: linear-gradient(180deg, #ff7373, #df4747);
-}
-
.root :global(.scan-city-cell) {
display: block;
}
@@ -9870,11 +9844,6 @@
-webkit-line-clamp: 2;
}
-.root :global(.scan-city-volume) {
- font-size: 14px;
- color: #dbe7f8;
-}
-
.root :global(.scan-time-cell) {
display: flex;
flex-direction: column;
@@ -10036,38 +10005,6 @@
color: #ff6868;
}
-.root :global(.scan-score-ring) {
- width: 58px;
- height: 58px;
- border-radius: 50%;
- background: conic-gradient(#20df91 0 85%, rgba(255, 255, 255, 0.06) 85% 100%);
-}
-
-.root :global(.scan-score-ring.tone-yellow) {
- background: conic-gradient(#ffbe26 0 72%, rgba(255, 255, 255, 0.06) 72% 100%);
-}
-
-.root :global(.scan-score-ring.tone-red) {
- background: conic-gradient(#ff5d5d 0 58%, rgba(255, 255, 255, 0.06) 58% 100%);
-}
-
-.root :global(.scan-score-ring::after) {
- content: "";
- position: absolute;
- inset: 5px;
- border-radius: 50%;
- background: #0b1728;
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
-}
-
-.root :global(.scan-score-ring span) {
- position: relative;
- z-index: 1;
- font-size: 18px;
- font-weight: 800;
- color: #ecf4ff;
-}
-
.root :global(.scan-row-fav) {
display: none;
}
diff --git a/frontend/components/dashboard/OpportunityTable.tsx b/frontend/components/dashboard/OpportunityTable.tsx
index e7609ce7..f0f8b7c9 100644
--- a/frontend/components/dashboard/OpportunityTable.tsx
+++ b/frontend/components/dashboard/OpportunityTable.tsx
@@ -24,14 +24,6 @@ function formatPercent(value?: number | null, signed = false) {
return `${signed && numeric >= 0 ? "+" : ""}${numeric.toFixed(1)}%`;
}
-function formatVolume(value?: number | null) {
- const numeric = Number(value || 0);
- if (!Number.isFinite(numeric) || numeric <= 0) return "--";
- if (numeric >= 1_000_000) return `$${(numeric / 1_000_000).toFixed(1)}M`;
- if (numeric >= 1_000) return `$${(numeric / 1_000).toFixed(0)}K`;
- return `$${numeric.toFixed(0)}`;
-}
-
function formatWindowMinutes(value: number | null | undefined, locale: string) {
if (value == null || !Number.isFinite(Number(value))) return "--";
const minutes = Math.max(0, Math.round(Number(value)));
@@ -118,13 +110,6 @@ export function getWindowPhaseMeta(
};
}
-function scoreTone(score?: number | null) {
- const numeric = Number(score || 0);
- if (numeric >= 85) return "green";
- if (numeric >= 70) return "yellow";
- return "gray";
-}
-
function ProbabilityPreview({
row,
locale,
@@ -197,15 +182,6 @@ function ProbabilityPreview({
);
}
-function ScoreRing({ score }: { score?: number | null }) {
- const displayScore = Math.max(0, Math.min(100, Number(score || 0)));
- return (
-
- {displayScore.toFixed(0)}
-
- );
-}
-
export const OpportunityTable = React.memo(function OpportunityTable({
rows,
status,
@@ -271,17 +247,15 @@ export const OpportunityTable = React.memo(function OpportunityTable({
) : null}
-
{isEn ? "City / Market" : "城市 / 市场"}
{isEn ? "Local Time / Phase" : "当前时间 / 阶段"}
{isEn ? "EMOS / Market" : "EMOS / 市场"}
{isEn ? "Quote / Model" : "买价 / 模型"}
{isEn ? "Edge" : "边际优势"}
- {isEn ? "Score" : "综合得分"}
- {rows.map((row, index) => {
+ {rows.map((row) => {
const phaseMeta = getWindowPhaseMeta(row, locale);
const tempSymbol = normalizeTemperatureSymbol(row.target_unit || row.temp_symbol);
const localizedCityName = getLocalizedCityName(
@@ -290,7 +264,6 @@ export const OpportunityTable = React.memo(function OpportunityTable({
locale,
);
const selected = selectedRowId === row.id;
- const scoreClass = scoreTone(row.final_score);
return (
);
})}
diff --git a/src/data_collection/polymarket_readonly.py b/src/data_collection/polymarket_readonly.py
index d806b838..6ec6370b 100644
--- a/src/data_collection/polymarket_readonly.py
+++ b/src/data_collection/polymarket_readonly.py
@@ -418,6 +418,8 @@ class PolymarketReadOnlyLayer:
_safe_float(os.getenv("POLYMARKET_SIGNAL_MIN_LIQUIDITY")) or 500.0
)
self.edge_threshold = _safe_float(os.getenv("POLYMARKET_SIGNAL_EDGE_PCT")) or 2.0
+ fast_price_only = _safe_bool(os.getenv("POLYMARKET_FAST_PRICE_ONLY", "true"))
+ self.fast_price_only = True if fast_price_only is None else bool(fast_price_only)
self._session = httpx.Client(
timeout=self.http_timeout,
@@ -1747,6 +1749,22 @@ class PolymarketReadOnlyLayer:
ask = _extract_price(
self._clob_get("/price", {"token_id": token_id, "side": "SELL"})
)
+ if self.fast_price_only:
+ buy, sell = self._resolve_trade_prices(buy=ask, sell=bid, book=None)
+ midpoint = (buy + sell) / 2.0 if buy is not None and sell is not None else (buy or sell)
+ spread = max(0.0, float(buy) - float(sell)) if buy is not None and sell is not None else None
+ return {
+ "buy": buy,
+ "sell": sell,
+ "midpoint": _clamp_probability(midpoint),
+ "spread": spread,
+ "last_trade_price": None,
+ "quote_source": "polymarket_clob_fast_price",
+ "quote_age_ms": 0,
+ "book": None,
+ "book_liquidity": None,
+ }
+
midpoint = _extract_price(self._clob_get("/midpoint", {"token_id": token_id}))
last_trade = _extract_price(
self._clob_get("/last-trade-price", {"token_id": token_id})
@@ -1754,10 +1772,14 @@ class PolymarketReadOnlyLayer:
orderbook_raw = self._clob_get("/book", {"token_id": token_id})
book, book_liquidity = self._normalize_orderbook(orderbook_raw)
buy, sell = self._resolve_trade_prices(buy=ask, sell=bid, book=book)
+ if midpoint is None and buy is not None and sell is not None:
+ midpoint = (buy + sell) / 2.0
+ spread = max(0.0, float(buy) - float(sell)) if buy is not None and sell is not None else None
return {
"buy": buy,
"sell": sell,
"midpoint": midpoint,
+ "spread": spread,
"last_trade_price": last_trade,
"quote_source": "polymarket_clob_rest",
"quote_age_ms": 0,
@@ -2326,7 +2348,12 @@ class PolymarketReadOnlyLayer:
missing.append(token_id)
continue
cached_data = cached.get("data", {}) or {}
- if include_books and not cached_data.get("book") and cached_data.get("book_liquidity") is None:
+ if (
+ include_books
+ and not self.fast_price_only
+ and not cached_data.get("book")
+ and cached_data.get("book_liquidity") is None
+ ):
missing.append(token_id)
continue
results[token_id] = dict(cached_data)
@@ -2350,24 +2377,36 @@ class PolymarketReadOnlyLayer:
"/prices",
[{"token_id": token_id, "side": "BUY"} for token_id in chunk],
)
- midpoint_payload = self._clob_post(
- "/midpoints",
- [{"token_id": token_id} for token_id in chunk],
+ midpoint_payload = (
+ self._clob_post(
+ "/midpoints",
+ [{"token_id": token_id} for token_id in chunk],
+ )
+ if not self.fast_price_only
+ else None
)
- spread_payload = self._clob_post(
- "/spreads",
- [{"token_id": token_id} for token_id in chunk],
+ spread_payload = (
+ self._clob_post(
+ "/spreads",
+ [{"token_id": token_id} for token_id in chunk],
+ )
+ if not self.fast_price_only
+ else None
)
- last_trade_payload = self._clob_post(
- "/last-trade-prices",
- [{"token_id": token_id} for token_id in chunk],
+ last_trade_payload = (
+ self._clob_post(
+ "/last-trade-prices",
+ [{"token_id": token_id} for token_id in chunk],
+ )
+ if not self.fast_price_only
+ else None
)
books_payload = (
self._clob_post(
"/books",
[{"token_id": token_id} for token_id in chunk],
)
- if include_books
+ if include_books and not self.fast_price_only
else None
)
ask_map.update(self._extract_batch_price_map(sell_payload, "SELL"))
@@ -2419,7 +2458,11 @@ class PolymarketReadOnlyLayer:
"midpoint": midpoint,
"spread": spread,
"last_trade_price": last_trade,
- "quote_source": "polymarket_clob_rest_batch",
+ "quote_source": (
+ "polymarket_clob_fast_batch"
+ if self.fast_price_only
+ else "polymarket_clob_rest_batch"
+ ),
"quote_age_ms": 0,
"book": book,
"book_liquidity": book_liquidity,
@@ -3087,7 +3130,10 @@ class PolymarketReadOnlyLayer:
continue
shortlisted_tokens.extend([entry["yes_token_id"], entry["no_token_id"]])
- precise_quotes = self._batch_get_token_market_data(shortlisted_tokens, include_books=True)
+ precise_quotes = self._batch_get_token_market_data(
+ shortlisted_tokens,
+ include_books=not self.fast_price_only,
+ )
for entry in market_entries:
market_slug = str(entry["market"].get("slug") or "").strip()
if market_slug not in seen_slugs:
diff --git a/tests/test_polymarket_readonly.py b/tests/test_polymarket_readonly.py
index 388cf006..95459061 100644
--- a/tests/test_polymarket_readonly.py
+++ b/tests/test_polymarket_readonly.py
@@ -39,6 +39,7 @@ def test_extract_market_bucket_range_supports_fahrenheit_ranges():
def test_fetch_token_market_data_uses_rest_orderbook_executable_prices():
layer = PolymarketReadOnlyLayer()
+ layer.fast_price_only = False
payloads = {
("/price", "BUY"): {"price": "0.27"},
("/price", "SELL"): {"price": "0.23"},
@@ -68,8 +69,37 @@ def test_fetch_token_market_data_uses_rest_orderbook_executable_prices():
assert data["quote_source"] == "polymarket_clob_rest"
+def test_fetch_token_market_data_fast_price_only_skips_heavy_endpoints():
+ layer = PolymarketReadOnlyLayer()
+ calls = []
+ payloads = {
+ ("/price", "BUY"): {"price": "0.23"},
+ ("/price", "SELL"): {"price": "0.27"},
+ }
+
+ def _fake_clob_get(path, params):
+ calls.append((path, params.get("side")))
+ if path == "/price":
+ return payloads[(path, params.get("side"))]
+ return None
+
+ layer._clob_get = _fake_clob_get
+
+ data = layer._fetch_token_market_data("token-1")
+
+ assert calls == [("/price", "BUY"), ("/price", "SELL")]
+ assert data["buy"] == 0.27
+ assert data["sell"] == 0.23
+ assert data["midpoint"] == 0.25
+ assert round(data["spread"], 6) == 0.04
+ assert data["last_trade_price"] is None
+ assert data["book"] is None
+ assert data["quote_source"] == "polymarket_clob_fast_price"
+
+
def test_fetch_token_market_data_keeps_buy_sell_semantics_without_orderbook():
layer = PolymarketReadOnlyLayer()
+ layer.fast_price_only = False
payloads = {
("/price", "BUY"): {"price": "0.23"},
("/price", "SELL"): {"price": "0.27"},