Enhance probability-price linkage in dashboard
This commit is contained in:
@@ -305,6 +305,25 @@ function getMarketTopBuckets(scan?: MarketScan | null) {
|
|||||||
const buckets = Array.isArray(scan?.top_buckets) ? scan.top_buckets : [];
|
const buckets = Array.isArray(scan?.top_buckets) ? scan.top_buckets : [];
|
||||||
if (!buckets.length) return [];
|
if (!buckets.length) return [];
|
||||||
|
|
||||||
|
return buckets
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
probability: normalizeMarketProbability(item.probability),
|
||||||
|
}))
|
||||||
|
.filter(
|
||||||
|
(item): item is MarketTopBucket & { probability: number } =>
|
||||||
|
item.probability != null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarketAllBuckets(scan?: MarketScan | null) {
|
||||||
|
const buckets = Array.isArray(scan?.all_buckets)
|
||||||
|
? scan.all_buckets
|
||||||
|
: Array.isArray(scan?.top_buckets)
|
||||||
|
? scan.top_buckets
|
||||||
|
: [];
|
||||||
|
if (!buckets.length) return [];
|
||||||
|
|
||||||
return buckets
|
return buckets
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -655,6 +674,7 @@ export function ProbabilityDistribution({
|
|||||||
)
|
)
|
||||||
.join(" · ");
|
.join(" · ");
|
||||||
const marketTopBuckets = isToday ? getMarketTopBuckets(marketScan) : [];
|
const marketTopBuckets = isToday ? getMarketTopBuckets(marketScan) : [];
|
||||||
|
const marketAllBuckets = isToday ? getMarketAllBuckets(marketScan) : [];
|
||||||
const sortedMarketTopBuckets = useMemo(() => {
|
const sortedMarketTopBuckets = useMemo(() => {
|
||||||
const sorted = [...marketTopBuckets].sort(
|
const sorted = [...marketTopBuckets].sort(
|
||||||
(a, b) => Number(b.probability || 0) - Number(a.probability || 0),
|
(a, b) => Number(b.probability || 0) - Number(a.probability || 0),
|
||||||
@@ -680,13 +700,54 @@ export function ProbabilityDistribution({
|
|||||||
const topProbabilityLabel = topProbability
|
const topProbabilityLabel = topProbability
|
||||||
? topProbability.label || `${topProbability.value}${detail.temp_symbol}`
|
? topProbability.label || `${topProbability.value}${detail.temp_symbol}`
|
||||||
: null;
|
: null;
|
||||||
|
const topProbabilityTemp = topProbability ? getBucketTemp(topProbability) : null;
|
||||||
|
const linkedMarketBucket = useMemo(() => {
|
||||||
|
if (topProbabilityTemp == null) return null;
|
||||||
|
return (
|
||||||
|
marketAllBuckets.find((bucket) => {
|
||||||
|
const bucketTemp = bucket.temp ?? bucket.value ?? null;
|
||||||
|
if (bucketTemp == null) return false;
|
||||||
|
const numeric = Number(bucketTemp);
|
||||||
|
return Number.isFinite(numeric) && Math.abs(numeric - topProbabilityTemp) < 0.26;
|
||||||
|
}) || null
|
||||||
|
);
|
||||||
|
}, [marketAllBuckets, topProbabilityTemp]);
|
||||||
const priceAnalysis = marketScan?.price_analysis;
|
const priceAnalysis = marketScan?.price_analysis;
|
||||||
const yesPriceView = priceAnalysis?.yes;
|
const yesPriceView = priceAnalysis?.yes;
|
||||||
const noPriceView = priceAnalysis?.no;
|
const noPriceView = priceAnalysis?.no;
|
||||||
|
const linkedMarketAsk =
|
||||||
|
linkedMarketBucket?.yes_buy ??
|
||||||
|
linkedMarketBucket?.market_price ??
|
||||||
|
yesPriceView?.ask ??
|
||||||
|
null;
|
||||||
|
const linkedMarketProbability =
|
||||||
|
topProbability?.probability != null ? Number(topProbability.probability) : null;
|
||||||
|
const linkedMarketEdge =
|
||||||
|
linkedMarketProbability != null && linkedMarketAsk != null
|
||||||
|
? linkedMarketProbability - Number(linkedMarketAsk)
|
||||||
|
: null;
|
||||||
|
const linkedMarketKelly =
|
||||||
|
linkedMarketProbability != null &&
|
||||||
|
linkedMarketAsk != null &&
|
||||||
|
Number(linkedMarketAsk) > 0 &&
|
||||||
|
Number(linkedMarketAsk) < 1
|
||||||
|
? linkedMarketEdge! / (1 - Number(linkedMarketAsk))
|
||||||
|
: null;
|
||||||
const preferredPriceView =
|
const preferredPriceView =
|
||||||
priceAnalysis?.best_side === "no" ? noPriceView : yesPriceView;
|
linkedMarketBucket
|
||||||
|
? {
|
||||||
|
ask: linkedMarketAsk,
|
||||||
|
edge: linkedMarketEdge,
|
||||||
|
quarter_kelly:
|
||||||
|
linkedMarketKelly != null ? Math.max(0, linkedMarketKelly) / 4 : null,
|
||||||
|
}
|
||||||
|
: priceAnalysis?.best_side === "no"
|
||||||
|
? noPriceView
|
||||||
|
: yesPriceView;
|
||||||
const preferredSideLabel =
|
const preferredSideLabel =
|
||||||
priceAnalysis?.best_side === "no"
|
linkedMarketBucket
|
||||||
|
? "YES"
|
||||||
|
: priceAnalysis?.best_side === "no"
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
? "NO"
|
? "NO"
|
||||||
: "NO"
|
: "NO"
|
||||||
@@ -694,16 +755,21 @@ export function ProbabilityDistribution({
|
|||||||
? "YES"
|
? "YES"
|
||||||
: "YES";
|
: "YES";
|
||||||
const hasPriceAnalysis =
|
const hasPriceAnalysis =
|
||||||
Boolean(priceAnalysis?.available) &&
|
isToday &&
|
||||||
(yesPriceView?.ask != null || noPriceView?.ask != null);
|
(Boolean(priceAnalysis?.available) ||
|
||||||
|
Boolean(marketScan) ||
|
||||||
|
Boolean(topProbability));
|
||||||
const lockEdge = normalizeSignedProbability(priceAnalysis?.lock?.edge);
|
const lockEdge = normalizeSignedProbability(priceAnalysis?.lock?.edge);
|
||||||
const lockAvailable = Boolean(priceAnalysis?.lock?.available && lockEdge != null);
|
const lockAvailable = Boolean(priceAnalysis?.lock?.available && lockEdge != null);
|
||||||
const quoteSource =
|
const quoteSource =
|
||||||
|
linkedMarketBucket?.quote_source ||
|
||||||
marketScan?.yes_token?.quote_source ||
|
marketScan?.yes_token?.quote_source ||
|
||||||
marketScan?.no_token?.quote_source ||
|
marketScan?.no_token?.quote_source ||
|
||||||
null;
|
null;
|
||||||
const quoteAgeMs =
|
const quoteAgeMs =
|
||||||
marketScan?.yes_token?.quote_age_ms ?? marketScan?.no_token?.quote_age_ms;
|
linkedMarketBucket?.quote_age_ms ??
|
||||||
|
marketScan?.yes_token?.quote_age_ms ??
|
||||||
|
marketScan?.no_token?.quote_age_ms;
|
||||||
const quoteSourceLabel =
|
const quoteSourceLabel =
|
||||||
quoteSource === "polymarket_ws"
|
quoteSource === "polymarket_ws"
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
@@ -761,10 +827,22 @@ export function ProbabilityDistribution({
|
|||||||
<div className="prob-price-card">
|
<div className="prob-price-card">
|
||||||
<div className="prob-price-head">
|
<div className="prob-price-head">
|
||||||
<span>
|
<span>
|
||||||
{locale === "en-US" ? "Price reference" : "价格参考"}
|
{locale === "en-US" ? "Probability x Price" : "概率 x 价格联动"}
|
||||||
</span>
|
</span>
|
||||||
<strong>
|
<strong>
|
||||||
{preferredPriceView?.edge != null
|
{!marketScan
|
||||||
|
? locale === "en-US"
|
||||||
|
? "Waiting for market layer"
|
||||||
|
: "等待市场层"
|
||||||
|
: !marketScan.available
|
||||||
|
? locale === "en-US"
|
||||||
|
? "No matched active market"
|
||||||
|
: "未匹配到活跃盘口"
|
||||||
|
: linkedMarketBucket
|
||||||
|
? locale === "en-US"
|
||||||
|
? `${topProbabilityLabel || "Top bucket"} vs ${toPriceCents(linkedMarketAsk) || "--"}`
|
||||||
|
: `${topProbabilityLabel || "最高概率桶"} 对比 ${toPriceCents(linkedMarketAsk) || "--"}`
|
||||||
|
: preferredPriceView?.edge != null
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
? `${preferredSideLabel} edge ${formatSignedPercent(preferredPriceView.edge)}`
|
? `${preferredSideLabel} edge ${formatSignedPercent(preferredPriceView.edge)}`
|
||||||
: `${preferredSideLabel} 边际 ${formatSignedPercent(preferredPriceView.edge)}`
|
: `${preferredSideLabel} 边际 ${formatSignedPercent(preferredPriceView.edge)}`
|
||||||
@@ -775,35 +853,43 @@ export function ProbabilityDistribution({
|
|||||||
</div>
|
</div>
|
||||||
<div className="prob-price-grid">
|
<div className="prob-price-grid">
|
||||||
<div>
|
<div>
|
||||||
<span>YES ask</span>
|
<span>{locale === "en-US" ? "Model bucket" : "概率主桶"}</span>
|
||||||
<strong>{toPriceCents(yesPriceView?.ask) || "--"}</strong>
|
<strong>{topProbabilityLabel || "--"}</strong>
|
||||||
<em>{formatSignedPercent(yesPriceView?.edge)}</em>
|
<em>{topProbabilityText || "--"}</em>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>NO ask</span>
|
<span>{locale === "en-US" ? "Market ask" : "市场 ask"}</span>
|
||||||
<strong>{toPriceCents(noPriceView?.ask) || "--"}</strong>
|
<strong>{toPriceCents(linkedMarketAsk ?? yesPriceView?.ask) || "--"}</strong>
|
||||||
<em>{formatSignedPercent(noPriceView?.edge)}</em>
|
<em>
|
||||||
|
{linkedMarketBucket
|
||||||
|
? locale === "en-US"
|
||||||
|
? "matched bucket"
|
||||||
|
: "已匹配桶"
|
||||||
|
: marketScan?.available
|
||||||
|
? locale === "en-US"
|
||||||
|
? "primary market"
|
||||||
|
: "主盘口"
|
||||||
|
: marketScan?.reason || "--"}
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{locale === "en-US" ? "Edge" : "边际"}</span>
|
||||||
|
<strong>{formatSignedPercent(preferredPriceView?.edge)}</strong>
|
||||||
|
<em>
|
||||||
|
{quoteSourceLabel}
|
||||||
|
</em>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>{locale === "en-US" ? "1/4 Kelly" : "1/4 Kelly"}</span>
|
<span>{locale === "en-US" ? "1/4 Kelly" : "1/4 Kelly"}</span>
|
||||||
<strong>{formatKellyFraction(preferredPriceView?.quarter_kelly)}</strong>
|
<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>
|
<em>
|
||||||
{priceAnalysis?.lock?.ask_sum != null
|
{lockAvailable
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
? `sum ${toPriceCents(priceAnalysis.lock.ask_sum)}`
|
? `lock ${formatSignedPercent(lockEdge)}`
|
||||||
: `合计 ${toPriceCents(priceAnalysis.lock.ask_sum)}`
|
: `锁价 ${formatSignedPercent(lockEdge)}`
|
||||||
: "--"}
|
: locale === "en-US"
|
||||||
|
? "cap required"
|
||||||
|
: "需限额"}
|
||||||
</em>
|
</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ export interface MarketScan {
|
|||||||
price_analysis?: MarketPriceAnalysis | null;
|
price_analysis?: MarketPriceAnalysis | null;
|
||||||
sparkline?: number[];
|
sparkline?: number[];
|
||||||
top_buckets?: MarketTopBucket[] | null;
|
top_buckets?: MarketTopBucket[] | null;
|
||||||
|
all_buckets?: MarketTopBucket[] | null;
|
||||||
recent_trades?: unknown[];
|
recent_trades?: unknown[];
|
||||||
websocket?: Record<string, unknown>;
|
websocket?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user