Surface current price; flag stale setups; declutter chart
Triggered by MRK: entry 113 shown with no current price (actually ~119). - Ticker header shows last close + day change % + "last close · Nd ago" (the age reveals OHLCV collection lag — why entry looked off) - Setup cards show Current price and entry drift; flag setups as stale (price moved >1/3 toward target) or invalidated (past stop) - Chart: draw only nearest support below + nearest resistance above current price, plus a prominent "Now" price line (full S/R stays in the S/R tab) - Chart overlay is selectable (Auto/Long/Short/None) — only the chosen setup's entry/stop/target render, instead of everything at once Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,26 @@ interface RecommendationPanelProps {
|
||||
symbol: string;
|
||||
longSetup?: TradeSetup;
|
||||
shortSetup?: TradeSetup;
|
||||
currentPrice?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* How far current price has drifted from the setup's entry. A setup whose
|
||||
* entry is far from the live price (price already ran toward target, or fell
|
||||
* through the stop) is stale — entering now changes the risk/reward.
|
||||
*/
|
||||
function entryDrift(setup: TradeSetup, currentPrice?: number) {
|
||||
if (currentPrice == null || !setup.entry_price) return null;
|
||||
const pct = ((currentPrice - setup.entry_price) / setup.entry_price) * 100;
|
||||
const towardTarget = setup.direction === 'long' ? currentPrice >= setup.entry_price : currentPrice <= setup.entry_price;
|
||||
// Past the stop entirely = invalidated; moved >1/3 of the way to target = stale
|
||||
const span = Math.abs(setup.target - setup.entry_price);
|
||||
const moved = Math.abs(currentPrice - setup.entry_price);
|
||||
const beyondStop = setup.direction === 'long' ? currentPrice <= setup.stop_loss : currentPrice >= setup.stop_loss;
|
||||
let status: 'fresh' | 'stale' | 'invalidated' = 'fresh';
|
||||
if (beyondStop) status = 'invalidated';
|
||||
else if (span > 0 && moved / span > 0.33) status = 'stale';
|
||||
return { pct, towardTarget, status };
|
||||
}
|
||||
|
||||
function riskClass(risk: TradeSetup['risk_level']) {
|
||||
@@ -54,7 +74,7 @@ function TargetTable({ setup }: { setup: TradeSetup }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SetupCard({ setup, action }: { setup?: TradeSetup; action?: TradeSetup['recommended_action'] }) {
|
||||
function SetupCard({ setup, action, currentPrice }: { setup?: TradeSetup; action?: TradeSetup['recommended_action']; currentPrice?: number }) {
|
||||
if (!setup) {
|
||||
return (
|
||||
<div className="glass-sm p-4 text-xs text-gray-500">
|
||||
@@ -64,6 +84,7 @@ function SetupCard({ setup, action }: { setup?: TradeSetup; action?: TradeSetup[
|
||||
}
|
||||
|
||||
const recommended = isRecommended(setup, action);
|
||||
const drift = entryDrift(setup, currentPrice);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -81,8 +102,20 @@ function SetupCard({ setup, action }: { setup?: TradeSetup; action?: TradeSetup[
|
||||
<p className="text-[11px] text-amber-400">Alternative setup (ticker bias currently favors the opposite direction).</p>
|
||||
)}
|
||||
|
||||
{drift && drift.status === 'invalidated' && (
|
||||
<p className="text-[11px] text-red-400">
|
||||
⚠ Price ({formatPrice(currentPrice!)}) is past the stop — this setup is invalidated.
|
||||
</p>
|
||||
)}
|
||||
{drift && drift.status === 'stale' && (
|
||||
<p className="text-[11px] text-amber-400">
|
||||
⚠ Price has moved {drift.pct >= 0 ? '+' : ''}{drift.pct.toFixed(1)}% from entry{drift.towardTarget ? ' toward target' : ' against the setup'} — entry may be stale.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="text-gray-500">Entry</div><div className="font-mono text-gray-200">{formatPrice(setup.entry_price)}</div>
|
||||
<div className="text-gray-500">Current</div><div className="font-mono text-gray-200">{currentPrice != null ? formatPrice(currentPrice) : '—'}</div>
|
||||
<div className="text-gray-500">Entry</div><div className="font-mono text-gray-200">{formatPrice(setup.entry_price)}{drift ? ` (${drift.pct >= 0 ? '+' : ''}${drift.pct.toFixed(1)}%)` : ''}</div>
|
||||
<div className="text-gray-500">Stop</div><div className="font-mono text-gray-200">{formatPrice(setup.stop_loss)}</div>
|
||||
<div className="text-gray-500">Primary Target</div><div className="font-mono text-gray-200">{formatPrice(setup.target)}</div>
|
||||
<div className="text-gray-500">R:R</div><div className="font-mono text-gray-200">{setup.rr_ratio.toFixed(2)}</div>
|
||||
@@ -99,7 +132,7 @@ function SetupCard({ setup, action }: { setup?: TradeSetup; action?: TradeSetup[
|
||||
);
|
||||
}
|
||||
|
||||
export function RecommendationPanel({ symbol, longSetup, shortSetup }: RecommendationPanelProps) {
|
||||
export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPrice }: RecommendationPanelProps) {
|
||||
const summary = longSetup?.recommendation_summary ?? shortSetup?.recommendation_summary;
|
||||
const action = (summary?.action ?? 'NEUTRAL') as TradeSetup['recommended_action'];
|
||||
const preferredDirection = recommendationActionDirection(action);
|
||||
@@ -143,7 +176,7 @@ export function RecommendationPanel({ symbol, longSetup, shortSetup }: Recommend
|
||||
|
||||
{preferredDirection !== 'neutral' && preferredSetup ? (
|
||||
<div className="space-y-3">
|
||||
<SetupCard setup={preferredSetup} action={action} />
|
||||
<SetupCard setup={preferredSetup} action={action} currentPrice={currentPrice} />
|
||||
|
||||
{alternativeSetup && (
|
||||
<details className="glass-sm p-3">
|
||||
@@ -151,15 +184,15 @@ export function RecommendationPanel({ symbol, longSetup, shortSetup }: Recommend
|
||||
Alternative scenario ({alternativeSetup.direction.toUpperCase()})
|
||||
</summary>
|
||||
<div className="mt-3">
|
||||
<SetupCard setup={alternativeSetup} action={action} />
|
||||
<SetupCard setup={alternativeSetup} action={action} currentPrice={currentPrice} />
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<SetupCard setup={longSetup} action={action} />
|
||||
<SetupCard setup={shortSetup} action={action} />
|
||||
<SetupCard setup={longSetup} action={action} currentPrice={currentPrice} />
|
||||
<SetupCard setup={shortSetup} action={action} currentPrice={currentPrice} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user