Commit Graph

2670 Commits

Author SHA1 Message Date
jgrusewski
ec257febe4 fix: all 6 issues blocking H100 — train/eval mismatch, budget, dynamic thresholds
1. Backtest hold enforcement: hold_time tracked at portfolio[5], min_hold_bars
   override in backtest_env_step. Train/eval mismatch fixed.
2. Documented evaluator strategy: Layer 2 in env_step, not action masking.
3. Trial budget observer: shared Arc<AtomicUsize> counter — budget enforced.
4. CVaR threshold: 0.05/sqrt(bars_per_day) instead of hardcoded 0.003.
5. MIN_TRADES_DEGENERATE constant, dynamic test vector dimensions.
6. Integration pending — local hyperopt next.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:24:38 +01:00
jgrusewski
9b4395b0df fix: trial budget observer — shared AtomicUsize counter enforces trial limit
increment_trial() was never called — budget enforcement was broken because
TrialBudgetObserver had its own internal Arc<AtomicUsize> disconnected from
the trial_counter used by evaluate_point() and cost(). With num_trials=2,
PSO ran 20 particle evaluations instead of stopping at 2.

Now TrialBudgetObserver::new() takes the caller-owned Arc<AtomicUsize> so
all evaluation paths (LHS via evaluate_point + PSO via cost()) share one
counter. ObjectiveFunction::cost() and ParallelObjectiveFunction::cost()
increment trial_counter directly (fetch_add SeqCst) and check budget inline;
observe_iter() reads the same counter to terminate PSO between iterations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 01:18:19 +01:00
jgrusewski
3b0f22e9e8 feat: hold_time tracking + hold enforcement in backtest_env_step
Backtest portfolio now tracks hold_time at index [5]. min_hold_bars
param enforces hold constraint during evaluation, matching training.
Fixes train/eval mismatch that caused NaN Sharpe → 1e6 penalty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:13:07 +01:00
jgrusewski
94cf330dad docs: final fixes plan — 6 issues blocking H100 deployment
1. Backtest hold_time tracking + enforcement (train/eval mismatch root cause)
2. Document evaluator action masking strategy (Layer 2 in env_step)
3. Fix trial budget observer (shared AtomicUsize counter)
4. Dynamic CVaR threshold (0.05 / sqrt(bars_per_day))
5. Extract MIN_TRADES_DEGENERATE constant + fix stale test vectors
6. Integration test + local hyperopt validation

Root cause chain: broken trial budget → 21 evals → no hold in eval
→ NaN Sharpe → 1e6 penalty → "degenerate" result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:06:28 +01:00
jgrusewski
0d41770958 Merge branch 'worktree-agent-acea52b4' 2026-03-26 00:48:44 +01:00
jgrusewski
cfcec6bc94 fix: pass 3 missing args to experience_action_select in backtest evaluator
The kernel was updated with portfolio_states, min_hold_bars, max_position
params (Task 3 of trade lifecycle fix) but the backtest evaluator's launch
site wasn't updated. It passed 10 args to a 13-param kernel, causing
SIGSEGV from reading garbage for the missing params.

Fix: pass NULL/0/0.0 for the 3 new params (hold enforcement disabled
during evaluation for now — will be enabled in eval cleanup plan).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:47:15 +01:00
jgrusewski
6414f57e34 docs: eval cleanup spec + plan — remove dead 5-action path, fix train/eval mismatch
3 fixes: (1) Delete GpuActionSelector + branching_action_select (zero callers,
hardcoded 5 actions vs production 9), (2) Add hold enforcement to backtest
env_step kernel (same Layer 2 as training), (3) Dynamic trade insufficiency
threshold = max(window_bars / (min_hold * 20), 5).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:43:58 +01:00
jgrusewski
116f04968b feat: major kernel modules load from precompiled cubins — zero runtime nvcc
Replace OnceLock<Ptx> + compile_ptx_for_device() with include_bytes! +
Ptx::from_binary() in 13 Rust files:

