Clarify ops market opportunity side probability
This commit is contained in:
@@ -47,7 +47,7 @@ export function runTests() {
|
|||||||
"选项",
|
"选项",
|
||||||
"方向",
|
"方向",
|
||||||
"买入价",
|
"买入价",
|
||||||
"模型概率",
|
"方向概率",
|
||||||
"Edge",
|
"Edge",
|
||||||
"市场链接",
|
"市场链接",
|
||||||
]) {
|
]) {
|
||||||
@@ -63,4 +63,10 @@ export function runTests() {
|
|||||||
client.includes('showAllLowPrice ? "-1" : minEdge'),
|
client.includes('showAllLowPrice ? "-1" : minEdge'),
|
||||||
"market opportunities filters must label price versus edge and not edge-filter the show-all-low-price view",
|
"market opportunities filters must label price versus edge and not edge-filter the show-all-low-price view",
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
client.includes("side_probability") &&
|
||||||
|
client.includes("yes_probability") &&
|
||||||
|
client.includes("YES"),
|
||||||
|
"NO opportunities must show side probability while keeping the underlying YES probability visible",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type MarketOpportunityRow = {
|
|||||||
side?: string;
|
side?: string;
|
||||||
ask_price?: number;
|
ask_price?: number;
|
||||||
model_probability?: number;
|
model_probability?: number;
|
||||||
|
yes_probability?: number;
|
||||||
|
side_probability?: number;
|
||||||
edge?: number;
|
edge?: number;
|
||||||
liquidity?: number | null;
|
liquidity?: number | null;
|
||||||
volume?: number | null;
|
volume?: number | null;
|
||||||
@@ -74,6 +76,19 @@ function sideTone(side?: string) {
|
|||||||
: "border-emerald-200 bg-emerald-50 text-emerald-700";
|
: "border-emerald-200 bg-emerald-50 text-emerald-700";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function yesProbability(row: MarketOpportunityRow) {
|
||||||
|
return typeof row.yes_probability === "number" ? row.yes_probability : row.model_probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sideProbability(row: MarketOpportunityRow) {
|
||||||
|
if (typeof row.side_probability === "number") return row.side_probability;
|
||||||
|
const yes = yesProbability(row);
|
||||||
|
if (String(row.side).toLowerCase() === "no" && typeof yes === "number") {
|
||||||
|
return 1 - yes;
|
||||||
|
}
|
||||||
|
return yes;
|
||||||
|
}
|
||||||
|
|
||||||
function Stat({
|
function Stat({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
@@ -152,9 +167,9 @@ export function MarketOpportunitiesPageClient() {
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-5">
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-5">
|
||||||
<Stat label="机会数" value={summary.opportunity_count ?? rows.length} sub="当前筛选结果" />
|
<Stat label="机会数" value={summary.opportunity_count ?? rows.length} sub="当前筛选结果" />
|
||||||
<Stat label="正边际" value={summary.positive_edge_count ?? 0} sub="模型概率高于买入价" />
|
<Stat label="正边际" value={summary.positive_edge_count ?? 0} sub="方向概率高于买入价" />
|
||||||
<Stat label="最低价格" value={cents(summary.min_price)} sub="低价合约下限" />
|
<Stat label="最低价格" value={cents(summary.min_price)} sub="低价合约下限" />
|
||||||
<Stat label="最大 Edge" value={percent(summary.max_edge)} sub="模型概率 - 市场价" />
|
<Stat label="最大 Edge" value={percent(summary.max_edge)} sub="方向概率 - 市场价" />
|
||||||
<Stat label="报价状态" value={quoteStatus} sub={`${summary.matched_event_count ?? 0}/${summary.scanned_city_count ?? 0} 城市匹配`} />
|
<Stat label="报价状态" value={quoteStatus} sub={`${summary.matched_event_count ?? 0}/${summary.scanned_city_count ?? 0} 城市匹配`} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -243,7 +258,7 @@ export function MarketOpportunitiesPageClient() {
|
|||||||
<th className="px-3 py-2">选项</th>
|
<th className="px-3 py-2">选项</th>
|
||||||
<th className="px-3 py-2">方向</th>
|
<th className="px-3 py-2">方向</th>
|
||||||
<th className="px-3 py-2 text-right">买入价</th>
|
<th className="px-3 py-2 text-right">买入价</th>
|
||||||
<th className="px-3 py-2 text-right">模型概率</th>
|
<th className="px-3 py-2 text-right">方向概率</th>
|
||||||
<th className="px-3 py-2 text-right">Edge</th>
|
<th className="px-3 py-2 text-right">Edge</th>
|
||||||
<th className="px-3 py-2 text-right">当前最高</th>
|
<th className="px-3 py-2 text-right">当前最高</th>
|
||||||
<th className="px-3 py-2 text-right">DEB</th>
|
<th className="px-3 py-2 text-right">DEB</th>
|
||||||
@@ -277,7 +292,14 @@ export function MarketOpportunitiesPageClient() {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right font-black text-slate-950">{cents(row.ask_price)}</td>
|
<td className="px-3 py-2 text-right font-black text-slate-950">{cents(row.ask_price)}</td>
|
||||||
<td className="px-3 py-2 text-right font-bold text-blue-700">{percent(row.model_probability)}</td>
|
<td className="px-3 py-2 text-right">
|
||||||
|
<div className="font-bold text-blue-700">{percent(sideProbability(row))}</div>
|
||||||
|
{String(row.side).toLowerCase() === "no" ? (
|
||||||
|
<div className="mt-0.5 text-[11px] font-semibold text-slate-400">
|
||||||
|
YES {percent(yesProbability(row))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
<td className="px-3 py-2 text-right font-black text-emerald-700">{percent(row.edge)}</td>
|
<td className="px-3 py-2 text-right font-black text-emerald-700">{percent(row.edge)}</td>
|
||||||
<td className="px-3 py-2 text-right font-semibold text-slate-700">{tempLabel(row.current_max_so_far)}</td>
|
<td className="px-3 py-2 text-right font-semibold text-slate-700">{tempLabel(row.current_max_so_far)}</td>
|
||||||
<td className="px-3 py-2 text-right font-semibold text-orange-700">{tempLabel(row.deb_prediction)}</td>
|
<td className="px-3 py-2 text-right font-semibold text-orange-700">{tempLabel(row.deb_prediction)}</td>
|
||||||
|
|||||||
@@ -101,9 +101,13 @@ def test_build_market_opportunities_scans_yes_and_no_low_price_edges():
|
|||||||
no = next(row for row in rows if row["bucket_label"] == "31°C" and row["side"] == "no")
|
no = next(row for row in rows if row["bucket_label"] == "31°C" and row["side"] == "no")
|
||||||
|
|
||||||
assert yes["model_probability"] == 0.46
|
assert yes["model_probability"] == 0.46
|
||||||
|
assert yes["yes_probability"] == 0.46
|
||||||
|
assert yes["side_probability"] == 0.46
|
||||||
assert yes["ask_price"] == 0.18
|
assert yes["ask_price"] == 0.18
|
||||||
assert round(yes["edge"], 2) == 0.28
|
assert round(yes["edge"], 2) == 0.28
|
||||||
assert no["model_probability"] == 0.10
|
assert no["model_probability"] == 0.10
|
||||||
|
assert no["yes_probability"] == 0.10
|
||||||
|
assert no["side_probability"] == 0.90
|
||||||
assert no["ask_price"] == 0.18
|
assert no["ask_price"] == 0.18
|
||||||
assert round(no["edge"], 2) == 0.72
|
assert round(no["edge"], 2) == 0.72
|
||||||
assert all(row["ask_price"] < 0.20 for row in rows)
|
assert all(row["ask_price"] < 0.20 for row in rows)
|
||||||
|
|||||||
@@ -336,6 +336,8 @@ def build_market_opportunity_rows(
|
|||||||
"side": option_side,
|
"side": option_side,
|
||||||
"ask_price": _round_price(ask_number),
|
"ask_price": _round_price(ask_number),
|
||||||
"model_probability": model_probability,
|
"model_probability": model_probability,
|
||||||
|
"yes_probability": model_probability,
|
||||||
|
"side_probability": target_probability,
|
||||||
"edge": edge,
|
"edge": edge,
|
||||||
"liquidity": _finite_number(market.get("liquidity")),
|
"liquidity": _finite_number(market.get("liquidity")),
|
||||||
"volume": _finite_number(market.get("volume")),
|
"volume": _finite_number(market.get("volume")),
|
||||||
|
|||||||
Reference in New Issue
Block a user