启用概率校准和 WebSocket 报价配置
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user