Show production rank below ticker chart
This commit is contained in:
@@ -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
|
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.
|
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
|
### Daily Load — the full refresh
|
||||||
|
|
||||||
Once a day (default 07:00). Steps run **in dependency order**, each consuming the previous step's fresh output:
|
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
|
- 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)
|
- 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)
|
- 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
|
- Data freshness bar showing availability and recency of each data source
|
||||||
- Watchlist with composite scores, R:R ratios, and S/R summaries
|
- Watchlist with composite scores, R:R ratios, and S/R summaries
|
||||||
- Ticker detail page: chart, scores, sentiment breakdown, fundamentals, technical indicators, S/R table
|
- 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import { SentimentPanel } from '../components/ticker/SentimentPanel';
|
|||||||
import { FundamentalsPanel } from '../components/ticker/FundamentalsPanel';
|
import { FundamentalsPanel } from '../components/ticker/FundamentalsPanel';
|
||||||
import { IndicatorSelector } from '../components/ticker/IndicatorSelector';
|
import { IndicatorSelector } from '../components/ticker/IndicatorSelector';
|
||||||
import { RecommendationPanel } from '../components/ticker/RecommendationPanel';
|
import { RecommendationPanel } from '../components/ticker/RecommendationPanel';
|
||||||
|
import { ProductionRankStrip } from '../components/ticker/ProductionRankStrip';
|
||||||
import { Button } from '../components/ui/Button';
|
import { Button } from '../components/ui/Button';
|
||||||
import { Callout } from '../components/ui/Callout';
|
import { Callout } from '../components/ui/Callout';
|
||||||
import { formatPrice } from '../lib/format';
|
import { formatPrice } from '../lib/format';
|
||||||
@@ -461,6 +462,10 @@ export default function TickerDetailPage() {
|
|||||||
{srLevels.isError && ' S/R levels unavailable.'}
|
{srLevels.isError && ' S/R levels unavailable.'}
|
||||||
{gateTargetLadder.isError && ' GTL diagnostic unavailable.'}
|
{gateTargetLadder.isError && ' GTL diagnostic unavailable.'}
|
||||||
</p>
|
</p>
|
||||||
|
<ProductionRankStrip
|
||||||
|
setup={longSetup ?? shortSetup}
|
||||||
|
momentumGate={gateMomentum}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(longSetup || shortSetup) && (
|
{(longSetup || shortSetup) && (
|
||||||
|
|||||||
Reference in New Issue
Block a user