From 2b85fa2fdffd218dfc031b8a742da18cbbfa8bac Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 25 Mar 2026 23:06:28 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20add=20min=5Fhold=5Fbars=20config=20(def?= =?UTF-8?q?ault=205)=20=E2=80=94=20prevents=20per-bar=20churning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add min_hold_bars to DQNHyperparameters (usize, default 5), ExperienceSection in training_profile, and both TOML configs (smoketest=3, production=5). Wired through apply_to() so TOML overrides land correctly. Co-Authored-By: Claude Sonnet 4.6 --- config/training/dqn-production.toml | 1 + config/training/dqn-smoketest.toml | 1 + crates/ml/src/trainers/dqn/config.rs | 6 ++++++ crates/ml/src/training_profile.rs | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/config/training/dqn-production.toml b/config/training/dqn-production.toml index 30e7797a0..4b16c5df4 100644 --- a/config/training/dqn-production.toml +++ b/config/training/dqn-production.toml @@ -68,6 +68,7 @@ min_loss_improvement_pct = 0.1 [experience] initial_capital = 100000.0 tx_cost_multiplier = 0.0001 +min_hold_bars = 5 [experience.fill_simulation] ioc_fill_prob = 0.85 diff --git a/config/training/dqn-smoketest.toml b/config/training/dqn-smoketest.toml index 33148b004..46f414065 100644 --- a/config/training/dqn-smoketest.toml +++ b/config/training/dqn-smoketest.toml @@ -37,6 +37,7 @@ min_epochs_before_stopping = 5 [experience] gpu_n_episodes = 32 gpu_timesteps_per_episode = 100 +min_hold_bars = 3 [experience.fill_simulation] ioc_fill_prob = 0.85 diff --git a/crates/ml/src/trainers/dqn/config.rs b/crates/ml/src/trainers/dqn/config.rs index 2828d67e4..114a8c6cc 100644 --- a/crates/ml/src/trainers/dqn/config.rs +++ b/crates/ml/src/trainers/dqn/config.rs @@ -904,6 +904,11 @@ pub struct DQNHyperparameters { /// Derived from data pipeline's BarSize when available. pub bars_per_day: f64, + /// Minimum bars to hold a position before allowing exit or reversal. + /// Prevents per-bar churning. Trailing stop can override after this period. + /// Default: 5 (about 5 minutes for 1-min bars). Range: [2, 20]. + pub min_hold_bars: usize, + // P2-B Enhancement: Cash reserve requirement /// Cash reserve requirement as percentage of portfolio value (0-100) /// Default: 0.0 (no reserve, backward compatible) @@ -1374,6 +1379,7 @@ impl DQNHyperparameters { // P2-A Enhancement initial_capital: 100_000.0, // $100K default bars_per_day: 390.0, // 1-minute bars (6.5h × 60min) + min_hold_bars: 5, // P2-B Enhancement cash_reserve_percent: 0.0, // Default: no reserve (backward compatible) diff --git a/crates/ml/src/training_profile.rs b/crates/ml/src/training_profile.rs index 7cb868c3b..635e682f0 100644 --- a/crates/ml/src/training_profile.rs +++ b/crates/ml/src/training_profile.rs @@ -195,6 +195,8 @@ pub struct ExperienceSection { pub initial_capital: Option, /// Maps to `DQNHyperparameters::transaction_cost_multiplier`. pub tx_cost_multiplier: Option, + /// Minimum bars to hold a position before allowing exit or reversal. + pub min_hold_bars: Option, /// Fill simulation sub-section for order routing realism. pub fill_simulation: Option, } @@ -801,6 +803,9 @@ impl DqnTrainingProfile { if let Some(v) = ex.tx_cost_multiplier { hp.transaction_cost_multiplier = v; } + if let Some(v) = ex.min_hold_bars { + hp.min_hold_bars = v; + } // [experience.fill_simulation] if let Some(ref fs) = ex.fill_simulation { if let Some(v) = fs.ioc_fill_prob {