Revert blue-sky projection; keep played-out setup UX

A local backtest (offline prod snapshot, 506 tickers) evaluated blue-sky
projected targets under the PRODUCTION exit (3x ATR trailing + 30d max hold,
paper_trade_service DEFAULT_EXIT_MODE="atr_trailing"). Blue-sky setups are
dilutive: the qualified book scored 328% return / Sharpe 1.84 / DD -21.0%
WITHOUT them vs 300% / 1.58 / -18.7% WITH them. They rank high on momentum by
construction, so they grab slots from S/R setups that catch bigger runs under
a trailing-stop exit (only ~2pp worse drawdown doesn't justify the lost return
and Sharpe).

Reverts the scanner/TargetGenerator measured-move projection, the stricter
projected activation gate, the frontend qualification mirror, the `projected`
type field, and the projected tests -- all backend files are now byte-identical
to the pre-blue-sky commit.

Keeps the played-out "No current setup" UX (RecommendationPanel): when price
has run past the target (played out) or through the stop (invalidated), the
panel shows a plain no-setup state instead of a stale actionable card. This is
frontend-only (reads last close + existing setup fields) and is what actually
fixes the reported stale-below-price bug -- no backend change or rescan needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 22:03:25 +02:00
co-authored by Claude Opus 4.8
parent 294d935030
commit 65d2dae62a
8 changed files with 25 additions and 416 deletions
@@ -103,7 +103,6 @@ function TargetTable({ setup }: { setup: TradeSetup }) {
<td className="py-2 pr-3 text-gray-300">
{target.is_primary && <span className="mr-1 text-blue-300"></span>}
{target.classification}
{target.projected && <span className="ml-1 text-sky-400">(projected)</span>}
</td>
<td className="py-2 pr-3 font-mono text-gray-200">{formatPrice(target.price)}</td>
<td className="py-2 pr-3 font-mono text-gray-200">{formatPercent((target.distance_from_entry / setup.entry_price) * 100)}</td>
@@ -130,7 +129,6 @@ function SetupCard({ setup, action, currentPrice, risk, regime }: { setup?: Trad
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;
const primaryProjected = setup.targets?.some((t) => t.is_primary && t.projected) ?? false;
// When price has run to/past the target (played out) or through the stop
// (invalidated), there is no fresh setup — show a plain "no current setup"
@@ -205,11 +203,6 @@ function SetupCard({ setup, action, currentPrice, risk, regime }: { setup?: Trad
</p>
)}
{primaryProjected && (
<p className="text-[11px] text-sky-400">
Blue-sky: no resistance overhead target is an ATR measured-move projection, not an S/R level.
</p>
)}
{drift && drift.status === 'invalidated' && (
<p className="text-[11px] text-red-400">
Price ({formatPrice(currentPrice!)}) is past the stop this setup is invalidated.