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