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) && (