Files
DOLPHIN/nautilus_dolphin/test_mc_system.py

432 lines
13 KiB
Python
Raw Normal View History

"""
Monte Carlo System Test Suite
=============================
Comprehensive tests for the MC envelope mapping system.
Run with:
python test_mc_system.py
Tests:
1. Sampler functionality
2. Validator constraint groups
3. Metrics computation
4. Store persistence
5. Executor (simulated mode)
6. Runner orchestration
7. ML training (optional)
"""
import sys
import os
import json
import tempfile
from pathlib import Path
# Add parent to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
sys.path.insert(0, str(Path(__file__).parent))
import numpy as np
def test_sampler():
"""Test MCSampler."""
print("\n" + "="*70)
print("TEST 1: MCSampler")
print("="*70)
from nautilus_dolphin.mc import MCSampler, MCTrialConfig
sampler = MCSampler(base_seed=42)
# Test switch vector generation
switches = sampler.generate_switch_vectors()
print(f" Switch vectors: {len(switches)}")
assert 50 < len(switches) < 150, "Unexpected number of switch vectors"
# Test trial generation (small)
trials = sampler.generate_trials(n_samples_per_switch=5, max_trials=50)
print(f" Generated trials: {len(trials)}")
assert len(trials) <= 50, "Too many trials generated"
# Test champion generation
champion = sampler.generate_champion_trial()
assert champion.trial_id == -1, "Champion should have trial_id=-1"
assert champion.vel_div_threshold == -0.02, "Champion threshold mismatch"
# Test parameter ranges
for trial in trials[:10]:
assert -0.040 <= trial.vel_div_threshold <= -0.008, "vel_div_threshold out of range"
assert 0.10 <= trial.min_leverage <= 1.50, "min_leverage out of range"
assert 1.50 <= trial.max_leverage <= 12.00, "max_leverage out of range"
assert 0.05 <= trial.fraction <= 0.40, "fraction out of range"
print(" [PASS] Sampler tests passed")
return True
def test_validator():
"""Test MCValidator."""
print("\n" + "="*70)
print("TEST 2: MCValidator")
print("="*70)
from nautilus_dolphin.mc import MCSampler, MCValidator, ValidationStatus
sampler = MCSampler()
validator = MCValidator(verbose=False)
# Generate some trials
trials = sampler.generate_trials(n_samples_per_switch=3, max_trials=30)
# Validate
results = validator.validate_batch(trials)
# Stats
stats = validator.get_validity_stats(results)
print(f" Total: {stats['total']}")
print(f" Valid: {stats['valid']} ({stats['validity_rate']*100:.1f}%)")
# Most should be valid or rejected for specific reasons
assert stats['total'] == len(trials), "Stats total mismatch"
# Test specific constraints
# Create an invalid config
invalid_config = trials[0]._replace(
vel_div_extreme=-0.01, # Not extreme enough
vel_div_threshold=-0.015 # Should fail: extreme >= threshold
)
result = validator.validate(invalid_config)
assert not result.is_valid(), "Should reject invalid VD config"
print(f" Invalid config rejected: {result.reject_reason}")
# Test leverage constraint
invalid_config2 = trials[0]._replace(
min_leverage=5.0,
max_leverage=3.0 # Should fail: min >= max
)
result2 = validator.validate(invalid_config2)
assert not result2.is_valid(), "Should reject invalid leverage config"
print(" [PASS] Validator tests passed")
return True
def test_metrics():
"""Test MCMetrics."""
print("\n" + "="*70)
print("TEST 3: MCMetrics")
print("="*70)
from nautilus_dolphin.mc import MCSampler, MCMetrics, MCTrialResult
sampler = MCSampler()
config = sampler.generate_champion_trial()
metrics = MCMetrics(initial_capital=25000.0)
# Create dummy data
trades = [
{'pnl': 100, 'pnl_pct': 0.004, 'exit_type': 'tp', 'bars_held': 50},
{'pnl': -50, 'pnl_pct': -0.002, 'exit_type': 'stop', 'bars_held': 20},
{'pnl': 150, 'pnl_pct': 0.006, 'exit_type': 'tp', 'bars_held': 80},
] * 20
daily_pnls = [50, -20, 80, -10, 100, -30, 60, 40, -15, 90] * 5
date_stats = [{'date': f'2026-01-{i%31+1:02d}', 'pnl': daily_pnls[i]}
for i in range(len(daily_pnls))]
signal_stats = {
'dc_skip_rate': 0.1,
'ob_skip_rate': 0.05,
'dc_confirm_rate': 0.7,
'irp_match_rate': 0.6,
'entry_attempt_rate': 0.3,
'signal_to_trade_rate': 0.15,
}
# Compute metrics
result = metrics.compute(config, trades, daily_pnls, date_stats, signal_stats)
print(f" ROI: {result.roi_pct:.2f}%")
print(f" Profit Factor: {result.profit_factor:.2f}")
print(f" Win Rate: {result.win_rate:.2%}")
print(f" Sharpe: {result.sharpe_ratio:.2f}")
print(f" Max DD: {result.max_drawdown_pct:.2f}%")
print(f" N Trades: {result.n_trades}")
# Verify labels
print(f" Profitable: {result.profitable}")
print(f" Champion Region: {result.champion_region}")
print(f" Catastrophic: {result.catastrophic}")
# Test serialization
result_dict = result.to_dict()
assert 'P_vel_div_threshold' in result_dict, "Config params not in dict"
assert 'M_roi_pct' in result_dict, "Metrics not in dict"
assert 'L_profitable' in result_dict, "Labels not in dict"
print(" [PASS] Metrics tests passed")
return True
def test_store():
"""Test MCStore."""
print("\n" + "="*70)
print("TEST 4: MCStore")
print("="*70)
from nautilus_dolphin.mc import MCStore, MCSampler, MCMetrics, MCTrialResult
from nautilus_dolphin.mc.mc_validator import ValidationResult, ValidationStatus
# Use temp directory
with tempfile.TemporaryDirectory() as tmpdir:
store = MCStore(output_dir=tmpdir, batch_size=10)
# Test validation result storage
val_results = [
ValidationResult(ValidationStatus.VALID, i)
for i in range(5)
]
val_results.append(ValidationResult(
ValidationStatus.REJECTED_V2, 5, "Test rejection"
))
store.save_validation_results(val_results, batch_id=0)
# Test trial result storage
sampler = MCSampler()
config = sampler.generate_champion_trial()
trial_results = []
for i in range(5):
result = MCTrialResult(
trial_id=i,
config=config._replace(trial_id=i),
roi_pct=np.random.uniform(-10, 50),
n_trades=np.random.randint(20, 200),
status='completed'
)
result.compute_labels()
trial_results.append(result)
store.save_trial_results(trial_results, batch_id=1)
# Test query
entries = store.query_index(limit=10)
print(f" Index entries: {len(entries)}")
assert len(entries) == 5, "Index query returned wrong count"
# Test stats
stats = store.get_corpus_stats()
print(f" Corpus stats: {stats}")
assert stats['total_trials'] == 5, "Corpus stats wrong"
print(" [PASS] Store tests passed")
return True
def test_executor():
"""Test MCExecutor (simulated mode)."""
print("\n" + "="*70)
print("TEST 5: MCExecutor (Simulated)")
print("="*70)
from nautilus_dolphin.mc import MCSampler, MCExecutor
sampler = MCSampler()
executor = MCExecutor(initial_capital=25000.0, verbose=False)
# Generate a few trials
trials = sampler.generate_trials(n_samples_per_switch=2, max_trials=5)
# Execute
results = executor.execute_batch(trials, progress_interval=2)
print(f" Executed {len(results)} trials")
for r in results:
print(f" Trial {r.trial_id}: ROI={r.roi_pct:.2f}%, Trades={r.n_trades}, Status={r.status}")
# All should complete
completed = sum(1 for r in results if r.status == 'completed')
print(f" Completed: {completed}/{len(results)}")
assert completed > 0, "No trials completed"
print(" [PASS] Executor tests passed")
return True
def test_runner():
"""Test MCRunner (small scale)."""
print("\n" + "="*70)
print("TEST 6: MCRunner (Small Scale)")
print("="*70)
from nautilus_dolphin.mc import MCRunner
with tempfile.TemporaryDirectory() as tmpdir:
runner = MCRunner(
output_dir=tmpdir,
n_workers=1,
batch_size=5,
base_seed=42,
verbose=False
)
# Run small envelope mapping
stats = runner.run_envelope_mapping(
n_samples_per_switch=2,
max_trials=10,
resume=False
)
print(f" Total trials: {stats['total_trials']}")
print(f" Champion region: {stats['champion_count']}")
print(f" Catastrophic: {stats['catastrophic_count']}")
assert stats['total_trials'] > 0, "No trials completed"
# Test report generation
report = runner.generate_report()
assert 'Corpus Statistics' in report, "Report missing content"
print(" [PASS] Runner tests passed")
return True
def test_cli():
"""Test CLI entry point."""
print("\n" + "="*70)
print("TEST 7: CLI Entry Point")
print("="*70)
from nautilus_dolphin.run_mc_envelope import create_parser, cmd_sample, cmd_report
# Test parser
parser = create_parser()
# Test sample command args
args = parser.parse_args(['--mode', 'sample', '--n-samples', '5', '--max-trials', '10'])
assert args.mode == 'sample'
assert args.n_samples == 5
print(" Parser works")
# Test actual sample command (small)
with tempfile.TemporaryDirectory() as tmpdir:
args.output_dir = tmpdir
args.seed = 42
args.quiet = True
result = cmd_sample(args)
assert result == 0, "Sample command failed"
# Check output
config_path = Path(tmpdir) / "manifests" / "all_configs.json"
assert config_path.exists(), "Config file not created"
with open(config_path) as f:
data = json.load(f)
print(f" Generated {len(data)} configs")
assert len(data) <= 10, "Too many configs generated"
print(" [PASS] CLI tests passed")
return True
def test_integration():
"""Integration test: full pipeline."""
print("\n" + "="*70)
print("TEST 8: Full Integration")
print("="*70)
with tempfile.TemporaryDirectory() as tmpdir:
# Step 1: Generate
from nautilus_dolphin.mc import MCSampler
sampler = MCSampler(base_seed=42)
trials = sampler.generate_trials(n_samples_per_switch=2, max_trials=10)
# Step 2: Validate
from nautilus_dolphin.mc import MCValidator
validator = MCValidator()
valid_trials = [t for t in trials if validator.validate(t).is_valid()]
print(f" Valid trials: {len(valid_trials)}/{len(trials)}")
# Step 3: Execute
from nautilus_dolphin.mc import MCExecutor
executor = MCExecutor(verbose=False)
results = executor.execute_batch(valid_trials[:5])
# Step 4: Store
from nautilus_dolphin.mc import MCStore
store = MCStore(output_dir=tmpdir)
store.save_trial_results(results, batch_id=1)
# Step 5: Query
stats = store.get_corpus_stats()
print(f" Stored trials: {stats['total_trials']}")
assert stats['total_trials'] > 0, "No trials stored"
print(" [PASS] Integration test passed")
return True
def run_all_tests():
"""Run all tests."""
print("\n" + "="*70)
print("MONTE CARLO SYSTEM TEST SUITE")
print("="*70)
tests = [
("Sampler", test_sampler),
("Validator", test_validator),
("Metrics", test_metrics),
("Store", test_store),
("Executor", test_executor),
("Runner", test_runner),
("CLI", test_cli),
("Integration", test_integration),
]
passed = 0
failed = 0
for name, test_func in tests:
try:
if test_func():
passed += 1
else:
failed += 1
print(f" [FAIL] {name} test failed")
except Exception as e:
failed += 1
print(f" [FAIL] {name} test failed with exception: {e}")
import traceback
traceback.print_exc()
print("\n" + "="*70)
print("TEST SUMMARY")
print("="*70)
print(f"Passed: {passed}/{len(tests)}")
print(f"Failed: {failed}/{len(tests)}")
if failed == 0:
print("\n[OK] ALL TESTS PASSED!")
return 0
else:
print("\n[FAIL] SOME TESTS FAILED")
return 1
if __name__ == "__main__":
sys.exit(run_all_tests())