Handle empty ops market scan snapshots
This commit is contained in:
@@ -114,6 +114,37 @@ def test_build_market_opportunities_scans_yes_and_no_low_price_edges():
|
|||||||
assert rows[0]["edge"] >= rows[-1]["edge"]
|
assert rows[0]["edge"] >= rows[-1]["edge"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_market_opportunities_includes_ask_equal_to_max_price():
|
||||||
|
event = {
|
||||||
|
"slug": "highest-temperature-in-shanghai-on-july-3-2026",
|
||||||
|
"markets": [
|
||||||
|
{
|
||||||
|
"question": "Will the highest temperature in Shanghai be 33°C on July 3?",
|
||||||
|
"slug": "highest-temperature-in-shanghai-on-july-3-2026-33c",
|
||||||
|
"active": True,
|
||||||
|
"closed": False,
|
||||||
|
"enableOrderBook": True,
|
||||||
|
"outcomes": '["Yes", "No"]',
|
||||||
|
"clobTokenIds": '["yes-33", "no-33"]',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = build_market_opportunity_rows(
|
||||||
|
[_row()],
|
||||||
|
{"shanghai": event},
|
||||||
|
{"yes-33": 0.18, "no-33": 0.82},
|
||||||
|
max_price=0.18,
|
||||||
|
side="yes",
|
||||||
|
positive_edge_only=True,
|
||||||
|
min_edge=0.0,
|
||||||
|
limit=20,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows[0]["ask_price"] == 0.18
|
||||||
|
|
||||||
|
|
||||||
def test_build_market_opportunities_aggregates_fahrenheit_distribution():
|
def test_build_market_opportunities_aggregates_fahrenheit_distribution():
|
||||||
event = {
|
event = {
|
||||||
"slug": "highest-temperature-in-houston-on-july-3-2026",
|
"slug": "highest-temperature-in-houston-on-july-3-2026",
|
||||||
@@ -296,6 +327,45 @@ def test_ops_market_opportunities_reuses_prewarmed_terminal_snapshot(monkeypatch
|
|||||||
assert captured["filters"]["min_liquidity"] == 500
|
assert captured["filters"]["min_liquidity"] == 500
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops_market_opportunities_force_refreshes_empty_terminal_snapshot(monkeypatch):
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(market_opportunities, "_require_ops", lambda _request: {"email": "ops@example.com"})
|
||||||
|
|
||||||
|
def fake_scan(_filters, *, force_refresh=False):
|
||||||
|
calls.append(force_refresh)
|
||||||
|
return {"rows": [_row()]} if force_refresh else {"rows": [], "status": "ready"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(market_opportunities, "build_scan_terminal_payload", fake_scan)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
market_opportunities,
|
||||||
|
"_collect_events_and_prices",
|
||||||
|
lambda rows, _scanner: ({row["city"]: None for row in rows}, {}, "ready"),
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = get_ops_market_opportunities(object(), max_price=0.90, min_edge=0.0)
|
||||||
|
|
||||||
|
assert calls == [False, True]
|
||||||
|
assert payload["summary"]["scanned_city_count"] == 1
|
||||||
|
assert payload["summary"]["quote_status"] == "ready"
|
||||||
|
assert payload["summary"]["scan_source"] == "force_refresh"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops_market_opportunities_reports_scan_empty_when_refresh_is_empty(monkeypatch):
|
||||||
|
monkeypatch.setattr(market_opportunities, "_require_ops", lambda _request: {"email": "ops@example.com"})
|
||||||
|
monkeypatch.setattr(
|
||||||
|
market_opportunities,
|
||||||
|
"build_scan_terminal_payload",
|
||||||
|
lambda *_args, **_kwargs: {"rows": [], "status": "ready"},
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = get_ops_market_opportunities(object(), max_price=0.90, min_edge=0.0)
|
||||||
|
|
||||||
|
assert payload["summary"]["scanned_city_count"] == 0
|
||||||
|
assert payload["summary"]["quote_status"] == "scan_empty"
|
||||||
|
assert payload["summary"]["matched_event_count"] == 0
|
||||||
|
assert payload["rows"] == []
|
||||||
|
|
||||||
|
|
||||||
def test_collect_events_and_prices_uses_gamma_hint_prices_without_clob_fanout():
|
def test_collect_events_and_prices_uses_gamma_hint_prices_without_clob_fanout():
|
||||||
class FakeScanner:
|
class FakeScanner:
|
||||||
def fetch_event(self, _slug):
|
def fetch_event(self, _slug):
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ def build_market_opportunity_rows(
|
|||||||
continue
|
continue
|
||||||
ask = ask_prices_by_token.get(token_id)
|
ask = ask_prices_by_token.get(token_id)
|
||||||
ask_number = _finite_number(ask)
|
ask_number = _finite_number(ask)
|
||||||
if ask_number is None or ask_number <= 0 or ask_number >= float(max_price):
|
if ask_number is None or ask_number <= 0 or ask_number > float(max_price):
|
||||||
continue
|
continue
|
||||||
if option_side == "no" and _is_late_priced_no_noise(
|
if option_side == "no" and _is_late_priced_no_noise(
|
||||||
scan_row,
|
scan_row,
|
||||||
@@ -549,20 +549,38 @@ def get_ops_market_opportunities(
|
|||||||
scan_rows = scan_payload.get("rows") if isinstance(scan_payload, dict) else []
|
scan_rows = scan_payload.get("rows") if isinstance(scan_payload, dict) else []
|
||||||
if not isinstance(scan_rows, list):
|
if not isinstance(scan_rows, list):
|
||||||
scan_rows = []
|
scan_rows = []
|
||||||
|
scan_source = "cache"
|
||||||
|
if not scan_rows:
|
||||||
|
refreshed_payload = build_scan_terminal_payload(filters, force_refresh=True)
|
||||||
|
refreshed_rows = refreshed_payload.get("rows") if isinstance(refreshed_payload, dict) else []
|
||||||
|
if isinstance(refreshed_rows, list) and refreshed_rows:
|
||||||
|
scan_payload = refreshed_payload
|
||||||
|
scan_rows = refreshed_rows
|
||||||
|
scan_source = "force_refresh"
|
||||||
|
|
||||||
quote_status = "ready"
|
quote_status = "ready"
|
||||||
try:
|
if not scan_rows:
|
||||||
events_by_city, ask_prices, quote_status = _collect_events_and_prices(
|
|
||||||
scan_rows,
|
|
||||||
PolymarketQuoteScanner(),
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
events_by_city = {}
|
events_by_city = {}
|
||||||
ask_prices = {}
|
ask_prices = {}
|
||||||
quote_status = "unavailable"
|
quote_status = "scan_empty"
|
||||||
error = str(exc)
|
error = (
|
||||||
|
str(scan_payload.get("stale_reason") or scan_payload.get("error") or "")
|
||||||
|
if isinstance(scan_payload, dict)
|
||||||
|
else ""
|
||||||
|
) or "scan terminal returned no rows"
|
||||||
else:
|
else:
|
||||||
error = None
|
try:
|
||||||
|
events_by_city, ask_prices, quote_status = _collect_events_and_prices(
|
||||||
|
scan_rows,
|
||||||
|
PolymarketQuoteScanner(),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
events_by_city = {}
|
||||||
|
ask_prices = {}
|
||||||
|
quote_status = "unavailable"
|
||||||
|
error = str(exc)
|
||||||
|
else:
|
||||||
|
error = None
|
||||||
|
|
||||||
rows = build_market_opportunity_rows(
|
rows = build_market_opportunity_rows(
|
||||||
scan_rows,
|
scan_rows,
|
||||||
@@ -595,6 +613,7 @@ def get_ops_market_opportunities(
|
|||||||
"min_price": min(prices) if prices else None,
|
"min_price": min(prices) if prices else None,
|
||||||
"max_edge": max(edges) if edges else None,
|
"max_edge": max(edges) if edges else None,
|
||||||
"quote_status": quote_status,
|
"quote_status": quote_status,
|
||||||
|
"scan_source": scan_source,
|
||||||
"scanned_city_count": len(scan_rows),
|
"scanned_city_count": len(scan_rows),
|
||||||
"matched_event_count": sum(1 for event in events_by_city.values() if isinstance(event, Mapping)),
|
"matched_event_count": sum(1 for event in events_by_city.values() if isinstance(event, Mapping)),
|
||||||
"error": error,
|
"error": error,
|
||||||
|
|||||||
Reference in New Issue
Block a user