diff --git a/frontend/components/dashboard/PanelSections.tsx b/frontend/components/dashboard/PanelSections.tsx index eacb9d45..4b3aabbe 100644 --- a/frontend/components/dashboard/PanelSections.tsx +++ b/frontend/components/dashboard/PanelSections.tsx @@ -305,6 +305,25 @@ function getMarketTopBuckets(scan?: MarketScan | null) { const buckets = Array.isArray(scan?.top_buckets) ? scan.top_buckets : []; 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 .map((item) => ({ ...item, @@ -655,6 +674,7 @@ export function ProbabilityDistribution({ ) .join(" · "); const marketTopBuckets = isToday ? getMarketTopBuckets(marketScan) : []; + const marketAllBuckets = isToday ? getMarketAllBuckets(marketScan) : []; const sortedMarketTopBuckets = useMemo(() => { const sorted = [...marketTopBuckets].sort( (a, b) => Number(b.probability || 0) - Number(a.probability || 0), @@ -680,13 +700,54 @@ export function ProbabilityDistribution({ const topProbabilityLabel = topProbability ? topProbability.label || `${topProbability.value}${detail.temp_symbol}` : 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 yesPriceView = priceAnalysis?.yes; 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 = - 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 = - priceAnalysis?.best_side === "no" + linkedMarketBucket + ? "YES" + : priceAnalysis?.best_side === "no" ? locale === "en-US" ? "NO" : "NO" @@ -694,16 +755,21 @@ export function ProbabilityDistribution({ ? "YES" : "YES"; const hasPriceAnalysis = - Boolean(priceAnalysis?.available) && - (yesPriceView?.ask != null || noPriceView?.ask != null); + isToday && + (Boolean(priceAnalysis?.available) || + Boolean(marketScan) || + Boolean(topProbability)); const lockEdge = normalizeSignedProbability(priceAnalysis?.lock?.edge); const lockAvailable = Boolean(priceAnalysis?.lock?.available && lockEdge != null); const quoteSource = + linkedMarketBucket?.quote_source || 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; + linkedMarketBucket?.quote_age_ms ?? + marketScan?.yes_token?.quote_age_ms ?? + marketScan?.no_token?.quote_age_ms; const quoteSourceLabel = quoteSource === "polymarket_ws" ? locale === "en-US" @@ -761,10 +827,22 @@ export function ProbabilityDistribution({