feat: exclude NEUTRAL setups from the activation gate (default on)
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 56s
Deploy / deploy (push) Successful in 34s

A NEUTRAL ("No Clear Setup") recommendation means the engine found no clear
directional trade, yet such setups could still qualify and even be crowned the
top pick purely on momentum rank (e.g. an extended momentum leader with a far,
5%-probability target). A NEUTRAL signal isn't actionable, so it shouldn't
qualify.

New `exclude_neutral` activation flag (default on): setup_qualifies drops setups
whose recommended_action is NEUTRAL. It lives in the shared gate, so it flows
through the dashboard's qualified/top-pick selection, the track record's
qualified stats, and the backtest (which computes recommended_action and gates on
meets_core). Toggleable in Admin → Settings → Activation; the frontend mirror and
activationSummary ("directional") match.

Re-run the backtest after enabling to confirm it holds/improves expectancy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 15:19:07 +02:00
parent 20a1c143f3
commit 6511a1020b
8 changed files with 60 additions and 0 deletions
@@ -9,6 +9,7 @@ const DEFAULTS: ActivationConfig = {
min_confidence: 55,
require_high_conviction: false,
exclude_conflicts: false,
exclude_neutral: true,
};
export function ActivationSettings() {
@@ -87,6 +88,24 @@ export function ActivationSettings() {
</label>
</div>
<div className="border-t border-white/[0.06] pt-4">
<label className="flex cursor-pointer items-start gap-2.5 text-sm text-gray-300">
<input
type="checkbox"
checked={form.exclude_neutral}
onChange={(e) => setForm((prev) => ({ ...prev, exclude_neutral: e.target.checked }))}
className="mt-0.5 h-4 w-4 cursor-pointer accent-blue-400"
/>
<span>
Require a directional call (exclude NEUTRAL)
<span className="mt-0.5 block text-[11px] text-gray-500">
On by default. A NEUTRAL ("No Clear Setup") recommendation isn't a tradeable signal, so it
never qualifies or becomes a top pick. Turn off to also count no-clear-direction momentum leaders.
</span>
</span>
</label>
</div>
<div className="border-t border-white/[0.06] pt-4">
<p className="text-xs font-medium uppercase tracking-widest text-gray-500">Optional tighteners</p>
<p className="mt-1 text-[11px] text-gray-600">Off by default — turn on to be more selective on top of the momentum gate.</p>
+3
View File
@@ -42,6 +42,8 @@ export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boo
return false;
}
}
// NEUTRAL = "no clear setup" — not actionable, so by default it doesn't qualify.
if (config.exclude_neutral && (setup.recommended_action ?? 'NEUTRAL') === 'NEUTRAL') return false;
if (config.require_high_conviction && !HIGH_CONVICTION_ACTIONS.has(setup.recommended_action ?? '')) {
return false;
}
@@ -74,6 +76,7 @@ export function activationSummary(config: ActivationConfig): string {
const parts = [];
if (config.min_momentum_percentile > 0) parts.push(`top ${(100 - config.min_momentum_percentile).toFixed(0)}% momentum`);
parts.push(`R:R ≥ ${config.min_rr.toFixed(1)}`, `conf ≥ ${config.min_confidence.toFixed(0)}%`);
if (config.exclude_neutral) parts.push('directional');
if (config.require_high_conviction) parts.push('high-conviction');
if (config.exclude_conflicts) parts.push('clean');
return parts.join(' · ');
+1
View File
@@ -166,6 +166,7 @@ export interface ActivationConfig {
min_confidence: number;
require_high_conviction: boolean;
exclude_conflicts: boolean;
exclude_neutral: boolean;
}
// Cron schedule for the daily/intraday pipelines + fundamentals