add market-regime guard (SPY trend) — inform + warn
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 36s
Deploy / deploy (push) Successful in 25s

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:
2026-06-15 12:34:07 +02:00
parent 1951531453
commit c4f2673799
14 changed files with 354 additions and 6 deletions
@@ -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>