Show production rank below ticker chart

This commit is contained in:
2026-07-13 12:35:16 +02:00
parent 4730d19694
commit 0873176f64
3 changed files with 218 additions and 0 deletions
+8
View File
@@ -108,6 +108,13 @@ price. Hover a bar to inspect its price, crossing count, strength and source.
The violet profile is deliberately distinct from the Structural S/R lines and
is off by default; it is a research aid, not another trade overlay.
Below the chart, the **Production rank** strip makes the current 80/20 ordering
snapshot explicit: a blue residual-momentum contribution and amber realized-
volatility contribution add to the stored strategy rank, while separate
percentile rails show each input. Only momentum carries the live activation-
gate marker. These are cross-sectional scan percentiles, not historical chart
indicators.
### Daily Load — the full refresh
Once a day (default 07:00). Steps run **in dependency order**, each consuming the previous step's fresh output:
@@ -284,6 +291,7 @@ Corollaries: never let an unvalidated score gate setups; the outcome evaluator m
- Interactive candlestick chart (Canvas 2D) with hover tooltips showing OHLCV values
- Support/Resistance level overlays on chart (top 6 by strength, dashed lines with labels)
- Optional GTL price-traffic profile on the ticker chart (right-edge diagnostic; explicitly not volume)
- Production-rank strip below the ticker chart (80/20 contribution ledger plus separate momentum and volatility percentiles)
- Data freshness bar showing availability and recency of each data source
- Watchlist with composite scores, R:R ratios, and S/R summaries
- Ticker detail page: chart, scores, sentiment breakdown, fundamentals, technical indicators, S/R table
@@ -0,0 +1,205 @@
import type { TradeSetup } from '../../lib/types';
const MOMENTUM_WEIGHT = 0.8;
const VOLATILITY_WEIGHT = 0.2;
interface ProductionRankStripProps {
setup?: TradeSetup;
momentumGate: number;
}
function clampPercent(value: number): number {
return Math.min(100, Math.max(0, value));
}
function topShare(value: number): string {
return `${Math.max(1, Math.round(100 - clampPercent(value)))}%`;
}
function PercentileRail({
value,
colorClass,
gate,
gateLabel,
}: {
value: number | null;
colorClass: string;
gate?: number;
gateLabel?: string;
}) {
const normalized = value == null ? 0 : clampPercent(value);
const normalizedGate = gate == null ? null : clampPercent(gate);
return (
<div
className="relative mt-2 h-1.5 rounded-full bg-white/[0.07]"
role="meter"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={value == null ? undefined : normalized}
aria-label={value == null ? 'Percentile unavailable' : `${normalized.toFixed(1)} percentile`}
>
{value != null && (
<span
className={`absolute inset-y-0 left-0 rounded-full ${colorClass}`}
style={{ width: `${normalized}%` }}
/>
)}
{normalizedGate != null && (
<span
className="absolute -top-1.5 h-4 w-px bg-gray-300/70"
style={{ left: `${normalizedGate}%` }}
title={gateLabel}
>
<span className="absolute -top-4 left-1/2 -translate-x-1/2 whitespace-nowrap text-[8px] uppercase tracking-[0.14em] text-gray-500">
gate
</span>
</span>
)}
</div>
);
}
export function ProductionRankStrip({ setup, momentumGate }: ProductionRankStripProps) {
const momentum = setup?.momentum_percentile ?? null;
const volatility = setup?.volatility_percentile ?? null;
const storedRank = setup?.strategy_rank ?? null;
const hasBlend = momentum != null && volatility != null;
const computedBlend = hasBlend
? momentum * MOMENTUM_WEIGHT + volatility * VOLATILITY_WEIGHT
: null;
const rank = storedRank ?? computedBlend ?? momentum;
if (rank == null && momentum == null && volatility == null) return null;
const normalizedRank = clampPercent(rank ?? 0);
const momentumContribution = hasBlend ? clampPercent(momentum) * MOMENTUM_WEIGHT : normalizedRank;
const volatilityContribution = hasBlend ? clampPercent(volatility) * VOLATILITY_WEIGHT : 0;
const gateEnabled = momentumGate > 0;
const gatePassed = momentum != null && (!gateEnabled || momentum >= momentumGate);
return (
<section
className="mt-4 border-y border-white/[0.07] py-4"
aria-label="Production ranking snapshot"
>
<div className="grid gap-5 lg:grid-cols-[minmax(170px,0.55fr)_minmax(0,1.8fr)] lg:gap-8">
<div className="flex items-end justify-between gap-4 lg:block">
<div>
<p className="num text-[9px] uppercase tracking-[0.22em] text-gray-500">
Production rank
</p>
{rank != null ? (
<p className="font-display mt-1 text-3xl font-semibold tracking-tight text-gray-100">
{normalizedRank.toFixed(1)}
<span className="ml-1 text-sm font-normal text-gray-500">%ile</span>
</p>
) : (
<p className="font-display mt-1 text-2xl font-semibold text-gray-500">Unavailable</p>
)}
</div>
<div className="text-right lg:mt-2 lg:text-left">
{rank != null && (
<p className="text-[11px] text-gray-400">top {topShare(normalizedRank)} of the universe</p>
)}
{momentum != null && gateEnabled && (
<p className={`mt-0.5 text-[10px] ${gatePassed ? 'text-blue-300' : 'text-red-300'}`}>
momentum gate {gatePassed ? 'passed' : 'not passed'}
</p>
)}
</div>
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
<p className="text-[11px] font-medium text-gray-300">80/20 weighted rank</p>
<p className="num text-[10px] text-gray-500">
{hasBlend
? `${momentumContribution.toFixed(1)} momentum + ${volatilityContribution.toFixed(1)} volatility`
: 'momentum-only fallback / volatility rank unavailable'}
</p>
</div>
<div
className="relative mt-2 h-2.5 overflow-visible rounded-full bg-white/[0.07]"
role="meter"
aria-label={rank == null ? 'Production rank unavailable' : `Production rank ${normalizedRank.toFixed(1)} percentile`}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={rank == null ? undefined : normalizedRank}
>
<span
className={`absolute inset-y-0 left-0 bg-blue-500 ${
volatilityContribution > 0 ? 'rounded-l-full' : 'rounded-full'
}`}
style={{ width: `${momentumContribution}%` }}
title={`Momentum contribution ${momentumContribution.toFixed(1)} points`}
/>
{volatilityContribution > 0 && (
<span
className="absolute inset-y-0 rounded-r-full bg-amber-400"
style={{ left: `${momentumContribution}%`, width: `${volatilityContribution}%` }}
title={`Volatility contribution ${volatilityContribution.toFixed(1)} points`}
/>
)}
{rank != null && (
<span
className="absolute -top-1.5 h-5 w-px bg-gray-100 shadow-[0_0_8px_rgba(237,238,243,0.55)]"
style={{ left: `${normalizedRank}%` }}
title={`Combined rank ${normalizedRank.toFixed(1)}`}
/>
)}
</div>
<div className="mt-5 grid gap-x-8 gap-y-4 sm:grid-cols-2">
<div>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-medium text-blue-200">Residual 12-1 momentum</p>
<p className="mt-0.5 text-[9px] text-gray-600">80% weight / activation signal</p>
</div>
<div className="text-right">
<p className="num text-sm text-gray-200">
{momentum == null ? '-' : momentum.toFixed(1)}
</p>
{momentum != null && (
<p className="text-[9px] text-gray-600">top {topShare(momentum)}</p>
)}
</div>
</div>
<PercentileRail
value={momentum}
colorClass="bg-blue-500"
gate={gateEnabled ? momentumGate : undefined}
gateLabel={gateEnabled ? `Activation requires at least the ${momentumGate.toFixed(0)}th percentile` : undefined}
/>
</div>
<div>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-medium text-amber-200">6-month realized volatility</p>
<p className="mt-0.5 text-[9px] text-gray-600">20% weight / ordering tilt only</p>
</div>
<div className="text-right">
<p className="num text-sm text-gray-200">
{volatility == null ? '-' : volatility.toFixed(1)}
</p>
{volatility != null && (
<p className="text-[9px] text-gray-600">top {topShare(volatility)}</p>
)}
</div>
</div>
<PercentileRail value={volatility} colorClass="bg-amber-400" />
</div>
</div>
</div>
</div>
<p className="mt-3 text-[9px] leading-relaxed text-gray-600">
Cross-sectional snapshot from the latest setup scan - not a historical price indicator.
Volatility can improve ordering, but it never opens the activation gate by itself.
</p>
</section>
);
}
+5
View File
@@ -17,6 +17,7 @@ import { SentimentPanel } from '../components/ticker/SentimentPanel';
import { FundamentalsPanel } from '../components/ticker/FundamentalsPanel';
import { IndicatorSelector } from '../components/ticker/IndicatorSelector';
import { RecommendationPanel } from '../components/ticker/RecommendationPanel';
import { ProductionRankStrip } from '../components/ticker/ProductionRankStrip';
import { Button } from '../components/ui/Button';
import { Callout } from '../components/ui/Callout';
import { formatPrice } from '../lib/format';
@@ -461,6 +462,10 @@ export default function TickerDetailPage() {
{srLevels.isError && ' S/R levels unavailable.'}
{gateTargetLadder.isError && ' GTL diagnostic unavailable.'}
</p>
<ProductionRankStrip
setup={longSetup ?? shortSetup}
momentumGate={gateMomentum}
/>
</>
)}
{(longSetup || shortSetup) && (