feat: implement read-only Polymarket data collection layer with market discovery and price fetching

This commit is contained in:
2569718930@qq.com
2026-04-21 21:54:20 +08:00
parent 9eba41cd1c
commit bb6fe21166
2 changed files with 73 additions and 9 deletions
+17 -9
View File
@@ -690,9 +690,15 @@ class PolymarketReadOnlyLayer:
bucket["no_buy"] = no_buy
if no_sell is not None:
bucket["no_sell"] = no_sell
if yes_midpoint is not None:
bucket["market_price"] = yes_midpoint
bucket["probability"] = yes_midpoint
reference_price = yes_midpoint
if reference_price is None and yes_buy is not None and yes_sell is not None:
reference_price = (yes_buy + yes_sell) / 2.0
if reference_price is None:
reference_price = yes_buy if yes_buy is not None else yes_sell
if reference_price is not None:
reference_price = max(0.0, min(1.0, float(reference_price)))
bucket["market_price"] = reference_price
bucket["probability"] = reference_price
if yes_prices.get("quote_source"):
bucket["quote_source"] = yes_prices.get("quote_source")
if yes_prices.get("quote_age_ms") is not None:
@@ -819,7 +825,6 @@ class PolymarketReadOnlyLayer:
ended_at = parsed
break
now_utc = datetime.now(timezone.utc)
tradable = True
reason = None
if closed:
@@ -831,9 +836,6 @@ class PolymarketReadOnlyLayer:
elif accepting_orders is False:
tradable = False
reason = "not_accepting_orders"
elif ended_at is not None and ended_at <= now_utc:
tradable = False
reason = "past_end_time"
return {
"active": active,
@@ -1397,6 +1399,8 @@ class PolymarketReadOnlyLayer:
"sell": sell,
"midpoint": midpoint,
"last_trade_price": last_trade,
"quote_source": "polymarket_clob_client",
"quote_age_ms": 0,
"book": book,
"book_liquidity": book_liquidity,
}
@@ -1420,6 +1424,8 @@ class PolymarketReadOnlyLayer:
"sell": sell,
"midpoint": midpoint,
"last_trade_price": last_trade,
"quote_source": "polymarket_clob_rest",
"quote_age_ms": 0,
"book": book,
"book_liquidity": book_liquidity,
}
@@ -1635,6 +1641,8 @@ class PolymarketReadOnlyLayer:
no_sell = max(0.0, min(1.0, 1.0 - yes_sell))
market_slug = str(market.get("slug") or "").strip()
row_yes_token_id = str(yes_token.get("token_id") or "").strip()
row_no_token_id = str(no_token.get("token_id") or "").strip()
top_rows.append(
{
"label": self._extract_market_bucket_label(market, bucket_temp),
@@ -1646,8 +1654,8 @@ class PolymarketReadOnlyLayer:
"yes_sell": yes_sell,
"no_buy": no_buy,
"no_sell": no_sell,
"yes_token_id": yes_token_id or None,
"no_token_id": no_token_id or None,
"yes_token_id": row_yes_token_id or None,
"no_token_id": row_no_token_id or None,
"quote_source": yes_prices.get("quote_source"),
"quote_age_ms": _safe_int(yes_prices.get("quote_age_ms"), 0),
"slug": market_slug or None,
+56
View File
@@ -59,6 +59,7 @@ def test_fetch_token_market_data_prefers_orderbook_executable_prices():
assert data["sell"] == 0.24
assert data["midpoint"] == 0.5
assert data["last_trade_price"] == 0.49
assert data["quote_source"] == "polymarket_clob_client"
def test_get_token_market_data_prefers_fresh_ws_cache():
@@ -117,6 +118,56 @@ def test_price_analysis_computes_edge_kelly_and_lock():
assert analysis["best_side"] == "yes"
def test_trade_state_keeps_open_markets_tradable_after_gamma_end_date():
layer = PolymarketReadOnlyLayer()
state = layer._market_trade_state(
{
"active": True,
"closed": False,
"acceptingOrders": True,
"endDate": "2020-01-01T00:00:00Z",
}
)
assert state["tradable"] is True
assert state["reason"] is None
assert state["ended_at_utc"] == "2020-01-01T00:00:00+00:00"
def test_hydrate_bucket_prices_uses_executable_quotes_without_midpoint():
layer = PolymarketReadOnlyLayer()
buckets = [
{
"temp": 14.0,
"yes_token_id": "yes-token",
"no_token_id": "no-token",
}
]
def _fake_get_token_market_data(token_id):
if token_id == "yes-token":
return {
"buy": 0.66,
"sell": 0.70,
"quote_source": "polymarket_clob_rest",
"quote_age_ms": 0,
}
return {"buy": 0.30, "sell": 0.36}
layer._get_token_market_data = _fake_get_token_market_data
layer._hydrate_bucket_prices(buckets)
assert buckets[0]["yes_buy"] == 0.66
assert buckets[0]["yes_sell"] == 0.70
assert buckets[0]["no_buy"] == 0.30
assert buckets[0]["no_sell"] == 0.36
assert round(buckets[0]["market_price"], 6) == 0.68
assert round(buckets[0]["probability"], 6) == 0.68
assert buckets[0]["quote_source"] == "polymarket_clob_rest"
def test_build_top_temperature_buckets_dedupes_same_temperature():
layer = PolymarketReadOnlyLayer()
@@ -195,6 +246,11 @@ def test_build_top_temperature_buckets_dedupes_same_temperature():
)
values = [row.get("value") for row in rows]
token_ids = [row.get("yes_token_id") for row in rows]
assert len(values) == len(set(values))
assert len(token_ids) == len(set(token_ids))
assert rows[0]["value"] == 14.0
assert rows[0]["yes_token_id"] == (
"highest-temperature-in-ankara-on-march-12-2026-14c-or-higher|yes"
)
assert all(not str(row.get("label") or "").startswith("<=") for row in rows)