feat: require gate reset before post-stop reentry

This commit is contained in:
2026-07-17 19:30:40 +02:00
parent 1a6f82bf6d
commit 5155d00d9e
18 changed files with 577 additions and 278 deletions
+3 -4
View File
@@ -43,7 +43,7 @@ export function liveRiskReward(setup: TradeSetup, currentPrice: number): number
* app/services/qualification.py — keep the two in sync.
*/
export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boolean {
if ((setup.reentry_lockdown_remaining_sessions ?? 0) > 0) return false;
if (setup.reentry_gate_reset_required) return false;
if (setup.rr_ratio < config.min_rr) return false;
// Live R:R from current price — drops setups whose price has already run
// toward target (reward consumed) or through the stop.
@@ -80,9 +80,8 @@ export function qualifiesSetup(setup: TradeSetup, config: ActivationConfig): boo
* qualifiesSetup rule-for-rule (keep the order in sync).
*/
export function disqualifyReason(setup: TradeSetup, config: ActivationConfig): string | null {
const lockdownRemaining = setup.reentry_lockdown_remaining_sessions ?? 0;
if (lockdownRemaining > 0) {
return `post-stop lockdown · ${lockdownRemaining} session${lockdownRemaining === 1 ? '' : 's'} remaining`;
if (setup.reentry_gate_reset_required) {
return 'post-stop gate reset required';
}
if (setup.rr_ratio < config.min_rr) {
return `R:R ${setup.rr_ratio.toFixed(1)} below gate ${config.min_rr.toFixed(1)}`;
+6 -6
View File
@@ -144,7 +144,7 @@ export interface TradeSetup {
momentum_percentile?: number | null;
strategy_rank?: number | null;
volatility_percentile?: number | null;
reentry_lockdown_remaining_sessions?: number | null;
reentry_gate_reset_required?: boolean;
context_as_of?: TradeSetupContextAsOf | null;
recommendation_summary?: RecommendationSummary;
}
@@ -356,10 +356,10 @@ export interface BacktestPortfolioMonitorRun extends BacktestPortfolioPolicy {
label: string;
description: string;
is_production: boolean;
comparison_arm?: 'live_no_lockdown' | 'live_lockdown_5' | null;
comparison_arm?: 'live_immediate' | 'live_gate_reset' | null;
entry_variant: string;
exit_policy: string;
reentry_lockdown_sessions?: number;
reentry_policy?: 'immediate' | 'gate_reset';
lookback: string;
lookback_label: string;
}
@@ -371,8 +371,8 @@ export interface BacktestPortfolioMonitor {
label: string;
description: string;
is_production: boolean;
comparison_arm?: 'live_no_lockdown' | 'live_lockdown_5' | null;
reentry_lockdown_sessions?: number;
comparison_arm?: 'live_immediate' | 'live_gate_reset' | null;
reentry_policy?: 'immediate' | 'gate_reset';
}[];
lookbacks: { lookback: string; label: string }[];
runs: BacktestPortfolioMonitorRun[];
@@ -415,7 +415,7 @@ export interface BacktestReport {
target_model?: 'production_gtl' | 'structural_sr';
target_model_label?: string;
is_production_target_model?: boolean;
production_reentry_lockdown_sessions?: number;
production_reentry_policy?: 'gate_reset';
};
overall_qualified: BacktestBucket;
overall_all: BacktestBucket;