- gpu_action_selector.rs (epsilon_greedy)
- gpu_statistics.rs (batch_statistics)
- gpu_training_guard.rs (training_guard)
- signal_adapter.rs (signal_adapter)
- gpu_monitoring.rs (monitoring_reduce)
- gpu_curiosity_trainer.rs (curiosity_training)
- gpu_attention.rs (attention forward + backward)
- gpu_backtest_evaluator.rs (env, metrics, gather, ppo, supervised, dqn)
- gpu_experience_collector.rs (experience, nstep, her_episode)
- gpu_her.rs (her_episode, her_relabel)
- gpu_iql_trainer.rs (iql_value)
- gpu_iqn_head.rs (iqn_dual_head)
- gpu_ppo_collector.rs (ppo_experience)

Remaining runtime compilation: inline utility kernels in gpu_dqn_trainer.rs
(grad_norm, adam, ema, saxpy, zero, etc.) — small kernels that compile in ms.
883/887 tests pass (4 pre-existing failures in data_loader/hyperopt).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:42:39 +01:00
jgrusewski
bf2a7ba56d feat: build.rs precompiles all 25 CUDA kernels at cargo build time
Extend build.rs to compile all 25 .cu kernel files to cubins via nvcc.
Common header prepended to 24 kernels; experience_kernels.cu is standalone.
Total cubin size ~1.1 MB embedded in binary.

- cargo:rerun-if-changed tracks all .cu and .cuh files
- CUDA_COMPUTE_CAP env var selects target arch (sm_86, sm_90)
- Graceful skip when nvcc unavailable (non-CUDA builds)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:25:56 +01:00
jgrusewski
33e5f61685 fix: move reversal P&L booking AFTER hold enforcement — prevents portfolio corruption
The reversal P&L block (Kelly stats, entry_price reset) ran BEFORE the
hold enforcement guard. If hold_time < min_hold_bars cancelled a reversal,
the P&L was already booked for a trade that didn't execute, corrupting
portfolio_states. This caused SIGSEGV in the hyperopt path where the
corrupted state triggered invalid memory access in subsequent kernels.

