From 0873176f64f99b37cfed76423207cb89ec72c523 Mon Sep 17 00:00:00 2001
From: Dennis Thiessen
Date: Mon, 13 Jul 2026 12:35:16 +0200
Subject: [PATCH] Show production rank below ticker chart
---
README.md | 8 +
.../components/ticker/ProductionRankStrip.tsx | 205 ++++++++++++++++++
frontend/src/pages/TickerDetailPage.tsx | 5 +
3 files changed, 218 insertions(+)
create mode 100644 frontend/src/components/ticker/ProductionRankStrip.tsx
diff --git a/README.md b/README.md
index e97d2ad..6e70d69 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/frontend/src/components/ticker/ProductionRankStrip.tsx b/frontend/src/components/ticker/ProductionRankStrip.tsx
new file mode 100644
index 0000000..33df0ab
--- /dev/null
+++ b/frontend/src/components/ticker/ProductionRankStrip.tsx
@@ -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 (
+
+ {value != null && (
+
+ )}
+ {normalizedGate != null && (
+
+
+ gate
+
+
+ )}
+
+ );
+}
+
+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 (
+
+
+
+
+
+ Production rank
+
+ {rank != null ? (
+
+ {normalizedRank.toFixed(1)}
+ %ile
+
+ ) : (
+
Unavailable
+ )}
+
+
+ {rank != null && (
+
top {topShare(normalizedRank)} of the universe
+ )}
+ {momentum != null && gateEnabled && (
+
+ momentum gate {gatePassed ? 'passed' : 'not passed'}
+
+ )}
+
+
+
+
+
+
80/20 weighted rank
+
+ {hasBlend
+ ? `${momentumContribution.toFixed(1)} momentum + ${volatilityContribution.toFixed(1)} volatility`
+ : 'momentum-only fallback / volatility rank unavailable'}
+
+
+
+
+ 0 ? 'rounded-l-full' : 'rounded-full'
+ }`}
+ style={{ width: `${momentumContribution}%` }}
+ title={`Momentum contribution ${momentumContribution.toFixed(1)} points`}
+ />
+ {volatilityContribution > 0 && (
+
+ )}
+ {rank != null && (
+
+ )}
+
+
+
+
+
+
+
Residual 12-1 momentum
+
80% weight / activation signal
+
+
+
+ {momentum == null ? '-' : momentum.toFixed(1)}
+
+ {momentum != null && (
+
top {topShare(momentum)}
+ )}
+
+
+
+
+
+
+
+
+
6-month realized volatility
+
20% weight / ordering tilt only
+
+
+
+ {volatility == null ? '-' : volatility.toFixed(1)}
+
+ {volatility != null && (
+
top {topShare(volatility)}
+ )}
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+ );
+}
diff --git a/frontend/src/pages/TickerDetailPage.tsx b/frontend/src/pages/TickerDetailPage.tsx
index ab71c73..6d3e8a6 100644
--- a/frontend/src/pages/TickerDetailPage.tsx
+++ b/frontend/src/pages/TickerDetailPage.tsx
@@ -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.'}
+
>
)}
{(longSetup || shortSetup) && (