启用概率校准和 WebSocket 报价配置

This commit is contained in:
2569718930@qq.com
2026-04-21 21:06:48 +08:00
parent 582ded8cfb
commit 82ec594277
22 changed files with 3703 additions and 90 deletions
@@ -1075,6 +1075,72 @@
border: 1px solid rgba(251, 113, 133, 0.26);
}
.root :global(.prob-price-card) {
display: grid;
gap: 8px;
padding: 10px;
border: 1px solid rgba(34, 211, 238, 0.16);
border-radius: 8px;
background: rgba(8, 20, 32, 0.52);
}
.root :global(.prob-price-head) {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.root :global(.prob-price-head span) {
color: var(--cyan);
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.root :global(.prob-price-head strong) {
color: var(--text-primary);
font-size: 13px;
font-weight: 800;
}
.root :global(.prob-price-grid) {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
}
.root :global(.prob-price-grid > div) {
display: grid;
gap: 3px;
min-width: 0;
padding: 8px;
border: 1px solid rgba(148, 163, 184, 0.12);
border-radius: 8px;
background: rgba(15, 23, 42, 0.45);
}
.root :global(.prob-price-grid span),
.root :global(.prob-price-grid em),
.root :global(.prob-price-card p) {
color: var(--text-muted);
font-size: 11px;
font-style: normal;
line-height: 1.35;
}
.root :global(.prob-price-grid strong) {
color: var(--text-primary);
font-size: 13px;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
.root :global(.prob-price-card p) {
margin: 0;
}
.root :global(.prob-model-hint) {
display: flex;
align-items: center;
@@ -2466,6 +2532,15 @@
.root :global(.marker-name) {
font-size: 11px;
}
.root :global(.prob-price-grid) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.root :global(.prob-price-head) {
align-items: flex-start;
flex-direction: column;
}
}
/* ── History Modal ── */
@@ -278,6 +278,29 @@ function normalizeMarketProbability(value?: number | null) {
return Math.max(0, Math.min(1, numeric));
}
function normalizeSignedProbability(value?: number | null) {
if (value == null) return null;
const numeric = Number(value);
if (!Number.isFinite(numeric)) return null;
if (Math.abs(numeric) > 1) return numeric / 100;
return numeric;
}
function formatSignedPercent(value?: number | null, digits = 1) {
const normalized = normalizeSignedProbability(value);
if (normalized == null) return "--";
const percent = normalized * 100;
const sign = percent > 0 ? "+" : "";
return `${sign}${percent.toFixed(digits)}%`;
}
function formatKellyFraction(value?: number | null) {
const normalized = normalizeSignedProbability(value);
if (normalized == null) return "--";
if (normalized <= 0) return "0%";
return `${(normalized * 100).toFixed(1)}%`;
}
function getMarketTopBuckets(scan?: MarketScan | null) {
const buckets = Array.isArray(scan?.top_buckets) ? scan.top_buckets : [];
if (!buckets.length) return [];
@@ -657,6 +680,38 @@ export function ProbabilityDistribution({
const topProbabilityLabel = topProbability
? topProbability.label || `${topProbability.value}${detail.temp_symbol}`
: null;
const priceAnalysis = marketScan?.price_analysis;
const yesPriceView = priceAnalysis?.yes;
const noPriceView = priceAnalysis?.no;
const preferredPriceView =
priceAnalysis?.best_side === "no" ? noPriceView : yesPriceView;
const preferredSideLabel =
priceAnalysis?.best_side === "no"
? locale === "en-US"
? "NO"
: "NO"
: locale === "en-US"
? "YES"
: "YES";
const hasPriceAnalysis =
Boolean(priceAnalysis?.available) &&
(yesPriceView?.ask != null || noPriceView?.ask != null);
const lockEdge = normalizeSignedProbability(priceAnalysis?.lock?.edge);
const lockAvailable = Boolean(priceAnalysis?.lock?.available && lockEdge != null);
const quoteSource =
marketScan?.yes_token?.quote_source ||
marketScan?.no_token?.quote_source ||
null;
const quoteAgeMs =
marketScan?.yes_token?.quote_age_ms ?? marketScan?.no_token?.quote_age_ms;
const quoteSourceLabel =
quoteSource === "polymarket_ws"
? locale === "en-US"
? `WS live${quoteAgeMs != null ? ` · ${Math.max(0, Math.round(Number(quoteAgeMs) / 1000))}s` : ""}`
: `WS 实时${quoteAgeMs != null ? ` · ${Math.max(0, Math.round(Number(quoteAgeMs) / 1000))}` : ""}`
: locale === "en-US"
? "CLOB fallback"
: "CLOB 兜底";
return (
<section className="prob-section">
@@ -702,6 +757,63 @@ export function ProbabilityDistribution({
: `市场仅作参考:该温度桶 ${marketYesText}`}
</div>
)}
{hasPriceAnalysis && (
<div className="prob-price-card">
<div className="prob-price-head">
<span>
{locale === "en-US" ? "Price reference" : "价格参考"}
</span>
<strong>
{preferredPriceView?.edge != null
? locale === "en-US"
? `${preferredSideLabel} edge ${formatSignedPercent(preferredPriceView.edge)}`
: `${preferredSideLabel} 边际 ${formatSignedPercent(preferredPriceView.edge)}`
: locale === "en-US"
? "Waiting for executable quote"
: "等待可执行报价"}
</strong>
</div>
<div className="prob-price-grid">
<div>
<span>YES ask</span>
<strong>{toPriceCents(yesPriceView?.ask) || "--"}</strong>
<em>{formatSignedPercent(yesPriceView?.edge)}</em>
</div>
<div>
<span>NO ask</span>
<strong>{toPriceCents(noPriceView?.ask) || "--"}</strong>
<em>{formatSignedPercent(noPriceView?.edge)}</em>
</div>
<div>
<span>{locale === "en-US" ? "1/4 Kelly" : "1/4 Kelly"}</span>
<strong>{formatKellyFraction(preferredPriceView?.quarter_kelly)}</strong>
<em>{locale === "en-US" ? "cap required" : "需限额"}</em>
</div>
<div>
<span>{locale === "en-US" ? "Lock" : "锁价"}</span>
<strong>
{lockAvailable
? formatSignedPercent(lockEdge)
: locale === "en-US"
? "None"
: "无"}
</strong>
<em>
{priceAnalysis?.lock?.ask_sum != null
? locale === "en-US"
? `sum ${toPriceCents(priceAnalysis.lock.ask_sum)}`
: `合计 ${toPriceCents(priceAnalysis.lock.ask_sum)}`
: "--"}
</em>
</div>
</div>
<p>
{locale === "en-US"
? `Read-only sizing reference from executable quotes; it does not place orders. Source: ${quoteSourceLabel}.`
: `基于可执行盘口的只读仓位参考;系统不会下单。来源:${quoteSourceLabel}`}
</p>
</div>
)}
{modelVoteHint && (
<div className="prob-model-hint">
<span>
+34
View File
@@ -289,6 +289,8 @@ export interface MarketToken {
sell_price?: number | null;
midpoint?: number | null;
last_trade_price?: number | null;
quote_source?: string | null;
quote_age_ms?: number | null;
}
export interface MarketPrimary {
@@ -314,11 +316,42 @@ export interface MarketTopBucket {
yes_sell?: number | null;
no_buy?: number | null;
no_sell?: number | null;
quote_source?: string | null;
quote_age_ms?: number | null;
slug?: string | null;
question?: string | null;
is_primary?: boolean;
}
export interface MarketPriceSide {
side?: "yes" | "no" | string | null;
model_probability?: number | null;
ask?: number | null;
bid?: number | null;
edge?: number | null;
edge_percent?: number | null;
kelly_fraction?: number | null;
quarter_kelly?: number | null;
}
export interface MarketPriceAnalysis {
available?: boolean;
source?: string | null;
model_probability?: number | null;
yes?: MarketPriceSide | null;
no?: MarketPriceSide | null;
best_side?: "yes" | "no" | string | null;
lock?: {
available?: boolean;
ask_sum?: number | null;
edge?: number | null;
} | null;
sell_side?: {
bid_sum?: number | null;
edge?: number | null;
} | null;
}
export interface MarketScan {
available?: boolean;
reason?: string | null;
@@ -343,6 +376,7 @@ export interface MarketScan {
last_trade_price?: number | null;
liquidity?: number | null;
volume?: number | null;
price_analysis?: MarketPriceAnalysis | null;
sparkline?: number[];
top_buckets?: MarketTopBucket[] | null;
recent_trades?: unknown[];