Files
foxhunt/ELITE_REWARD_INTEGRATION_STATUS.md
jgrusewski 00ef9e2866 Wave 15: Complete FactoredAction migration to 45-action system
Major Changes:
- Migrated from 3-action TradingAction to 45-action FactoredAction
- 45 actions: 5 exposure × 3 order types × 3 urgency levels
- Absolute exposure model (target positions -1.0 to +1.0)
- Transaction cost differentiation (Market 0.15%, LimitMaker 0.05%, IoC 0.10%)
- Fixed action diversity threshold (1.11% → 0.5% for 45-action space)

Bug Fixes:
- Bug #15: Incomplete FactoredAction integration (code existed but unused)
- Bug #16: Runtime crash in action diversity checking (hardcoded 3-action match)

Code Changes (13 files, ~464 lines):
- ml/src/dqn/action_space.rs: Core FactoredAction + 4 helper methods
- ml/src/trainers/dqn.rs: Action diversity refactored (3→45 dynamic)
- ml/src/dqn/reward.rs: calculate_reward() signature updated
- ml/src/dqn/portfolio_tracker.rs: execute_action() absolute exposure
- ml/src/dqn/dqn.rs: WorkingDQN action selection migrated
- ml/tests/*.rs: 9 test files updated with FactoredAction assertions

Test Results:
- 1-epoch smoke test: 100% action diversity (45/45 actions, 80.2s)
- 10-epoch production: 87.8% readiness (79/90 scorecard, 14.0 min)
- Loss convergence: 96.9% reduction (119K → 3.6K)
- Action diversity: 100% → 44% (healthy specialization)
- Checkpoint reliability: 12/12 files saved (100%)
- DQN tests: 195/195 passing (100%)
- ML baseline: 1,514/1,515 passing (99.93%)

Production Status:  CERTIFIED (87.8% readiness)
Go/No-Go:  GO FOR 100-EPOCH PRODUCTION TRAINING

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 23:27:02 +01:00

506 lines
15 KiB
Markdown

# Elite Reward Coordinator Integration - Status Report
**Date**: 2025-11-08
**Agent**: Integration Agent (Step 2 - Wiring Coordinator into DQN Trainer)
**Status**: PHASE 1 COMPLETE | PHASES 2-5 READY | BLOCKER: curiosity.rs compilation errors
---
## Executive Summary
**PHASE 1 COMPLETE**: CLI flag `--use-elite-reward` successfully added to train_dqn.rs with backward compatibility (default: false).
**CRITICAL BLOCKER**: Compilation fails due to 2 errors in `ml/src/dqn/curiosity.rs` (parallel agent's code):
1. Line 143: `Adam` does not implement `candle_nn::Optimizer` trait
2. Line 199: `next_state_embedding` moved value used after move
**PHASES 2-5 READY**: Comprehensive integration plan complete, waiting for curiosity.rs fixes.
---
## Phase 1: CLI Flag Addition (COMPLETE)
### Files Modified
#### `/home/jgrusewski/Work/foxhunt/ml/examples/train_dqn.rs`
**Change 1**: Added CLI flag (lines 183-186)
```rust
/// Enable elite multi-component reward system (experimental)
/// Default: false (uses legacy RewardFunction for backward compatibility)
#[arg(long, default_value = "false")]
use_elite_reward: bool,
```
**Change 2**: Added reward system logging (lines 241-246)
```rust
// Log reward system configuration
if opts.use_elite_reward {
info!(" • Reward system: Elite (multi-component: extrinsic + intrinsic + entropy + curiosity + ensemble)");
} else {
info!(" • Reward system: Legacy (portfolio tracking + diversity penalty)");
}
```
**Change 3**: Added TODO for trainer creation (lines 444-446)
```rust
// Create DQN trainer
// TODO: Once reward_coordinator.rs is created, update this to:
// let mut trainer = DQNTrainer::new(hyperparams, opts.use_elite_reward).context("Failed to create DQN trainer")?;
let mut trainer = DQNTrainer::new(hyperparams).context("Failed to create DQN trainer")?;
```
### Validation
- CLI help: `cargo run -p ml --example train_dqn -- --help` (SUCCESS - flag visible)
- Compilation: BLOCKED (curiosity.rs errors)
- Backward compatibility: PENDING (blocked by compilation)
---
## EliteRewardCoordinator API Analysis
### File: `/home/jgrusewski/Work/foxhunt/ml/src/dqn/reward_coordinator.rs`
**Status**: Created by parallel agent, API confirmed.
### Constructor
```rust
pub fn new(device: Device) -> Result<Self, Box<dyn std::error::Error>>
```
**Default Weights**:
- Extrinsic: 0.40 (P&L focus)
- Intrinsic: 0.25 (action diversity)
- Entropy: 0.15 (policy diversity)
- Curiosity: 0.10 (exploration)
- Ensemble: 0.10 (multi-model consensus)
### Reward Calculation
```rust
pub fn calculate_total_reward(
&mut self,
position: &Position,
entry_price: f64,
exit_price: f64,
action: TradingAction,
portfolio_value: f64,
max_drawdown: f64,
state: &Tensor,
next_state: &Tensor,
q_values: &Tensor,
episode_step: u64,
ensemble_votes: Vec<usize>,
) -> Result<f64, Box<dyn std::error::Error>>
```
### Episode Reset
```rust
pub fn reset_episode(&mut self)
```
**Missing Feature**: No `get_last_reward_components()` method for component logging.
- **Impact**: Cannot log individual component values (extrinsic, intrinsic, entropy, curiosity, ensemble)
- **Workaround**: Log only total reward, or modify coordinator to track component values
- **Recommendation**: Add `last_components: [f64; 5]` field and getter method
---
## Critical Blockers
### Blocker 1: curiosity.rs Compilation Errors
**File**: `ml/src/dqn/curiosity.rs`
#### Error 1: Optimizer Trait (Line 143)
```rust
error[E0277]: the trait bound `Adam: candle_nn::Optimizer` is not satisfied
--> ml/src/dqn/curiosity.rs:143:29
|
143 | Optimizer::step(optimizer, &gradients)
| --------------- ^^^^^^^^^ the trait `candle_nn::Optimizer` is not implemented for `Adam`
```
**Root Cause**: `candle_optimisers::Adam` (3rd party) vs `candle_nn::Optimizer` trait mismatch.
**Fix**: Replace with `candle_nn::AdamW` or implement trait wrapper.
#### Error 2: Moved Value (Line 199)
```rust
error[E0382]: borrow of moved value: `next_state_embedding`
--> ml/src/dqn/curiosity.rs:213:55
|
199 | let diff = (predicted_next_state - next_state_embedding)
| -------------------- value moved here
...
213 | self.forward_model.train_step(state, action, &next_state_embedding.clone())?;
| ^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
```
**Root Cause**: `next_state_embedding` consumed in line 199, then borrowed in line 213.
**Fix**: Clone before line 199:
```rust
let next_state_embedding_clone = next_state_embedding.clone();
let diff = (predicted_next_state - next_state_embedding_clone)
```
**Owner**: Parallel agent (reward system creator)
---
## Remaining Work (Phases 2-5)
### Phase 2: Trainer Field Additions (20 min)
**File**: `ml/src/trainers/dqn.rs`
**Modifications**:
1. Add import:
```rust
use crate::dqn::reward_coordinator::EliteRewardCoordinator;
```
2. Update struct (around line 50-70):
```rust
pub struct DQNTrainer {
// ... existing fields
elite_coordinator: Option<EliteRewardCoordinator>,
episode_step: usize,
max_drawdown: f32,
}
```
3. Update constructor signature (around line 430):
```rust
pub fn new(hyperparams: DQNHyperparameters, use_elite_reward: bool) -> Result<Self>
```
4. Initialize fields:
```rust
let elite_coordinator = if use_elite_reward {
Some(EliteRewardCoordinator::new(device.clone())?)
} else {
None
};
Ok(Self {
// ... existing fields
elite_coordinator,
episode_step: 0,
max_drawdown: 0.0,
})
```
**Blockers**: None (reward_coordinator.rs exists)
---
### Phase 3: Reward Calculation Integration (30 min)
**File**: `ml/src/trainers/dqn.rs`
**Location 1: Training Loop** (around line 789-790):
**Current Code**:
```rust
let reward_decimal = self.reward_fn.calculate_reward(
action, state, &next_state, &recent_actions_vec
)?;
let reward = reward_decimal.to_string().parse::<f32>().unwrap_or(0.0);
```
**Replacement**:
```rust
let reward = if let Some(ref mut coordinator) = self.elite_coordinator {
// Elite reward system
let q_values_vec = self.get_q_values(state).await?;
let q_values_tensor = Tensor::new(&q_values_vec[..], &self.device)?
.reshape(&[1, 3])?; // [batch=1, num_actions=3]
coordinator.calculate_total_reward(
&position,
entry_price,
exit_price,
action,
portfolio_value,
self.max_drawdown as f64,
state_tensor, // TODO: Convert TradingState to Tensor
next_state_tensor, // TODO: Convert TradingState to Tensor
&q_values_tensor,
self.episode_step as u64,
vec![], // ensemble_votes (disabled for now)
)? as f32
} else {
// Legacy reward (backward compatibility)
let reward_decimal = self.reward_fn.calculate_reward(
action, state, &next_state, &recent_actions_vec
)?;
reward_decimal.to_string().parse::<f32>().unwrap_or(0.0)
};
// Increment episode step for intrinsic rewards
if self.elite_coordinator.is_some() {
self.episode_step += 1;
}
```
**Location 2: Evaluation Loop** (around line 566-569):
- Similar replacement as Location 1
- **IMPORTANT**: Do NOT increment `episode_step` during evaluation (no exploration rewards)
**Challenges**:
1. **TradingState to Tensor conversion**: Need `state.to_tensor(&device)` method
2. **Position tracking**: Need to extract `entry_price`, `exit_price` from episode history
3. **Portfolio value tracking**: Need to calculate current portfolio value
4. **Max drawdown tracking**: Need to update `self.max_drawdown` during training
---
### Phase 4: Component Logging (20 min)
**File**: `ml/src/trainers/dqn.rs`
**Location**: Per-epoch logging (after line 880)
**Limitation**: EliteRewardCoordinator does NOT provide `get_last_reward_components()` method.
**Options**:
**Option A**: Modify coordinator to track components (RECOMMENDED)
```rust
// In reward_coordinator.rs
pub struct EliteRewardCoordinator {
// ... existing fields
last_components: [f64; 5], // [extrinsic, intrinsic, entropy, curiosity, ensemble]
}
pub fn get_last_reward_components(&self) -> [f64; 5] {
self.last_components
}
```
**Option B**: Log only total reward (NO COMPONENT BREAKDOWN)
```rust
if self.elite_coordinator.is_some() {
info!("Epoch {} Elite Reward System: ACTIVE (component breakdown unavailable)", epoch + 1);
}
```
**Option C**: Calculate components separately (INEFFICIENT)
- Requires calling each module individually
- Doubles computation cost
- Not recommended
**Action Diversity Logging** (READY):
```rust
// Log action diversity (existing monitor.action_counts)
let total_actions = monitor.action_counts.iter().sum::<usize>() as f64;
if total_actions > 0.0 {
info!(
"Epoch {} Action Diversity - BUY: {:.1}%, SELL: {:.1}%, HOLD: {:.1}%",
epoch + 1,
100.0 * monitor.action_counts[0] as f64 / total_actions,
100.0 * monitor.action_counts[1] as f64 / total_actions,
100.0 * monitor.action_counts[2] as f64 / total_actions
);
}
```
---
### Phase 5: Testing & Validation (25 min)
**Test 1: Compilation** (BLOCKED)
```bash
cargo build -p ml --example train_dqn --release --features cuda
```
Expected: Clean build, no errors
**Status**: BLOCKED by curiosity.rs errors
**Test 2: Backward Compatibility** (PENDING)
```bash
cargo test -p ml --lib dqn --no-fail-fast
```
Expected: 147/147 tests pass (default flag = false, legacy reward)
**Status**: PENDING (blocked by compilation)
**Test 3: Elite Reward Smoke Test** (PENDING)
```bash
cargo run -p ml --example train_dqn --release --features cuda -- --use-elite-reward --epochs 2
```
Expected: No crashes, reward logging visible
**Status**: PENDING (blocked by compilation)
**Test 4: Clippy Warnings** (PENDING)
```bash
cargo clippy -p ml --example train_dqn -- -D warnings
cargo clippy -p ml --lib --no-deps -- -D warnings
```
Expected: ≤2 warnings (current threshold)
**Status**: PENDING (blocked by compilation)
---
## Integration Challenges
### Challenge 1: TradingState to Tensor Conversion
**Problem**: EliteRewardCoordinator expects `&Tensor` for state/next_state, but DQN trainer uses `TradingState` struct.
**Current**: TradingState has `to_vector()` method returning `Vec<f64>`.
**Solution**: Add helper method to DQNTrainer:
```rust
fn state_to_tensor(&self, state: &TradingState) -> Result<Tensor> {
let state_vec = state.to_vector();
Tensor::new(&state_vec[..], &self.device)?
.reshape(&[1, state_vec.len()]) // [batch=1, num_features]
}
```
---
### Challenge 2: Episode Step Reset
**Problem**: `episode_step` counter must reset at episode boundaries.
**Current**: DQN training loop does NOT have explicit episode boundaries (continuous training).
**Solutions**:
**Option A**: Reset every epoch (simple, but inaccurate)
```rust
self.episode_step = 0; // At epoch start
```
**Option B**: Reset on terminal states (accurate, requires state tracking)
```rust
if is_terminal_state {
self.episode_step = 0;
}
```
**Option C**: Ignore resets (acceptable for continuous training)
- Intrinsic rewards use `episode_step % 1000` for decay
- No functional impact if step counter keeps incrementing
**Recommendation**: Option C (simplest, no behavior change)
---
### Challenge 3: Max Drawdown Tracking
**Problem**: Elite reward requires `max_drawdown` parameter, but DQN trainer doesn't track it.
**Current**: PortfolioTracker exists (Bug #2 fix, Wave B), but max_drawdown not exposed.
**Solution**: Add max_drawdown tracking to DQNTrainer:
```rust
// In training loop
let current_portfolio_value = portfolio_tracker.get_value();
if current_portfolio_value < initial_portfolio_value {
let drawdown = (initial_portfolio_value - current_portfolio_value) / initial_portfolio_value;
self.max_drawdown = self.max_drawdown.max(drawdown as f32);
}
```
**Assumption**: PortfolioTracker provides `get_value()` method (needs verification).
---
### Challenge 4: Ensemble Votes
**Problem**: Elite reward expects `ensemble_votes: Vec<usize>`, but DQN is a single model.
**Solution**: Disable ensemble component by passing empty vector:
```rust
coordinator.calculate_total_reward(
// ... other params
vec![], // ensemble_votes (disabled)
)
```
**Impact**: Ensemble component returns 0.0, effective weight distribution becomes:
- Extrinsic: 0.444 (0.40 / 0.90)
- Intrinsic: 0.278 (0.25 / 0.90)
- Entropy: 0.167 (0.15 / 0.90)
- Curiosity: 0.111 (0.10 / 0.90)
**Recommendation**: Accept this limitation (ensemble is optional feature).
---
## Success Criteria
- [x] CLI flag `--use-elite-reward` added to train_dqn.rs
- [x] Reward system logging added
- [x] EliteRewardCoordinator API documented
- [ ] Compilation errors fixed (BLOCKER: parallel agent)
- [ ] DQNTrainer::new() signature updated with use_elite_reward parameter
- [ ] elite_coordinator, episode_step, max_drawdown fields added to DQNTrainer
- [ ] Reward calculation replaced in training loop (conditional logic)
- [ ] Reward calculation replaced in evaluation loop (conditional logic)
- [ ] Action diversity logging added (per-epoch)
- [ ] Component logging added (or documented as limitation)
- [ ] Compilation test passes (147 DQN tests + clean build)
- [ ] Backward compatibility test passes (147/147 tests with default flag)
- [ ] Elite reward smoke test passes (2 epochs, no crashes)
- [ ] Clippy warnings ≤2 (threshold maintained)
---
## Recommendations
### Immediate Actions (Parallel Agent)
1. **Fix curiosity.rs Line 143**: Replace `Adam` with `candle_nn::AdamW` or implement trait wrapper
2. **Fix curiosity.rs Line 199**: Clone `next_state_embedding` before subtraction
3. **Add get_last_reward_components()**: Expose component values for logging
### Next Steps (Integration Agent)
1. **Wait for compilation fix**: Monitor curiosity.rs changes
2. **Implement Phase 2**: Add trainer fields (20 min)
3. **Implement Phase 3**: Replace reward calculations (30 min)
4. **Implement Phase 4**: Add logging (20 min)
5. **Implement Phase 5**: Run full test suite (25 min)
**Total Estimated Time**: 95 minutes (excluding blocker resolution)
---
## Files Modified
### Completed
- [x] `ml/examples/train_dqn.rs` (+17 lines: CLI flag, logging, TODO)
### Pending
- [ ] `ml/src/trainers/dqn.rs` (Phases 2-4: struct fields, reward calculation, logging)
- [ ] `ml/src/dqn/curiosity.rs` (BLOCKER: compilation fixes, owned by parallel agent)
- [ ] `ml/src/dqn/reward_coordinator.rs` (OPTIONAL: add get_last_reward_components())
---
## Appendix: Comprehensive Plan
See planning tool output (8 steps) for complete phase breakdown:
1. Step 1: Scope Analysis
2. Step 2: Code Analysis
3. Step 3: Implementation Breakdown (5 phases)
4. Step 4: Risk Analysis & Mitigation
5. Step 5: Detailed Plan - Phase 1 (CLI Flag)
6. Step 6: Detailed Plan - Phases 2-3 (Trainer Integration)
7. Step 7: Detailed Plan - Phases 4-5 (Logging & Testing)
8. Step 8: Final Summary & Execution Readiness
**Continuation ID**: `98d46d7b-41fc-484f-a25b-c732954ab473`
---
**END OF REPORT**