feat: add PanelSections component with helper utilities for dashboard data visualization and model processing
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user