diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 759694dc..ff98e640 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -142,6 +142,34 @@ function getSideRow( return null; } +function getDetailSideAsk( + scanRow: ScanOpportunityRow | null, + marketScan: MarketScan | null | undefined, + side: "yes" | "no", +) { + if (scanRow?.ask != null) return scanRow.ask; + if (side === "yes") { + if (scanRow?.yes_ask != null) return scanRow.yes_ask; + return marketScan?.yes_buy ?? null; + } + if (scanRow?.no_ask != null) return scanRow.no_ask; + return marketScan?.no_buy ?? null; +} + +function getDetailSideBid( + scanRow: ScanOpportunityRow | null, + marketScan: MarketScan | null | undefined, + side: "yes" | "no", +) { + if (scanRow?.bid != null) return scanRow.bid; + if (side === "yes") { + if (scanRow?.yes_bid != null) return scanRow.yes_bid; + return marketScan?.yes_sell ?? null; + } + if (scanRow?.no_bid != null) return scanRow.no_bid; + return marketScan?.no_sell ?? null; +} + function buildComparisonBuckets( marketScan: MarketScan | null | undefined, row: ScanOpportunityRow | null, @@ -396,24 +424,30 @@ function DetailPanel({ {isEn ? "Buy Yes" : "买入 Yes"} {displayRow.target_label || ""}

- {formatPrice(yesRow?.ask ?? marketScan?.yes_buy)} →{" "} + {formatPrice(getDetailSideAsk(yesRow, marketScan, "yes"))} →{" "} {formatProbability(yesRow?.model_probability)}

{formatPercent(yesRow?.edge_percent, true)} {isEn ? "edge" : "边际优势"}

-

{isEn ? "Spread" : "点差"} {formatPrice(yesRow?.spread)}

+

+ {isEn ? "Bid / Ask" : "买卖价"} {formatPrice(getDetailSideBid(yesRow, marketScan, "yes"))} /{" "} + {formatPrice(getDetailSideAsk(yesRow, marketScan, "yes"))} +

{isEn ? "Buy No" : "买入 No"} {displayRow.target_label || ""}

- {formatPrice(noRow?.ask ?? marketScan?.no_buy)} →{" "} + {formatPrice(getDetailSideAsk(noRow, marketScan, "no"))} →{" "} {formatProbability(noRow?.model_probability)}

= 0 ? "positive" : "negative"}> {formatPercent(noRow?.edge_percent, true)} {isEn ? "edge" : "边际优势"}

-

{isEn ? "Spread" : "点差"} {formatPrice(noRow?.spread)}

+

+ {isEn ? "Bid / Ask" : "买卖价"} {formatPrice(getDetailSideBid(noRow, marketScan, "no"))} /{" "} + {formatPrice(getDetailSideAsk(noRow, marketScan, "no"))} +

diff --git a/frontend/playwright-live-home.png b/frontend/playwright-live-home.png new file mode 100644 index 00000000..c2cb0570 Binary files /dev/null and b/frontend/playwright-live-home.png differ diff --git a/tests/test_polymarket_readonly.py b/tests/test_polymarket_readonly.py index 297a2e26..07b862bf 100644 --- a/tests/test_polymarket_readonly.py +++ b/tests/test_polymarket_readonly.py @@ -40,8 +40,8 @@ def test_extract_market_bucket_range_supports_fahrenheit_ranges(): def test_fetch_token_market_data_uses_rest_orderbook_executable_prices(): layer = PolymarketReadOnlyLayer() payloads = { - ("/price", "BUY"): {"price": "0.11"}, - ("/price", "SELL"): {"price": "0.88"}, + ("/price", "BUY"): {"price": "0.27"}, + ("/price", "SELL"): {"price": "0.23"}, ("/midpoint", None): {"midpoint": "0.50"}, ("/last-trade-price", None): {"price": "0.49"}, ("/book", None): { @@ -68,6 +68,30 @@ def test_fetch_token_market_data_uses_rest_orderbook_executable_prices(): assert data["quote_source"] == "polymarket_clob_rest" +def test_fetch_token_market_data_keeps_buy_sell_semantics_without_orderbook(): + layer = PolymarketReadOnlyLayer() + payloads = { + ("/price", "BUY"): {"price": "0.23"}, + ("/price", "SELL"): {"price": "0.27"}, + ("/midpoint", None): {"midpoint": "0.25"}, + ("/last-trade-price", None): {"price": "0.24"}, + ("/book", None): None, + } + + def _fake_clob_get(path, params): + if path == "/price": + return payloads[(path, params.get("side"))] + return payloads[(path, None)] + + layer._clob_get = _fake_clob_get + + data = layer._fetch_token_market_data("token-1") + + assert data["buy"] == 0.27 + assert data["sell"] == 0.23 + assert data["midpoint"] == 0.25 + + def test_get_token_market_data_uses_price_cache_within_ttl(): layer = PolymarketReadOnlyLayer() calls = []