Horizon redesign: space theme, visual dashboard, chart reskin
Replaces the citron/green glass theme with the Horizon direction (mockup at /design-horizon): space-void background, starfield, dim Mars horizon, rim-cyan accent, rose for negative. Palette validated for CVD separation and contrast on the dark surface. - tailwind.config: gray -> cool space neutrals, blue/emerald -> cyan scale, red -> rose; display font Space Grotesk - globals.css: Horizon tokens, atmosphere, denser glass, cyan buttons/inputs, price-rail / R-bar / radar chart CSS - Dashboard rebuilt: verdict hero, top-pick card with spatial price rail, KPI tiles, open positions as diverging R-bars with expandable detail + trade chart (OHLCV since entry, entry/stop/target levels), radar list with per-setup disqualify reason, watchlist chips - OpenTradesPanel: table replaced by R-bar rows; all fields kept in the drill-down (shares, P&L, alpha, trailing stop, sell) - qualification: disqualifyReason() mirrors qualifiesSetup rule order - CandlestickChart: canvas colors moved to Horizon palette; S/R now cyan/neutral so rose stays reserved for the stop level - ScoreCard: radar score fingerprint with hover values - Design mockup pages included (routed in App.tsx) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,42 @@ export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boo
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a setup does NOT clear the gate — the first failing rule, phrased for the
|
||||
* dashboard's radar list. Returns null when the setup qualifies. Mirrors
|
||||
* qualifiesSetup rule-for-rule (keep the order in sync).
|
||||
*/
|
||||
export function disqualifyReason(setup: TradeSetup, config: ActivationConfig): string | null {
|
||||
if (setup.rr_ratio < config.min_rr) {
|
||||
return `R:R ${setup.rr_ratio.toFixed(1)} below gate ${config.min_rr.toFixed(1)}`;
|
||||
}
|
||||
if (setup.current_price != null && liveRiskReward(setup, setup.current_price) < config.min_rr) {
|
||||
return 'price has run — live R:R below gate';
|
||||
}
|
||||
const targetProbability = primaryTargetProbability(setup);
|
||||
if (targetProbability == null || targetProbability <= 0) return 'no target probability';
|
||||
if ((setup.confidence_score ?? 0) < config.min_confidence) {
|
||||
return `confidence below ${config.min_confidence.toFixed(0)}%`;
|
||||
}
|
||||
if (config.min_momentum_percentile > 0) {
|
||||
if (setup.direction === 'short') return 'short — momentum gate is long-only';
|
||||
if (setup.momentum_percentile == null) return 'no residual momentum rank';
|
||||
if (setup.momentum_percentile < config.min_momentum_percentile) {
|
||||
return `momentum below ${config.min_momentum_percentile.toFixed(0)}th %ile`;
|
||||
}
|
||||
}
|
||||
if (config.exclude_neutral) {
|
||||
const actionDir = actionDirection(setup.recommended_action);
|
||||
if (actionDir === 'neutral') return 'model action neutral';
|
||||
if (actionDir !== setup.direction) return 'counter to model bias';
|
||||
}
|
||||
if (config.require_high_conviction && !HIGH_CONVICTION_ACTIONS.has(setup.recommended_action ?? '')) {
|
||||
return 'conviction below High';
|
||||
}
|
||||
if (config.exclude_conflicts && (setup.risk_level ?? '') !== 'Low') return 'risk flags not clean';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbol of the current single 'top pick' — the #1 row the dashboard highlights:
|
||||
* the highest residual 12-1 momentum percentile among qualified setups. Returns
|
||||
|
||||
Reference in New Issue
Block a user