Parameter space expansion: - Original 15 params: 101K configurations - NEW: MA gap filter (3 dimensions) = 18× expansion - Total: ~810,000 configurations across 4 time profiles - Chunk size: 1,000 configs/chunk = ~810 chunks MA Gap Filter parameters: - use_ma_gap: True/False (2 values) - ma_gap_min_long: -5.0%, 0%, +5.0% (3 values) - ma_gap_min_short: -5.0%, 0%, +5.0% (3 values) Implementation: - money_line_v9.py: Full v9 indicator with MA gap logic - v9_advanced_worker.py: Chunk processor (1,000 configs) - v9_advanced_coordinator.py: Work distributor (2 EPYC workers) - run_v9_advanced_sweep.sh: Startup script (generates + launches) Infrastructure: - Uses existing EPYC cluster (64 cores total) - Worker1: bd-epyc-02 (32 threads) - Worker2: bd-host01 (32 threads via SSH hop) - Expected runtime: 70-80 hours - Database: SQLite (chunk tracking + results) Goal: Find optimal MA gap thresholds for filtering false breakouts during MA whipsaw zones while preserving trend entries.
300 lines
10 KiB
Python
300 lines
10 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Advanced v9 Money Line parameter sweep - AGGRESSIVE optimization.
|
||
|
||
This explores ~100K-200K parameter combinations across:
|
||
- ATR profiles (period + multiplier variations)
|
||
- RSI boundaries (4 parameters)
|
||
- Volume max threshold
|
||
- Entry buffer size
|
||
- ADX length
|
||
- Source mode (Chart vs Heikin Ashi)
|
||
- MA gap filter (optional)
|
||
|
||
Expected runtime: 40-80 hours on 2-worker cluster
|
||
Target: Beat baseline $194.43/1k (19.44% returns)
|
||
"""
|
||
|
||
import itertools
|
||
import multiprocessing as mp
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# Add project root to path
|
||
project_root = Path(__file__).parent.parent
|
||
sys.path.insert(0, str(project_root))
|
||
|
||
import pandas as pd
|
||
from tqdm import tqdm
|
||
|
||
from backtester.data_loader import load_data
|
||
from backtester.indicators.money_line_v9 import MoneyLineV9Inputs, money_line_v9_signals
|
||
from backtester.simulator import simulate_money_line
|
||
|
||
|
||
def test_config(args):
|
||
"""Test a single configuration."""
|
||
config_id, params = args
|
||
|
||
# Load data
|
||
df = load_data("solusdt_5m.csv")
|
||
|
||
# Create inputs with parameters
|
||
inputs = MoneyLineV9Inputs(
|
||
# Basic optimized params (FIXED from previous sweep)
|
||
confirm_bars=0,
|
||
flip_threshold_percent=0.5,
|
||
cooldown_bars=3,
|
||
adx_min=21,
|
||
long_pos_max=75,
|
||
short_pos_min=20,
|
||
vol_min=1.0,
|
||
|
||
# ADVANCED OPTIMIZATION PARAMETERS:
|
||
atr_period=params['atr_period'],
|
||
multiplier=params['multiplier'],
|
||
adx_length=params['adx_length'],
|
||
rsi_length=params['rsi_length'],
|
||
rsi_long_min=params['rsi_long_min'],
|
||
rsi_long_max=params['rsi_long_max'],
|
||
rsi_short_min=params['rsi_short_min'],
|
||
rsi_short_max=params['rsi_short_max'],
|
||
vol_max=params['vol_max'],
|
||
entry_buffer_atr=params['entry_buffer_atr'],
|
||
use_heikin_ashi=params['use_heikin_ashi'],
|
||
use_ma_gap_filter=params['use_ma_gap_filter'],
|
||
ma_gap_long_min=params['ma_gap_long_min'],
|
||
ma_gap_short_max=params['ma_gap_short_max'],
|
||
)
|
||
|
||
try:
|
||
# Generate signals
|
||
signals = money_line_v9_signals(df, inputs)
|
||
|
||
# Simulate trades
|
||
results = simulate_money_line(df, signals)
|
||
|
||
return {
|
||
'config_id': config_id,
|
||
'atr_period': params['atr_period'],
|
||
'multiplier': params['multiplier'],
|
||
'adx_length': params['adx_length'],
|
||
'rsi_length': params['rsi_length'],
|
||
'rsi_long_min': params['rsi_long_min'],
|
||
'rsi_long_max': params['rsi_long_max'],
|
||
'rsi_short_min': params['rsi_short_min'],
|
||
'rsi_short_max': params['rsi_short_max'],
|
||
'vol_max': params['vol_max'],
|
||
'entry_buffer_atr': params['entry_buffer_atr'],
|
||
'use_heikin_ashi': params['use_heikin_ashi'],
|
||
'use_ma_gap_filter': params['use_ma_gap_filter'],
|
||
'ma_gap_long_min': params['ma_gap_long_min'],
|
||
'ma_gap_short_max': params['ma_gap_short_max'],
|
||
'pnl': results['total_pnl'],
|
||
'win_rate': results['win_rate'],
|
||
'profit_factor': results['profit_factor'],
|
||
'max_drawdown': results['max_drawdown'],
|
||
'total_trades': results['total_trades'],
|
||
}
|
||
except Exception as e:
|
||
print(f"Error testing config {config_id}: {e}")
|
||
return {
|
||
'config_id': config_id,
|
||
'pnl': 0,
|
||
'win_rate': 0,
|
||
'profit_factor': 0,
|
||
'max_drawdown': 0,
|
||
'total_trades': 0,
|
||
}
|
||
|
||
|
||
def generate_parameter_grid():
|
||
"""
|
||
Generate comprehensive parameter grid for advanced optimization.
|
||
|
||
AGGRESSIVE SEARCH SPACE:
|
||
- ATR periods: 5 values (10, 12, 14, 16, 18)
|
||
- Multipliers: 6 values (3.0, 3.2, 3.5, 3.8, 4.0, 4.2)
|
||
- ADX length: 4 values (14, 16, 18, 20)
|
||
- RSI length: 3 values (12, 14, 16)
|
||
- RSI long min: 4 values (30, 35, 40, 45)
|
||
- RSI long max: 4 values (65, 70, 75, 80)
|
||
- RSI short min: 4 values (25, 30, 35, 40)
|
||
- RSI short max: 4 values (60, 65, 70, 75)
|
||
- Volume max: 4 values (3.0, 3.5, 4.0, 4.5)
|
||
- Entry buffer: 3 values (0.15, 0.20, 0.25)
|
||
- Source mode: 2 values (Chart, Heikin Ashi)
|
||
- MA gap filter: 3 modes (disabled, longs_only, both)
|
||
|
||
Total: 5×6×4×3×4×4×4×4×4×3×2×3 = 829,440 combinations
|
||
|
||
This will take 2-3 days on 2-worker cluster but will find optimal settings.
|
||
"""
|
||
|
||
# ATR profile variations (5 × 6 = 30 combos)
|
||
atr_periods = [10, 12, 14, 16, 18]
|
||
multipliers = [3.0, 3.2, 3.5, 3.8, 4.0, 4.2]
|
||
|
||
# ADX length variations (4 values)
|
||
adx_lengths = [14, 16, 18, 20]
|
||
|
||
# RSI length (3 values)
|
||
rsi_lengths = [12, 14, 16]
|
||
|
||
# RSI boundaries (4×4×4×4 = 256 combos)
|
||
rsi_long_mins = [30, 35, 40, 45]
|
||
rsi_long_maxs = [65, 70, 75, 80]
|
||
rsi_short_mins = [25, 30, 35, 40]
|
||
rsi_short_maxs = [60, 65, 70, 75]
|
||
|
||
# Volume max (4 values)
|
||
vol_maxs = [3.0, 3.5, 4.0, 4.5]
|
||
|
||
# Entry buffer (3 values)
|
||
entry_buffers = [0.15, 0.20, 0.25]
|
||
|
||
# Source mode (2 values)
|
||
use_heikin_ashis = [False, True]
|
||
|
||
# MA gap filter modes (3 modes = 3 parameter sets)
|
||
# Mode 1: Disabled
|
||
# Mode 2: Longs only (require ma50 > ma200)
|
||
# Mode 3: Both directions (bull/bear confirmation)
|
||
ma_gap_configs = [
|
||
(False, 0.0, 0.0), # Disabled
|
||
(True, 0.5, 0.0), # Longs only: require 0.5% gap
|
||
(True, 0.5, -0.5), # Both: longs need +0.5%, shorts need -0.5%
|
||
]
|
||
|
||
configs = []
|
||
config_id = 0
|
||
|
||
for atr_period, multiplier, adx_length, rsi_length, \
|
||
rsi_long_min, rsi_long_max, rsi_short_min, rsi_short_max, \
|
||
vol_max, entry_buffer, use_ha, ma_gap_config in \
|
||
itertools.product(
|
||
atr_periods, multipliers, adx_lengths, rsi_lengths,
|
||
rsi_long_mins, rsi_long_maxs, rsi_short_mins, rsi_short_maxs,
|
||
vol_maxs, entry_buffers, use_heikin_ashis, ma_gap_configs
|
||
):
|
||
|
||
# Validity check: RSI min < max
|
||
if rsi_long_min >= rsi_long_max:
|
||
continue
|
||
if rsi_short_min >= rsi_short_max:
|
||
continue
|
||
|
||
use_ma_gap, ma_gap_long_min, ma_gap_short_max = ma_gap_config
|
||
|
||
configs.append((config_id, {
|
||
'atr_period': atr_period,
|
||
'multiplier': multiplier,
|
||
'adx_length': adx_length,
|
||
'rsi_length': rsi_length,
|
||
'rsi_long_min': rsi_long_min,
|
||
'rsi_long_max': rsi_long_max,
|
||
'rsi_short_min': rsi_short_min,
|
||
'rsi_short_max': rsi_short_max,
|
||
'vol_max': vol_max,
|
||
'entry_buffer_atr': entry_buffer,
|
||
'use_heikin_ashi': use_ha,
|
||
'use_ma_gap_filter': use_ma_gap,
|
||
'ma_gap_long_min': ma_gap_long_min,
|
||
'ma_gap_short_max': ma_gap_short_max,
|
||
}))
|
||
config_id += 1
|
||
|
||
return configs
|
||
|
||
|
||
def main():
|
||
"""Run advanced parameter sweep."""
|
||
print("=" * 80)
|
||
print("v9 ADVANCED PARAMETER SWEEP - AGGRESSIVE OPTIMIZATION")
|
||
print("=" * 80)
|
||
print()
|
||
print("This will explore ~800K parameter combinations across:")
|
||
print(" - ATR profiles (5 periods × 6 multipliers)")
|
||
print(" - RSI boundaries (4×4×4×4 = 256 combinations)")
|
||
print(" - Volume max (4 values)")
|
||
print(" - Entry buffer (3 values)")
|
||
print(" - ADX length (4 values)")
|
||
print(" - RSI length (3 values)")
|
||
print(" - Source mode (Chart vs Heikin Ashi)")
|
||
print(" - MA gap filter (3 modes)")
|
||
print()
|
||
print("Expected runtime: 40-80 hours on 2-worker cluster")
|
||
print("Target: Beat baseline $194.43/1k (19.44% returns)")
|
||
print()
|
||
|
||
# Generate parameter grid
|
||
print("Generating parameter combinations...")
|
||
configs = generate_parameter_grid()
|
||
print(f"Total configurations: {len(configs):,}")
|
||
print()
|
||
|
||
# Determine number of workers
|
||
n_workers = mp.cpu_count()
|
||
print(f"Using {n_workers} CPU cores")
|
||
print()
|
||
|
||
# Run sweep
|
||
print("Starting parameter sweep...")
|
||
with mp.Pool(n_workers) as pool:
|
||
results = list(tqdm(
|
||
pool.imap(test_config, configs),
|
||
total=len(configs),
|
||
desc="Testing configs"
|
||
))
|
||
|
||
# Convert to DataFrame
|
||
results_df = pd.DataFrame(results)
|
||
|
||
# Save full results
|
||
output_file = "sweep_v9_advanced_full.csv"
|
||
results_df.to_csv(output_file, index=False)
|
||
print(f"Full results saved to: {output_file}")
|
||
|
||
# Sort by PnL and save top 1000
|
||
top_results = results_df.nlargest(1000, 'pnl')
|
||
top_file = "sweep_v9_advanced_top1000.csv"
|
||
top_results.to_csv(top_file, index=False)
|
||
print(f"Top 1000 configurations saved to: {top_file}")
|
||
|
||
# Print summary
|
||
print()
|
||
print("=" * 80)
|
||
print("SWEEP COMPLETE")
|
||
print("=" * 80)
|
||
print()
|
||
print(f"Best configuration:")
|
||
best = top_results.iloc[0]
|
||
print(f" PnL: ${best['pnl']:.2f}")
|
||
print(f" Win Rate: {best['win_rate']:.1f}%")
|
||
print(f" Profit Factor: {best['profit_factor']:.2f}")
|
||
print(f" Max Drawdown: ${best['max_drawdown']:.2f}")
|
||
print(f" Total Trades: {best['total_trades']}")
|
||
print()
|
||
print("Parameters:")
|
||
print(f" ATR Period: {best['atr_period']}")
|
||
print(f" Multiplier: {best['multiplier']}")
|
||
print(f" ADX Length: {best['adx_length']}")
|
||
print(f" RSI Length: {best['rsi_length']}")
|
||
print(f" RSI Long: {best['rsi_long_min']}-{best['rsi_long_max']}")
|
||
print(f" RSI Short: {best['rsi_short_min']}-{best['rsi_short_max']}")
|
||
print(f" Volume Max: {best['vol_max']}")
|
||
print(f" Entry Buffer: {best['entry_buffer_atr']}")
|
||
print(f" Heikin Ashi: {best['use_heikin_ashi']}")
|
||
print(f" MA Gap Filter: {best['use_ma_gap_filter']}")
|
||
if best['use_ma_gap_filter']:
|
||
print(f" Long Min: {best['ma_gap_long_min']:.1f}%")
|
||
print(f" Short Max: {best['ma_gap_short_max']:.1f}%")
|
||
print()
|
||
print(f"Baseline to beat: $194.43 (19.44%)")
|
||
improvement = ((best['pnl'] - 194.43) / 194.43) * 100
|
||
print(f"Improvement: {improvement:+.1f}%")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|