Commit Graph

2679 Commits

Author SHA1 Message Date
jgrusewski
d485dde5a2 fix: eliminate NVRTC, fix evaluator SIGSEGV, 22D search space, trade physics
Major changes:
- Eliminate ALL runtime NVRTC: c51_loss + mse_loss kernels converted to
  precompiled cubins with runtime num_atoms/v_min/v_max/branch params
- Fix evaluator SIGSEGV: rng_states/q_gaps sized for chunked batch (cn)
  not n_windows; NULL pointer guard in action_select kernel
- Add trade_physics.cuh shared header for train/eval consistency
- Add equity circuit breaker (25% DD from peak) + margin-aware position cap
- Consolidate 46D→22D hyperopt search space, enable ensemble by default
- Fix trade counting: use exposure index not factored action
- Fix Calmar overflow: clamp to ±100 in kernel
- Softer CVaR penalty (cap 3.0 not 10.0) for undertrained models
- Fix win_rate display (ratio→percentage)
- Remove dead code (normalize_reward, calculate_completion_penalty)
- Add tracing subscriber to hyperopt test for visible metrics
- Per-chunk sync in evaluator for reliable error reporting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:33:05 +01:00
jgrusewski
65cc0175ac fix: attention kernel shared memory underallocation — hardcoded [128] vs dynamic d
Forward kernel has shared_concat[128] (512 bytes) but Rust allocated d*4
bytes. With d=72: 288 < 512 → shared memory overflow. Same for backward
(concat[128] + d_proj[128] = 1024 bytes vs d*8).

Fix: use d.max(128) for shared memory sizing.
Also: revert c51/mse to runtime NVRTC (NUM_ATOMS controls loop bounds).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 08:39:09 +01:00
jgrusewski
719d3b2367 fix: revert c51/mse to runtime NVRTC — NUM_ATOMS controls loop bounds, not just sizing
c51_loss and mse_loss kernels use NUM_ATOMS for loop bounds AND shared
memory. Precompiling with NUM_ATOMS=51 then running with num_atoms=101
causes wrong loop iterations. These 2 kernels MUST use runtime #define
injection to match the hyperopt config.

35 of 37 kernels remain precompiled. Only c51_loss and mse_loss use
runtime NVRTC (they're the only kernels where a #define controls logic).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 08:25:38 +01:00
jgrusewski
9f63193351 fix: NUM_ATOMS 51→201 in c51/mse kernels — array overflow when num_atoms=101
Same class of bug as IQN_HIDDEN: compile-time array float local_proj[51]
overflows when hyperopt picks num_atoms=101. Crashed at ~35 min when
PSO explored larger atom counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 08:19:19 +01:00
jgrusewski
500d8f18cd fix: IQN_HIDDEN 256→2048 — register array overflow caused SIGSEGV in hyperopt
Root cause (found via Zen debug + Gemini 2.5 Pro):
IQN_DIST_MAX = (IQN_HIDDEN + 31) / 32 = 8 (from IQN_HIDDEN=256).
Register arrays h_dist[8], embed_dist[8], comb_dist[8] overflow when
hyperopt picks hidden_dim > 256. With hidden_dim=512: loops iterate
16 times on 8-element arrays → stack corruption → SIGSEGV.

Fix: IQN_HIDDEN=2048 → IQN_DIST_MAX=64. Runtime loops still use
actual hidden_dim param. Only register array sizing affected — no
perf impact (unused elements are never accessed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:27:09 +01:00
jgrusewski
f9e55a45d7 fix: parameterize IQN_EMBED_DIM in all 9 IQN CUDA kernels
IQN_EMBED_DIM (cosine embedding dimension, default 64) converted from
compile-time #define to runtime kernel parameter `embed_dim`. This was
the last remaining hardcoded dimension in the precompiled IQN cubin
that could diverge from GpuIqnConfig.embed_dim at runtime, causing
SIGSEGV from out-of-bounds weight offset computation.

All 9 IQN kernels (forward_loss, backward, grad_norm, adam, forward,
trunk_forward, ema, decode_actions, sample_taus) now receive embed_dim
as the final parameter. The iqn_compute_offsets() device helper uses
the runtime embed_dim for W_EMBED→B_EMBED offset calculation.

The other 7 kernel files in the task spec were verified safe:
- c51/mse_loss_kernel.cu: NVRTC-compiled with config values injected
- attention/attention_backward: already parameterized (state_dim, num_heads)
- dt_kernels.cu: NVRTC-compiled with config values injected
- curiosity/ppo: architecturally constant values matching Rust constants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 02:54:19 +01:00
jgrusewski
4ab8ca0162 fix: parameterize IQN_SHARED_H1 + IQN_HIDDEN in all 9 IQN kernels
Build-time #define was 256 but runtime config can be 128 (hidden_dim_base).
Caused CUDA_ERROR_ILLEGAL_ADDRESS in IQN trunk gradient. Now passed as
kernel params matching the STATE_DIM parameterization pattern.

Also: warn→error for training failures in optimizer, eprintln for debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:55:52 +01:00
jgrusewski
232a1f0301 fix: parameterize IQN_SHARED_H1 + IQN_HIDDEN in all 9 IQN kernels
Build-time #define was 256 but runtime config can be 128 (hidden_dim_base).
Caused CUDA_ERROR_ILLEGAL_ADDRESS in IQN trunk gradient. Now passed as
kernel params — matches the pattern used for STATE_DIM parameterization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:55:15 +01:00
jgrusewski
8e6918ab26 feat: add release-test profile — 4x faster compile for GPU smoke tests
thin LTO + 16 codegen units + opt-level 3. Tests need unwind (not abort).
Usage: cargo test --profile release-test -p ml --lib -- test_name --ignored

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:30:47 +01:00
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