feat: add PanelSections component with helper utilities for dashboard data visualization and model processing

This commit is contained in:
2569718930@qq.com
2026-04-21 23:18:42 +08:00
parent 42dd0a7c6d
commit 193af17726
+66 -67
View File
@@ -260,16 +260,6 @@ function getRoundedModelVoteDistribution(
}; };
} }
function getMarketNoPrice(scan?: MarketScan | null) {
if (scan?.no_buy != null) {
const direct = Number(scan.no_buy);
if (Number.isFinite(direct)) return direct;
}
const marketYes = getMarketYesPrice(scan);
if (marketYes != null) return Math.max(0, Math.min(1, 1 - marketYes));
return null;
}
function normalizeMarketProbability(value?: number | null) { function normalizeMarketProbability(value?: number | null) {
if (value == null) return null; if (value == null) return null;
const numeric = Number(value); const numeric = Number(value);
@@ -294,13 +284,6 @@ function formatSignedPercent(value?: number | null, digits = 1) {
return `${sign}${percent.toFixed(digits)}%`; 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) { 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 [];
@@ -652,9 +635,7 @@ export function ProbabilityDistribution({
const view = getProbabilityView(detail, targetDate); const view = getProbabilityView(detail, targetDate);
const marketBucketTemp = getMarketBucketTemp(marketScan); const marketBucketTemp = getMarketBucketTemp(marketScan);
const marketYesPrice = getMarketYesPrice(marketScan); const marketYesPrice = getMarketYesPrice(marketScan);
const marketNoPrice = getMarketNoPrice(marketScan);
const marketYesText = toPercent(marketYesPrice); const marketYesText = toPercent(marketYesPrice);
const marketNoText = toPercent(marketNoPrice);
const isToday = !targetDate || targetDate === detail.local_date; const isToday = !targetDate || targetDate === detail.local_date;
const probabilityEngineLabel = formatProbabilityEngineLabel( const probabilityEngineLabel = formatProbabilityEngineLabel(
detail, detail,
@@ -720,33 +701,40 @@ export function ProbabilityDistribution({
linkedMarketBucket?.market_price ?? linkedMarketBucket?.market_price ??
yesPriceView?.ask ?? yesPriceView?.ask ??
null; null;
const linkedNoAsk = linkedMarketBucket?.no_buy ?? noPriceView?.ask ?? null;
const linkedMarketProbability = const linkedMarketProbability =
topProbability?.probability != null ? Number(topProbability.probability) : null; topProbability?.probability != null ? Number(topProbability.probability) : null;
const linkedMarketEdge = const linkedMarketEdge =
linkedMarketProbability != null && linkedMarketAsk != null linkedMarketProbability != null && linkedMarketAsk != null
? linkedMarketProbability - Number(linkedMarketAsk) ? linkedMarketProbability - Number(linkedMarketAsk)
: null; : null;
const linkedMarketKelly = const linkedNoEdge =
linkedMarketProbability != null && linkedMarketProbability != null && linkedNoAsk != null
linkedMarketAsk != null && ? 1 - linkedMarketProbability - Number(linkedNoAsk)
Number(linkedMarketAsk) > 0 &&
Number(linkedMarketAsk) < 1
? linkedMarketEdge! / (1 - Number(linkedMarketAsk))
: null; : null;
const linkedBestSide =
linkedMarketBucket && linkedNoEdge != null && linkedMarketEdge != null
? linkedNoEdge > linkedMarketEdge
? "no"
: "yes"
: null;
const linkedBestAsk = linkedBestSide === "no" ? linkedNoAsk : linkedMarketAsk;
const linkedBestEdge =
linkedBestSide === "no" ? linkedNoEdge : linkedMarketEdge;
const preferredPriceView = const preferredPriceView =
linkedMarketBucket linkedMarketBucket
? { ? {
ask: linkedMarketAsk, ask: linkedBestAsk,
edge: linkedMarketEdge, edge: linkedBestEdge,
quarter_kelly:
linkedMarketKelly != null ? Math.max(0, linkedMarketKelly) / 4 : null,
} }
: priceAnalysis?.best_side === "no" : priceAnalysis?.best_side === "no"
? noPriceView ? noPriceView
: yesPriceView; : yesPriceView;
const preferredSideLabel = const preferredSideLabel =
linkedMarketBucket linkedMarketBucket
? "YES" ? linkedBestSide === "no"
? "NO"
: "YES"
: priceAnalysis?.best_side === "no" : priceAnalysis?.best_side === "no"
? locale === "en-US" ? locale === "en-US"
? "NO" ? "NO"
@@ -778,6 +766,39 @@ export function ProbabilityDistribution({
: locale === "en-US" : locale === "en-US"
? "CLOB fallback" ? "CLOB fallback"
: "CLOB 兜底"; : "CLOB 兜底";
const actionableEdge = normalizeSignedProbability(preferredPriceView?.edge);
const actionText =
!marketScan
? locale === "en-US"
? "Waiting"
: "等待"
: !marketScan.available
? locale === "en-US"
? "No market"
: "无盘口"
: actionableEdge == null
? locale === "en-US"
? "No quote"
: "无报价"
: actionableEdge >= 0.02
? locale === "en-US"
? `Watch ${preferredSideLabel}`
: `可关注 ${preferredSideLabel}`
: actionableEdge > 0
? locale === "en-US"
? `Small ${preferredSideLabel}`
: `${preferredSideLabel} 优势较小`
: locale === "en-US"
? "No clear edge"
: "暂无优势";
const actionNote =
actionableEdge != null && actionableEdge >= 0.02
? locale === "en-US"
? "model above ask"
: "模型高于买价"
: locale === "en-US"
? "read-only signal"
: "只读信号";
return ( return (
<section className="prob-section"> <section className="prob-section">
@@ -840,12 +861,12 @@ export function ProbabilityDistribution({
: "未匹配到活跃盘口" : "未匹配到活跃盘口"
: linkedMarketBucket : linkedMarketBucket
? locale === "en-US" ? locale === "en-US"
? `${topProbabilityLabel || "Top bucket"} vs ${toPriceCents(linkedMarketAsk) || "--"}` ? `${actionText}: ${topProbabilityLabel || "top bucket"}`
: `${topProbabilityLabel || "最高概率桶"} 对比 ${toPriceCents(linkedMarketAsk) || "--"}` : `${actionText}${topProbabilityLabel || "最高概率桶"}`
: preferredPriceView?.edge != null : preferredPriceView?.edge != null
? locale === "en-US" ? locale === "en-US"
? `${preferredSideLabel} edge ${formatSignedPercent(preferredPriceView.edge)}` ? `${actionText} · edge ${formatSignedPercent(preferredPriceView.edge)}`
: `${preferredSideLabel} 边际 ${formatSignedPercent(preferredPriceView.edge)}` : `${actionText} · 优势 ${formatSignedPercent(preferredPriceView.edge)}`
: locale === "en-US" : locale === "en-US"
? "Waiting for executable quote" ? "Waiting for executable quote"
: "等待可执行报价"} : "等待可执行报价"}
@@ -858,8 +879,13 @@ export function ProbabilityDistribution({
<em>{topProbabilityText || "--"}</em> <em>{topProbabilityText || "--"}</em>
</div> </div>
<div> <div>
<span>{locale === "en-US" ? "Market ask" : "市场 ask"}</span> <span>{locale === "en-US" ? "Candidate" : "可关注"}</span>
<strong>{toPriceCents(linkedMarketAsk ?? yesPriceView?.ask) || "--"}</strong> <strong>{actionText}</strong>
<em>{actionNote}</em>
</div>
<div>
<span>{locale === "en-US" ? "Buy price" : "买入价格"}</span>
<strong>{toPriceCents(preferredPriceView?.ask) || "--"}</strong>
<em> <em>
{linkedMarketBucket {linkedMarketBucket
? locale === "en-US" ? locale === "en-US"
@@ -873,30 +899,17 @@ export function ProbabilityDistribution({
</em> </em>
</div> </div>
<div> <div>
<span>{locale === "en-US" ? "Edge" : "边际"}</span> <span>{locale === "en-US" ? "Model edge" : "模型优势"}</span>
<strong>{formatSignedPercent(preferredPriceView?.edge)}</strong> <strong>{formatSignedPercent(preferredPriceView?.edge)}</strong>
<em> <em>
{quoteSourceLabel} {quoteSourceLabel}
</em> </em>
</div> </div>
<div>
<span>{locale === "en-US" ? "1/4 Kelly" : "1/4 Kelly"}</span>
<strong>{formatKellyFraction(preferredPriceView?.quarter_kelly)}</strong>
<em>
{lockAvailable
? locale === "en-US"
? `lock ${formatSignedPercent(lockEdge)}`
: `锁价 ${formatSignedPercent(lockEdge)}`
: locale === "en-US"
? "cap required"
: "需限额"}
</em>
</div>
</div> </div>
<p> <p>
{locale === "en-US" {locale === "en-US"
? `Read-only sizing reference from executable quotes; it does not place orders. Source: ${quoteSourceLabel}.` ? `Read-only comparison between model probability and executable ask; it does not place orders. Source: ${quoteSourceLabel}${lockAvailable ? ` · lock ${formatSignedPercent(lockEdge)}` : ""}.`
: `基于可执行盘口的只读仓位参考;系统不会下单。来源:${quoteSourceLabel}`} : `只比较模型概率与可执行买价;系统不会下单。来源:${quoteSourceLabel}${lockAvailable ? ` · 锁价 ${formatSignedPercent(lockEdge)}` : ""}`}
</p> </p>
</div> </div>
)} )}
@@ -926,26 +939,12 @@ export function ProbabilityDistribution({
marketBucketTemp != null && marketBucketTemp != null &&
bucketTemp != null && bucketTemp != null &&
Math.abs(bucketTemp - marketBucketTemp) < 0.26; Math.abs(bucketTemp - marketBucketTemp) < 0.26;
const marketTag = isMarketBucket
? locale === "en-US"
? `Market ref: ${marketYesText || "--"}`
: `市场参考: ${marketYesText || "--"}`
: marketNoText
? locale === "en-US"
? `Market hedge: ${marketNoText}`
: `市场反向: ${marketNoText}`
: null;
const yesPriceText = toPriceCents(marketYesPrice); const yesPriceText = toPriceCents(marketYesPrice);
const noPriceText = toPriceCents(marketNoPrice);
const marketTagFinal = isMarketBucket const marketTagFinal = isMarketBucket
? locale === "en-US" ? locale === "en-US"
? `Market ref: ${yesPriceText || "--"}` ? `Market ref: ${yesPriceText || "--"}`
: `市场参考: ${yesPriceText || "--"}` : `市场参考: ${yesPriceText || "--"}`
: noPriceText : null;
? locale === "en-US"
? `Market hedge: ${noPriceText}`
: `市场反向: ${noPriceText}`
: marketTag;
let bucketLabel = let bucketLabel =
bucket.label || `${bucket.value}${detail.temp_symbol}`; bucket.label || `${bucket.value}${detail.temp_symbol}`;
if (bucketLabel) { if (bucketLabel) {