Fix ops market opportunity scan snapshot
This commit is contained in:
@@ -57,4 +57,10 @@ export function runTests() {
|
||||
client.includes("positive_edge_only") && client.includes("显示全部低价"),
|
||||
"market opportunities page must default to positive edge and allow showing every low-price option",
|
||||
);
|
||||
assert(
|
||||
client.includes("最高买入价") &&
|
||||
client.includes("最小 Edge") &&
|
||||
client.includes('showAllLowPrice ? "-1" : minEdge'),
|
||||
"market opportunities filters must label price versus edge and not edge-filter the show-all-low-price view",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export function MarketOpportunitiesPageClient() {
|
||||
q: query.trim(),
|
||||
side,
|
||||
max_price: maxPrice,
|
||||
min_edge: minEdge,
|
||||
min_edge: showAllLowPrice ? "-1" : minEdge,
|
||||
positive_edge_only,
|
||||
limit: 200,
|
||||
});
|
||||
@@ -142,7 +142,7 @@ export function MarketOpportunitiesPageClient() {
|
||||
市场机会
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-slate-300">
|
||||
仅 Ops 内部使用,扫描低于 20¢ 的 Yes/No 选项,并按模型概率与市场价格差排序。
|
||||
仅 Ops 内部使用,按价格上限扫描 Yes/No 选项,并按模型概率与市场价格差排序。
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={load} className="gap-1.5">
|
||||
@@ -161,7 +161,7 @@ export function MarketOpportunitiesPageClient() {
|
||||
<Card>
|
||||
<CardHeader className="gap-3">
|
||||
<CardTitle>低价选项扫描</CardTitle>
|
||||
<div className="grid gap-2 md:grid-cols-[minmax(180px,1fr)_120px_120px_120px_auto]">
|
||||
<div className="grid items-end gap-2 md:grid-cols-[minmax(180px,1fr)_120px_132px_132px_auto]">
|
||||
<label className="relative block">
|
||||
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
@@ -174,29 +174,42 @@ export function MarketOpportunitiesPageClient() {
|
||||
placeholder="搜索城市或选项"
|
||||
/>
|
||||
</label>
|
||||
<select
|
||||
value={side}
|
||||
onChange={(event) => setSide(event.target.value)}
|
||||
className="h-9 rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
>
|
||||
<option value="both">Yes / No</option>
|
||||
<option value="yes">仅 Yes</option>
|
||||
<option value="no">仅 No</option>
|
||||
</select>
|
||||
<input
|
||||
value={maxPrice}
|
||||
onChange={(event) => setMaxPrice(event.target.value)}
|
||||
className="h-9 rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
inputMode="decimal"
|
||||
aria-label="最高买入价"
|
||||
/>
|
||||
<input
|
||||
value={minEdge}
|
||||
onChange={(event) => setMinEdge(event.target.value)}
|
||||
className="h-9 rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
inputMode="decimal"
|
||||
aria-label="最小 Edge"
|
||||
/>
|
||||
<label className="block">
|
||||
<span className="sr-only">方向</span>
|
||||
<select
|
||||
value={side}
|
||||
onChange={(event) => setSide(event.target.value)}
|
||||
className="h-9 w-full rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
>
|
||||
<option value="both">Yes / No</option>
|
||||
<option value="yes">仅 Yes</option>
|
||||
<option value="no">仅 No</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-1 block text-[11px] font-black uppercase tracking-wide text-slate-500">
|
||||
最高买入价
|
||||
</span>
|
||||
<input
|
||||
value={maxPrice}
|
||||
onChange={(event) => setMaxPrice(event.target.value)}
|
||||
className="h-9 w-full rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
inputMode="decimal"
|
||||
aria-label="最高买入价"
|
||||
/>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-1 block text-[11px] font-black uppercase tracking-wide text-slate-500">
|
||||
最小 Edge
|
||||
</span>
|
||||
<input
|
||||
value={minEdge}
|
||||
onChange={(event) => setMinEdge(event.target.value)}
|
||||
className="h-9 w-full rounded-md border border-slate-300 bg-white px-2 text-sm font-semibold text-slate-700"
|
||||
inputMode="decimal"
|
||||
aria-label="最小 Edge"
|
||||
/>
|
||||
</label>
|
||||
<Button variant="outline" size="sm" onClick={load}>
|
||||
应用筛选
|
||||
</Button>
|
||||
|
||||
@@ -180,3 +180,28 @@ def test_ops_market_opportunities_degrades_when_quotes_fail(monkeypatch):
|
||||
assert payload["summary"]["quote_status"] == "unavailable"
|
||||
assert payload["summary"]["error"] == "polymarket down"
|
||||
assert payload["rows"] == []
|
||||
|
||||
|
||||
def test_ops_market_opportunities_reuses_prewarmed_terminal_snapshot(monkeypatch):
|
||||
captured = {}
|
||||
monkeypatch.setattr(market_opportunities, "_require_ops", lambda _request: {"email": "ops@example.com"})
|
||||
|
||||
def fake_scan(filters, *, force_refresh=False):
|
||||
captured["filters"] = filters
|
||||
captured["force_refresh"] = force_refresh
|
||||
return {"rows": [_row()]}
|
||||
|
||||
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 payload["summary"]["scanned_city_count"] == 1
|
||||
assert captured["force_refresh"] is False
|
||||
assert captured["filters"]["limit"] == 180
|
||||
assert captured["filters"]["min_edge_pct"] == 2
|
||||
assert captured["filters"]["min_liquidity"] == 500
|
||||
|
||||
@@ -460,8 +460,8 @@ def get_ops_market_opportunities(
|
||||
"scan_mode": "tradable",
|
||||
"min_price": 0.05,
|
||||
"max_price": 0.95,
|
||||
"min_edge_pct": 0,
|
||||
"min_liquidity": 0,
|
||||
"min_edge_pct": 2,
|
||||
"min_liquidity": 500,
|
||||
"market_type": "maxtemp",
|
||||
"time_range": "today",
|
||||
"limit": 180,
|
||||
|
||||
Reference in New Issue
Block a user