add market-regime guard (SPY trend) — inform + warn
New market_regime_service computes a benchmark (SPY) trend from its 50/200-day SMAs, cached in a SystemSetting and refreshed by a nightly job; GET /market/regime exposes it. Dashboard shows a regime banner; setup cards flag a counter-trend caution when a setup fights the regime (LONG in a bearish market / SHORT in a bullish one). Informational only — nothing is suppressed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@ import { formatPrice, formatPercent } from '../../lib/format';
|
||||
import { recommendationActionDirection, recommendationActionLabel } from '../../lib/recommendation';
|
||||
import { useRiskSettings, type RiskSettings } from '../../hooks/useRiskSettings';
|
||||
import { positionSize } from '../../lib/position';
|
||||
import { useMarketRegime } from '../../hooks/useMarketRegime';
|
||||
import { isCounterTrend } from '../../lib/regime';
|
||||
import type { MarketRegime } from '../../lib/types';
|
||||
|
||||
interface RecommendationPanelProps {
|
||||
symbol: string;
|
||||
@@ -85,7 +88,7 @@ function TargetTable({ setup }: { setup: TradeSetup }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SetupCard({ setup, action, currentPrice, risk }: { setup?: TradeSetup; action?: TradeSetup['recommended_action']; currentPrice?: number; risk: RiskSettings }) {
|
||||
function SetupCard({ setup, action, currentPrice, risk, regime }: { setup?: TradeSetup; action?: TradeSetup['recommended_action']; currentPrice?: number; risk: RiskSettings; regime?: MarketRegime }) {
|
||||
if (!setup) {
|
||||
return (
|
||||
<div className="glass-sm p-4 text-xs text-gray-500">
|
||||
@@ -97,6 +100,7 @@ function SetupCard({ setup, action, currentPrice, risk }: { setup?: TradeSetup;
|
||||
const recommended = isRecommended(setup, action);
|
||||
const drift = entryDrift(setup, currentPrice);
|
||||
const sizing = positionSize(risk.accountSize, risk.riskPct, setup.entry_price, setup.stop_loss);
|
||||
const counterTrend = regime ? isCounterTrend(setup.direction, regime.label) : false;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -114,6 +118,13 @@ function SetupCard({ setup, action, currentPrice, risk }: { setup?: TradeSetup;
|
||||
<p className="text-[11px] text-amber-400">Alternative setup (ticker bias currently favors the opposite direction).</p>
|
||||
)}
|
||||
|
||||
{counterTrend && regime && (
|
||||
<p className="text-[11px] text-amber-400">
|
||||
⚠ Counter-trend: {setup.direction.toUpperCase()} against a {regime.label} market
|
||||
({regime.benchmark ?? 'SPY'}). Lower odds — size down or wait for confirmation.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{drift && drift.status === 'invalidated' && (
|
||||
<p className="text-[11px] text-red-400">
|
||||
⚠ Price ({formatPrice(currentPrice!)}) is past the stop — this setup is invalidated.
|
||||
@@ -206,6 +217,7 @@ function RiskSettingsBar({ risk, update }: { risk: RiskSettings; update: (p: Par
|
||||
|
||||
export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPrice }: RecommendationPanelProps) {
|
||||
const { settings: risk, update: updateRisk } = useRiskSettings();
|
||||
const regime = useMarketRegime().data;
|
||||
const summary = longSetup?.recommendation_summary ?? shortSetup?.recommendation_summary;
|
||||
const action = (summary?.action ?? 'NEUTRAL') as TradeSetup['recommended_action'];
|
||||
const preferredDirection = recommendationActionDirection(action);
|
||||
@@ -251,7 +263,7 @@ export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPric
|
||||
|
||||
{preferredDirection !== 'neutral' && preferredSetup ? (
|
||||
<div className="space-y-3">
|
||||
<SetupCard setup={preferredSetup} action={action} currentPrice={currentPrice} risk={risk} />
|
||||
<SetupCard setup={preferredSetup} action={action} currentPrice={currentPrice} risk={risk} regime={regime} />
|
||||
|
||||
{alternativeSetup && (
|
||||
<details className="glass-sm p-3">
|
||||
@@ -259,15 +271,15 @@ export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPric
|
||||
Alternative scenario ({alternativeSetup.direction.toUpperCase()})
|
||||
</summary>
|
||||
<div className="mt-3">
|
||||
<SetupCard setup={alternativeSetup} action={action} currentPrice={currentPrice} risk={risk} />
|
||||
<SetupCard setup={alternativeSetup} action={action} currentPrice={currentPrice} risk={risk} regime={regime} />
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<SetupCard setup={longSetup} action={action} currentPrice={currentPrice} risk={risk} />
|
||||
<SetupCard setup={shortSetup} action={action} currentPrice={currentPrice} risk={risk} />
|
||||
<SetupCard setup={longSetup} action={action} currentPrice={currentPrice} risk={risk} regime={regime} />
|
||||
<SetupCard setup={shortSetup} action={action} currentPrice={currentPrice} risk={risk} regime={regime} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user