Files
foxhunt/ml/tests/cash_accounting_fix_test.rs
jgrusewski f5947c2b22 Wave 16S-V11: Bug #8 fix + P2-A/B implementation
Bug #8 (CRITICAL): Fixed action selection frequency catastrophe
- Root cause: execute_action called during training (522,713 orders/epoch)
- Fix: Removed execute_action from experience collection loop (line 928-936)
- Impact: 522,713 → 0 orders/epoch (100% reduction)
- Transaction costs: $338K → $0 (eliminated)
- Test suite: ml/tests/action_selection_frequency_test.rs (3/3 passing)

P2-A: Configurable Initial Capital
- CLI argument: --initial-capital (default: $100K, min: $1K)
- Files modified: trainers/dqn.rs, train_dqn.rs, hyperopt adapter
- Test suite: ml/tests/configurable_capital_test.rs (8/8 passing)
- Supports: Small accounts ($10K), Standard ($100K), Institutional ($500K+)

P2-B: Cash Reserve Requirement
- CLI argument: --cash-reserve-percent (default: 0%, range: 0-100%)
- Reserve enforcement: BUY trades only (SELL always allowed)
- Dynamic reserve adjusts with portfolio value
- Files modified: portfolio_tracker.rs (70 lines), trainers/dqn.rs, train_dqn.rs
- Test suite: ml/tests/cash_reserve_requirement_test.rs (10/10 passing)

Test Status: 21/21 core tests passing (P2-C deferred due to API mismatch)

Wave 16S-V11 Agents:
- Agent #1: Bug #8 investigation (transaction cost analysis)
- Agent #2: P2-A implementation (configurable capital)
- Agent #3: P2-B implementation + test fix (cash reserve)
- Agent #4: Integration validation (certification report)
2025-11-12 23:05:51 +01:00

107 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#[cfg(test)]
mod cash_accounting_tests {
use ml::dqn::portfolio_tracker::PortfolioTracker;
use ml::dqn::action_space::{FactoredAction, ExposureLevel, OrderType, Urgency};
#[test]
fn test_buy_long_decreases_cash() {
let mut tracker = PortfolioTracker::new(100_000.0, 0.0001, 1.0);
let initial_cash = tracker.cash_balance();
// Buy 1 contract at $5,600 (go from 0 to +1 position)
let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
tracker.execute_action(action, 5600.0, 1.0);
let final_cash = tracker.cash_balance();
// Cash should DECREASE when buying
assert!(final_cash < initial_cash,
"Cash should decrease when buying. Initial: ${:.2}, Final: ${:.2}",
initial_cash, final_cash);
// Should be approximately -$5,608.40 (price + 0.15% market fee)
let expected_decrease = 5600.0 + (5600.0 * 0.0015);
let actual_decrease = initial_cash - final_cash;
assert!((actual_decrease - expected_decrease).abs() < 1.0,
"Expected decrease: ${:.2}, Actual: ${:.2}",
expected_decrease, actual_decrease);
}
#[test]
fn test_sell_short_increases_cash() {
let mut tracker = PortfolioTracker::new(100_000.0, 0.0001, 1.0);
let initial_cash = tracker.cash_balance();
// Sell 1 contract at $5,600 (go from 0 to -1 position)
let action = FactoredAction::new(ExposureLevel::Short100, OrderType::Market, Urgency::Normal);
tracker.execute_action(action, 5600.0, 1.0);
let final_cash = tracker.cash_balance();
// Cash should INCREASE when selling short
assert!(final_cash > initial_cash,
"Cash should increase when selling short. Initial: ${:.2}, Final: ${:.2}",
initial_cash, final_cash);
// Should be approximately +$5,591.60 (price - 0.15% market fee)
let expected_increase = 5600.0 - (5600.0 * 0.0015);
let actual_increase = final_cash - initial_cash;
assert!((actual_increase - expected_increase).abs() < 1.0,
"Expected increase: ${:.2}, Actual: ${:.2}",
expected_increase, actual_increase);
}
#[test]
fn test_close_long_increases_cash() {
let mut tracker = PortfolioTracker::new(100_000.0, 0.0001, 1.0);
// First, buy 1 contract
let buy_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
tracker.execute_action(buy_action, 5600.0, 1.0);
let cash_after_buy = tracker.cash_balance();
// Now close the position (go from +1 to 0)
let close_action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
tracker.execute_action(close_action, 5650.0, 1.0); // Price increased
let final_cash = tracker.cash_balance();
// Cash should increase when closing long position
assert!(final_cash > cash_after_buy,
"Cash should increase when closing long. After buy: ${:.2}, After close: ${:.2}",
cash_after_buy, final_cash);
}
#[test]
fn test_no_free_money_exploit() {
let mut tracker = PortfolioTracker::new(100_000.0, 0.0001, 1.0);
let initial_portfolio = tracker.total_value(5600.0);
// Execute 10 round-trip trades at same price
for _ in 0..10 {
// Buy
let buy = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
tracker.execute_action(buy, 5600.0, 1.0);
// Sell
let sell = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
tracker.execute_action(sell, 5600.0, 1.0);
}
let final_portfolio = tracker.total_value(5600.0);
// Portfolio should DECREASE due to transaction costs, not increase
assert!(final_portfolio < initial_portfolio,
"Portfolio should lose money from transaction costs, not gain. Initial: ${:.2}, Final: ${:.2}",
initial_portfolio, final_portfolio);
// Should lose approximately 20 × (5600 × 0.0015) = $168 in fees
let expected_loss = 20.0 * 5600.0 * 0.0015;
let actual_loss = initial_portfolio - final_portfolio;
assert!((actual_loss - expected_loss).abs() < 10.0,
"Expected loss: ${:.2}, Actual loss: ${:.2}",
expected_loss, actual_loss);
}
}