Require aligned action for qualified setups
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m7s
Deploy / deploy (push) Successful in 39s

This commit is contained in:
2026-07-03 16:13:27 +02:00
parent eaad935a2a
commit 14327ab25a
6 changed files with 57 additions and 24 deletions
+16 -7
View File
@@ -2,6 +2,13 @@ import type { ActivationConfig, TradeSetup } from './types';
const HIGH_CONVICTION_ACTIONS = new Set(['LONG_HIGH', 'SHORT_HIGH']);
function actionDirection(action: TradeSetup['recommended_action']): 'long' | 'short' | 'neutral' {
if (!action || action === 'NEUTRAL') return 'neutral';
if (action.startsWith('LONG')) return 'long';
if (action.startsWith('SHORT')) return 'short';
return 'neutral';
}
export function bestTargetProbability(setup: TradeSetup): number {
return setup.targets?.length ? Math.max(...setup.targets.map((t) => t.probability)) : 0;
}
@@ -42,8 +49,11 @@ 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;
// NEUTRAL = "no clear setup"; an opposite action means this setup is counter-bias.
if (config.exclude_neutral) {
const actionDir = actionDirection(setup.recommended_action);
if (actionDir === 'neutral' || actionDir !== setup.direction) return false;
}
if (config.require_high_conviction && !HIGH_CONVICTION_ACTIONS.has(setup.recommended_action ?? '')) {
return false;
}
@@ -53,9 +63,9 @@ export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boo
/**
* Symbol of the current single 'top pick' — the #1 row the dashboard highlights:
* the highest residual 12-1 momentum percentile among qualified setups (or among all
* setups when none qualify). Returns null when there are no setups. Keep in step
* with the Top Setups ranking in DashboardPage.
* the highest residual 12-1 momentum percentile among qualified setups. Returns
* null when there are no actionable setups. Keep in step with the Top Setups
* ranking in DashboardPage.
*/
export function topPickSymbol(
trades: TradeSetup[] | undefined,
@@ -64,8 +74,7 @@ export function topPickSymbol(
const all = trades ?? [];
if (all.length === 0) return null;
const qualified = activation ? all.filter((t) => qualifiesSetup(t, activation)) : [];
const pool = qualified.length > 0 ? qualified : all;
const top = [...pool].sort(
const top = [...qualified].sort(
(a, b) => (b.momentum_percentile ?? -Infinity) - (a.momentum_percentile ?? -Infinity),
)[0];
return top?.symbol ?? null;