Fix: moved the reversal P&L block from line 751 to after line 862
(after hold enforcement resolves final reversing_trade value).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:24:31 +01:00
jgrusewski
9f4762d763 refactor: pass state_dim/num_atoms/etc as kernel args instead of #define strings
Update all Rust kernel launch sites to pass the new runtime parameters
added in the previous commit:
- gpu_attention.rs: state_dim, num_heads for forward + backward
- gpu_monitoring.rs: num_actions, order_actions, urgency_actions
- gpu_backtest_evaluator.rs: metrics + PPO forward params
- gpu_dqn_trainer.rs: state_dim for regime_scale kernel
- gpu_iql_trainer.rs: state_dim for forward_loss + backward
- gpu_her.rs: state_dim for relabel kernel
- gpu_iqn_head.rs: state_dim for trunk_forward kernel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:22:20 +01:00
jgrusewski
bcf62eaecd fix: remove kernel printf debug, verify trade count fix works
Trade count dropped from 77% turnover (620K/800K) to 16.5% (527/3200)
with min_hold_bars=3. The hold enforcement is working correctly in the
standard training path. SIGSEGV in hyperopt path is a separate issue
(likely buffer sizing in the hyperopt adapter's experience collector).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:20:49 +01:00
jgrusewski
82b595f633 refactor: convert #define constants to kernel parameters in .cu files
Replace #error guards in common_device_functions.cuh with safe defaults
(STATE_DIM=72, MARKET_DIM=42, PORTFOLIO_DIM=8). Add MAX_STATE_DIM=128
for safe stack array sizing.

Add runtime parameters to kernels that used compile-time #define:
- attention_kernel.cu: state_dim, num_heads
- attention_backward_kernel.cu: state_dim, num_heads
- backtest_forward_ppo_kernel.cu: state_dim, num_actions
- backtest_metrics_kernel.cu: num_actions, order_actions, urgency_actions
- dqn_utility_kernels.cu: state_dim
- her_relabel_kernel.cu: state_dim
- iql_value_kernel.cu: state_dim (all 3 kernel functions)
- iqn_dual_head_kernel.cu: state_dim
- monitoring_kernel.cu: num_actions, order_actions, urgency_actions

This enables single-cubin-per-kernel precompilation — no #define
injection needed at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:18:18 +01:00
jgrusewski
4729549250 fix: C51 loss threshold 50→500 for v_range ±240, fix flaky softmax diversity test
With v_range ±240 (vs old ±2), C51 distributional loss is naturally
~100-400 instead of ~3-6. Updated smoke test assertion accordingly.

Trade lifecycle fix validated: 467 trades (was 620K) with min_hold_bars=3.
PF=1.87, WinRate=49.7%, MaxDD=41.7% — model learns multi-bar swing trading.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:17:09 +01:00
jgrusewski
a2bf5210ec feat: build.rs precompiles epsilon_greedy_kernel.cu — proof of concept for NVRTC elimination
Add build.rs that compiles .cu kernels to cubins at cargo build time via nvcc.
Replace OnceLock<Ptx> + compile_ptx_for_device() in gpu_action_selector.rs with
include_bytes! + Ptx::from_binary() loading from the precompiled cubin.

- build.rs gracefully skips when nvcc is not available (non-CUDA builds)
- CUDA_COMPUTE_CAP env var controls target GPU arch (86=RTX 3050, 90=H100)
- common_device_functions.cuh prepended to kernel sources at build time
- Zero runtime compiler invocations for epsilon_greedy kernel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:07:16 +01:00
jgrusewski
0b743c0f80 docs: plan to eliminate NVRTC — precompile all 25 CUDA kernels at build time
7 tasks: build.rs POC → parameterize #define → update launches → build all
→ replace runtime compilation → delete NVRTC infra → integration test.

Key insight: convert #define STATE_DIM/NUM_ATOMS to kernel params so each
kernel has ONE version regardless of model config. No multi-variant compilation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:58:30 +01:00
jgrusewski
5f2e8930d3 feat: trade lifecycle fix — 3-layer hold enforcement (action masking + kernel override + reward scaling)
4 bugs fixed:
1. Hold guard was dead code (!exiting_trade always false) — replaced with unified guard
2. Reversals bypassed hold — new guard covers exits AND reversals
3. Single-bar trades got full reward — reward scaled by hold_time/min_hold_bars
4. No action masking — experience_action_select now forces current exposure during hold

3 layers of defense:
- Layer 1: Action masking in experience_action_select (masks Q-values + blocks random exploration)
- Layer 2: Kernel override in experience_env_step (catches epsilon exploration slipthrough)
- Layer 3: Reward scaling (smooth gradient: 10% floor to 100% at min_hold_bars)

Config: min_hold_bars added to DQNHyperparameters (default 5), TOML, hyperopt [2,20].
Trailing stop respects min_hold_bars (was hardcoded > 2.0f).
Search space: 46D (was 45D).

Also fixes flaky test_batch_hierarchical_vs_flat_softmax_diversity assertion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:32:15 +01:00
jgrusewski
745bd262cb feat: min_hold_bars in hyperopt search space [2, 20]
Integer parameter (rounded from continuous). Lets PSO discover
optimal hold duration for ES futures regime characteristics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 23:26:42 +01:00
jgrusewski
64925a21ff feat: action masking in experience_action_select — Layer 1 hold enforcement
During min_hold period, forces exposure to current position index.
Covers both greedy (Q-value) and random (epsilon) paths.
Portfolio state already GPU-resident — zero-copy parameter pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 23:17:36 +01:00
jgrusewski
84f8543a5f fix: hold enforcement in experience_env_step — blocks exits AND reversals during min_hold
Layer 2 (safety net): unified hold guard replaces dead code boolean.
Old guard (prev_sign!=0 && curr_sign==0 && !exiting_trade) was always
false. New guard covers both flat exits and reversals.

Layer 3 (gradient): reward scaled by hold_time/min_hold_bars (floor 10%).
Trailing stop respects min_hold_bars (was hardcoded > 2.0f).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 23:13:08 +01:00
jgrusewski
2b85fa2fdf feat: add min_hold_bars config (default 5) — prevents per-bar churning
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 <noreply@anthropic.com>
2026-03-25 23:06:28 +01:00
jgrusewski
d71bf5805e docs: trade lifecycle fix plan — address review feedback
- Fixed struct name: ExperienceCollectorConfig (not GpuExperienceCollectorConfig)
- Fixed max_pos scoping: moved declaration before action_select launch
- Fixed exiting_trade ordering: preliminary variable before trailing stop
- Documented hardcoded 5-action limitation in branching_action_select
- Zero Q-gap during hold periods (conviction meaningless when forced)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:02:39 +01:00
jgrusewski
2197db8472 docs: trade lifecycle fix spec — address review feedback
- Layer 1 now targets experience_action_select (GPU-fused training path)
  AND branching_action_select (backtest/fallback), not just the fallback
- Action masking covers both greedy AND random exploration paths
- Exposure index uses parameterized b0_size, not hardcoded 5 or 9
- Fixed end-of-episode variable name (total_bars, not timesteps_per_episode)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:53:13 +01:00
jgrusewski
1da1b52bcd feat: centralize bars_per_day — no more hardcoded 390.0 scattered everywhere
- Added BARS_PER_DAY, BARS_PER_YEAR, ANNUALIZATION_FACTOR to common::thresholds::time
- Added bars_per_day field to DQNHyperparameters (default 390.0, configurable)
- compute_epoch_financials() now takes bars_per_day parameter from hyperparams
- coordinator_extended.rs + ab_testing.rs use centralized ANNUALIZATION_FACTOR
- When switching to tick data, change one constant in common/thresholds.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:17:20 +01:00
jgrusewski
b3110557df fix: use annualized mean return instead of compounded, cap MaxDD at 100%
Compounding per-bar returns over 10K+ bars still produces extreme numbers
(+1.8M% return, 2687% MaxDD). Replaced with:
- Return: mean_per_bar × bars_per_year (annualized, no compounding)
- MaxDD: windowed over last 10K bars AND capped at 1.0 (100%)

These are monitoring metrics — the hyperopt objective uses the backtest
evaluator which already has correct windowed metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:53 +01:00
jgrusewski
62484e9c59 fix: stop deleting feature cache on every hyperopt run
The feature cache auto-invalidates via content hash — manual rm -f
wasted ~2 minutes of MBP-10 + OFI recomputation per trial start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:03:48 +01:00
jgrusewski
f456dd1220 fix: cap training financials to 10K-bar window (Return/MaxDD were ±trillions%)
Training epoch metrics compound step_returns multiplicatively. With 2M+
bars per epoch on H100, this produces Return=+2.1e24% and MaxDD=99.5% —
meaningless numbers that corrupt risk monitoring and early stopping.

Fixed: total_return and max_drawdown now use only the last 10K bars
(~25 trading days), matching the backtest evaluator's window cap.
Sharpe/Sortino are unaffected (use arithmetic mean/std, not compounding).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:54:38 +01:00
jgrusewski
a5d672806d fix: update smoke tests for 9-action branching DQN (was 5-action)
- smoke_test_real_data: branch sizes vec![5,3,3] → vec![9,3,3], action
  range 0..45 → 0..81 to match 9-exposure-level action space
- liquid.rs: remove unused pred1 variable (clippy warning)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:37:09 +01:00
jgrusewski
83b089928c fix: recalibrate CI tests for wider v_range (±240) and larger gradients
dqn-smoke: Walk-forward validation DQN uses raw price returns (~0.001),
not production reward_scale=10. Set v_min/v_max to ±10 for the small
16-dim 3-action test network (was inheriting ±240 from production default).

dqn-early-stop: Gradient collapse threshold = lr × multiplier must exceed
the actual gradient norm to trigger collapse. With v_range ±240, gradient
norms reach 100-10000 (was ~0.5-2.0 with old ±2.0 range). Updated
multiplier from 1e9 to 1e12 to guarantee threshold (10000) > grad norm.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:30:11 +01:00
jgrusewski
69415f1a97 fix: remove use_noisy reference in train_baseline_rl example
Noisy nets are now always on (mandatory feature since config unification).
Epsilon start always uses the noisy-nets-aware default (0.05).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:11:17 +01:00
jgrusewski
5a35741da3 fix: update CI tests for expanded search space (45D) and wider v_range (±240)
- dqn_action_collapse_fix_test: search space grew from 39D to 45D
  (added c51_warmup, her_ratio, curiosity, cvar, dt_pretrain). Updated
  assertions to use >= 39 and dynamic index lookup for cql_alpha.
- training_stability: Q-value assertion widened from 2.5 to 300.0
  to match v_range ±240 (computed from reward_scale=10, gamma=0.95).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:01:00 +01:00
jgrusewski
b555aafe77 feat: wire MBP-10 + trades data into hyperopt campaign config
Adds mbp10_data_dir and trades_data_dir to CampaignConfig, passed to
DQNTrainer via with_ofi_data_dirs(). Enables OFI features (VPIN,
Kyle's Lambda, etc.) in hyperopt campaigns. dqn_full() now defaults
to 50 epochs/trial for baseline runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 20:38:56 +01:00
jgrusewski
3bd339b5a3 fix: backtest evaluator — correct annualization, window sizing, objective calibration
Root cause: backtest windows of 300K bars produced ±billions% returns via
multiplicative compounding, and sqrt(252) annualization was wrong for
1-minute bars.

Fixes:
- Window size capped to 10K bars (~25 trading days), evenly distributed
  across the full validation set (was clustered in first 6%)
- Annualization: configurable bars_per_day field in GpuBacktestConfig
  (default 390.0 for 1-min), produces sqrt(98280) ≈ 313.5
- tanh normalization recalibrated: Sharpe/5, Sortino/8 (was /2, /3)
- CVaR threshold scaled to per-bar: 0.003 with slope 1400 (was 0.05/200)
- VaR/CVaR strided sampling covers full window (was first 4096 only)
- financials.rs + ab_testing.rs: sqrt(252) → sqrt(98280) for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:43:26 +01:00
jgrusewski
a04cd3d0f8 refactor: extract BARS_PER_YEAR constant in coordinator_extended.rs
Replaces inline (252.0 * 6.5 * 60.0).sqrt() with named constant
matching the pattern used in financials.rs and ab_testing.rs.
2026-03-25 19:37:45 +01:00
jgrusewski
d2751762ee feat: boundary-aware parallel trade counting with exact stitching
Hybrid approach: each of 256 threads exports boundary metadata (first/last
action, prefix/suffix return, interior trade count). Thread 0 stitches
boundaries in O(256) to produce exact trade count and win rate.

Fixes: trades spanning chunk boundaries were fragmented (returns lost,
counts incorrect). Now every trade's cumulative return is tracked exactly
regardless of which thread chunks it spans.

7 boundary values stored in s_sorted[stride..8*stride] — zero extra
shared memory (reuses sort scratch between equity reduction and bitonic sort).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 19:34:40 +01:00
jgrusewski
b6038ca709 refactor: remove 3 dead reduction arrays + per-thread drawdown vars from metrics kernel
s_max_dd, s_trades, s_wins parallel reductions were made redundant by
the exact sequential drawdown scan and upcoming boundary stitching.
Reduces shared memory from 25KB to 22KB and removes 3 dead ops/bar
from the per-thread loop. Trade count/win rate temporarily zeroed —
restored by boundary stitching in next commit.
2026-03-25 19:25:10 +01:00
jgrusewski
fb5d6a57a2 fix: stress tester init non-fatal on small GPUs + hyperopt runs
The stress tester creates a SECOND DQNTrainer for validation, which
OOMs on 4GB GPUs. Changed from fatal error (?) to warning + skip.

Hyperopt now successfully trains on RTX 3050 — 2 trials × 10 epochs
completed in 19 minutes. Backtest metrics still show extreme returns
(multiplicative compounding over 895K bars) — needs separate fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:49:24 +01:00
jgrusewski
80f701f5d4 wip: hyperopt adapter needs update for removed use_ fields + new reward_scale
The DQNTrainer adapter in hyperopt silently returns penalty (1000000)
because build_hyperparams() fails with the cleaned config struct.
Needs: update DQNParams→DQNHyperparameters mapping for removed use_
booleans and new reward_scale field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:44:40 +01:00
jgrusewski
a400288ae0 feat: comprehensive TOML profiles + config consistency test
Task 7: Update all three DQN TOML profiles with every configurable parameter.
- dqn-smoketest: add [distributional], [advanced], [exploration] (noisy_sigma,
  entropy_coefficient, count_bonus), [risk] (max_position, loss_aversion),
  fix learning_rate 0.0003 -> 0.00003, add reward_scale + gamma
- dqn-production: add reward_scale, exploration params (noisy/entropy/count_bonus),
  advanced params (n_steps/tau/c51_warmup/her/iqn_lambda/spectral_norm),
  remove hardcoded v_min/v_max (now computed), remove duplicate tau from [training]
- dqn-hyperopt: add search space bounds for spectral_norm_sigma_max,
  c51_warmup_epochs, her_ratio

Profile system additions (training_profile.rs):
- TrainingSection: add reward_scale (recomputes v_min/v_max on apply)
- ExplorationSection: add noisy_sigma_init, entropy_coefficient,
  count_bonus_coefficient, q_gap_threshold
- AdvancedSection: add n_steps, tau, c51_warmup_epochs, her_ratio,
  iqn_lambda, spectral_norm_sigma_max, gradient_clip_norm
- RiskSection: add max_position (alias for max_position_absolute), loss_aversion
- RewardSection: add reward_scale
- SearchSpaceSection: add spectral_norm_sigma_max, c51_warmup_epochs, her_ratio
- apply_to: gamma change now recomputes v_min/v_max automatically

Task 8: Config consistency integration tests (5 tests):
- test_config_consistency_across_structs: v_range computed not hardcoded,
  fill simulation bounds, q_clip symmetry
- test_toml_profile_applies_all_fields: smoketest profile applies every
  new section field correctly
- test_production_profile_applies_all_sections: production profile end-to-end
- test_hyperopt_search_space_has_new_bounds: new search space fields parse
- test_reward_scale_recomputes_v_range: gamma override triggers v_range recomputation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:27:45 +01:00
jgrusewski
8777288880 feat: v_range computed from reward_scale + gamma — config flows HP → DQNConfig → GpuConfig
Task 4: Add `reward_scale` field (default 10.0) to DQNHyperparameters with
`computed_v_min()`/`computed_v_max()` methods. Formula:
v_range = (reward_scale / (1 - gamma) * 1.2).clamp(20, 300).
conservative() now computes v_min/v_max = +-240 (was hardcoded +-50).
Hyperopt adapter uses same formula instead of hardcoded max_abs_reward.

Task 5: Verified DQNConfig receives v_min/v_max from DQNHyperparameters
in constructor.rs (lines 285-286). Chain intact.

Task 6: Verified GpuDqnTrainConfig receives v_min/v_max from DQNConfig
in fused_training.rs (lines 169-170). Chain intact.

All Default impls updated: DQNConfig, GpuDqnTrainConfig,
ExperienceCollectorConfig, DqnBacktestConfig — zero hardcoded v_min/v_max.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:16:26 +01:00
jgrusewski
e8f54c37c1 feat: expose experience collector params in TOML training profiles
Add 7 new fields to DQNHyperparameters (fill_ioc_fill_prob,
fill_limit_fill_min, fill_limit_fill_max, fill_spread_cost_frac,
fill_spread_capture_frac, q_clip_min, q_clip_max) and wire them
through training_profile.rs into ExperienceCollectorConfig construction
in training_loop.rs. Previously these 7 values were hardcoded at the
construction site; now they flow from TOML [experience.fill_simulation]
and [risk] sections. Default values match the prior hardcoded constants
so existing behavior is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:05:01 +01:00
jgrusewski
1a51e67ba0 fix: unify Default impls — all config structs match conservative() values
DQNConfig::default(): learning_rate 1e-4→3e-5, gamma 0.99→0.95,
batch_size 64→1024, v_min/v_max -25/25→-50/50, q_clip -100/100→-200/200.

DQNConfig::conservative(): learning_rate 1e-4→3e-5, gamma 0.99→0.95,
batch_size 32→1024, v_min/v_max -25/25→-50/50, q_clip -500/500→-200/200.

DQNConfig::emergency_safe_defaults(): v_min/v_max -25/25→-50/50,
q_clip -500/500→-200/200.

GpuDqnTrainConfig::default(): state_dim 72→48, v_min/v_max -2/2→-50/50,
lr 3e-4→3e-5, weight_decay 1e-5→1e-4, batch_size 256→64.

ExperienceCollectorConfig::default(): use_noisy_nets false→true,
use_distributional false→true, num_atoms 1→51,
v_min/v_max -2/2→-50/50, q_clip -500/500→-200/200.

DqnBacktestConfig::from_network_dims(): v_min/v_max -2/2→-50/50.
GpuExperienceCollector constructor: v_min/v_max -2/2→-50/50.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:57:04 +01:00
jgrusewski
04d8802c94 refactor: remove 8 always-on use_ booleans — features are mandatory
Remove use_double_dqn, use_dueling, use_per, use_branching,
use_distributional, use_noisy_nets, use_huber_loss, and use_cql
from DQNConfig, DQNHyperparameters, and DqnParams structs.

These features are always enabled (Rainbow DQN standard). The boolean
flags were dead code — every constructor set them to true, and the
only code paths that set them to false were in tests that disabled
features for simplicity. With the fields removed, the features are
unconditionally active, eliminating ~490 lines of dead configuration.

Key changes:
- Struct field declarations removed from 3 core config structs
- Conditional branches (if use_X { ... } else { ... }) simplified:
  dueling/branching/PER network creation is now unconditional
- Checkpoint metadata hardcodes "true" for backward compatibility
- Hyperopt search space index 11 (use_branching) fixed at 1.0
- TOML/YAML config files cleaned of removed fields
- Tests that toggled these flags updated or rewritten

45 files changed, -487 net lines. Zero new test failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:45:54 +01:00
jgrusewski
70a9499f33 docs: config unification plan — single source of truth, 8 tasks 2026-03-25 09:01:12 +01:00
jgrusewski
eceaf45b11 feat: --full mode for 3-phase hyperopt pipeline (BC → RL → Refinement)
- CampaignMode enum: Quick, Standard, Full
- dqn_full() constructor: 20 trials × 100 epochs (covers all 3 phases)
- fxt tune start --full flag: auto-sets 20 trials, 100 epochs
- Phase 1 (BC): MSE warmup + expert demos + DT pretrain
- Phase 2 (RL): all 25 features, C51 ramp, HER
- Phase 3 (Refine): pure C51, shrink-and-perturb

Usage: fxt tune start --model dqn --full --gpu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:44:41 +01:00
jgrusewski
704ee72412 Revert "fix: stop clearing feature cache on every Argo run — auto-invalidates via content hash"
This reverts commit eab24bd288.
2026-03-25 08:40:10 +01:00
jgrusewski
eab24bd288 fix: stop clearing feature cache on every Argo run — auto-invalidates via content hash 2026-03-25 08:37:59 +01:00
jgrusewski
5e7cb5d9ff fix: backtest metrics — multiplicative compounding, chunked drawdown, per-trade win rate
6 bugs in backtest_metrics_kernel.cu:
1. Additive return accumulation → multiplicative (equity *= 1+r)
2. Absolute drawdown → fractional ((peak-current)/peak)
3. Total return from additive sum → from compounded equity
4. Strided bar processing → consecutive chunks (correct drawdown)
5. (Sharpe unchanged — arithmetic mean is correct)
6. Per-bar win count → per-trade win tracking

Before: Return=17975%, MaxDD=100% (impossible). After: honest metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:09:56 +01:00
jgrusewski
480b07864e fix: backtest metrics — multiplicative compounding, chunked drawdown, per-trade win rate
Five bugs in the GPU backtest metrics kernel produced impossible results
(17,975% return, 100% MaxDD on every trial):

1. Additive return accumulation (local_cum += r) replaced with
   multiplicative compounding (local_cum *= 1+r, init 1.0)
2. Absolute drawdown (peak - current) replaced with fractional
   drawdown ((peak - current) / peak)
3. Strided bar processing (thread sees every Nth bar) replaced with
   consecutive chunked processing for correct drawdown tracking
4. Per-bar win counting replaced with per-trade win/loss tracking
5. Total return now computed via multiplicative equity product
   reduction across threads (s_sorted[0] - 1.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:07:56 +01:00
jgrusewski
07545a3df3 docs: reward v6 fixes plan 2026-03-25 01:49:53 +01:00