Files
trading_bot_v4/backtester/scripts/comprehensive_sweep.py
mindesbunister cc56b72df2 fix: Database-first cluster status detection + Stop button clarification
CRITICAL FIX (Nov 30, 2025):
- Dashboard showed 'idle' despite 22+ worker processes running
- Root cause: SSH-based worker detection timing out
- Solution: Check database for running chunks FIRST

Changes:
1. app/api/cluster/status/route.ts:
   - Query exploration database before SSH detection
   - If running chunks exist, mark workers 'active' even if SSH fails
   - Override worker status: 'offline' → 'active' when chunks running
   - Log: ' Cluster status: ACTIVE (database shows running chunks)'
   - Database is source of truth, SSH only for supplementary metrics

2. app/cluster/page.tsx:
   - Stop button ALREADY EXISTS (conditionally shown)
   - Shows Start when status='idle', Stop when status='active'
   - No code changes needed - fixed by status detection

Result:
- Dashboard now shows 'ACTIVE' with 2 workers (correct)
- Workers show 'active' status (was 'offline')
- Stop button automatically visible when cluster active
- System resilient to SSH timeouts/network issues

Verified:
- Container restarted: Nov 30 21:18 UTC
- API tested: Returns status='active', activeWorkers=2
- Logs confirm: Database-first logic working
- Workers confirmed running: 22+ processes on worker1, workers on worker2
2025-11-30 22:23:01 +01:00

235 lines
8.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Comprehensive parameter sweep to maximize v9 profitability.
Tests all critical parameters that could boost profit.
"""
import sys
from pathlib import Path
from datetime import datetime
import itertools
import multiprocessing as mp
from typing import List, Tuple, Optional
# Add parent directories to path
sys.path.insert(0, str(Path(__file__).parent.parent))
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from backtester.data_loader import load_csv
from backtester.simulator import simulate_money_line, TradeConfig
from backtester.indicators.money_line import MoneyLineInputs
def test_config(args):
"""Test a single parameter configuration."""
config_id, params, data_slice = args
try:
# Unpack parameters
flip_thresh, ma_gap, adx_min, long_pos, short_pos, cooldown, \
pos_size, tp1_mult, tp2_mult, sl_mult, tp1_close, trail_mult, \
vol_min, max_bars = params
# Create inputs
inputs = MoneyLineInputs(
flip_threshold_percent=flip_thresh,
ma_gap_threshold=ma_gap,
momentum_min_adx=adx_min,
momentum_long_max_pos=long_pos,
momentum_short_min_pos=short_pos,
cooldown_bars=cooldown,
momentum_spacing=3,
momentum_cooldown=2
)
# Create config
config = TradeConfig(
position_size=pos_size,
atr_multiplier_tp1=tp1_mult,
atr_multiplier_tp2=tp2_mult,
atr_multiplier_sl=sl_mult,
take_profit_1_size_percent=tp1_close,
trailing_atr_multiplier=trail_mult,
max_bars_per_trade=max_bars
)
# Quality filter (optional based on vol_min)
if vol_min > 0:
quality_filter = lambda s: s.adx >= adx_min and s.volume_ratio >= vol_min
else:
quality_filter = None
# Run simulation
results = simulate_money_line(
data_slice.data,
data_slice.symbol,
inputs,
config,
quality_filter=quality_filter
)
# Calculate per-$1000 profitability
pnl_per_1k = (results.total_pnl / pos_size) * 1000.0
return (
config_id,
len(results.trades),
results.win_rate * 100,
results.total_pnl,
pnl_per_1k,
params
)
except Exception as e:
return (config_id, 0, 0.0, 0.0, 0.0, params)
def main():
print("=" * 80)
print("COMPREHENSIVE V9 PARAMETER SWEEP")
print("=" * 80)
print(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# Load data
print("Loading data...")
data_path = Path(__file__).parent.parent / 'data' / 'solusdt_5m_aug_nov.csv'
data_slice = load_csv(data_path, 'SOL-PERP', '5m')
print(f"Loaded {len(data_slice.data)} candles")
print()
# Define parameter ranges to test
print("Setting up parameter grid...")
# CRITICAL PARAMETERS (most impact on profit)
flip_thresholds = [0.4, 0.5, 0.6, 0.7, 0.8] # 5 values
ma_gaps = [0.2, 0.35, 0.5] # 3 values
adx_mins = [15, 18, 21, 24] # 4 values
long_pos_maxs = [75, 80, 85, 90] # 4 values
short_pos_mins = [10, 15, 20, 25] # 4 values
cooldowns = [0, 1, 2, 3] # 4 values
# TRADE CONFIG PARAMETERS
position_sizes = [210] # Keep bot's size for consistency
tp1_multipliers = [1.5, 2.0, 2.5] # 3 values
tp2_multipliers = [3.0, 4.0, 5.0] # 3 values
sl_multipliers = [2.5, 3.0, 3.5] # 3 values
tp1_close_percents = [50, 60, 70, 80] # 4 values
trailing_multipliers = [1.5, 2.0, 2.5, 3.0] # 4 values
# QUALITY FILTERS
vol_mins = [0, 0.7, 0.9] # 3 values (0 = no filter)
max_bars_list = [None, 144, 288] # 3 values
# Calculate total combinations
total_combos = (
len(flip_thresholds) * len(ma_gaps) * len(adx_mins) *
len(long_pos_maxs) * len(short_pos_mins) * len(cooldowns) *
len(position_sizes) * len(tp1_multipliers) * len(tp2_multipliers) *
len(sl_multipliers) * len(tp1_close_percents) * len(trailing_multipliers) *
len(vol_mins) * len(max_bars_list)
)
print(f"Total combinations: {total_combos:,}")
print()
print("Parameter ranges:")
print(f" Flip Threshold: {flip_thresholds}")
print(f" MA Gap: {ma_gaps}")
print(f" ADX Min: {adx_mins}")
print(f" Long Pos Max: {long_pos_maxs}")
print(f" Short Pos Min: {short_pos_mins}")
print(f" Cooldown: {cooldowns}")
print(f" TP1 Multiplier: {tp1_multipliers}")
print(f" TP2 Multiplier: {tp2_multipliers}")
print(f" SL Multiplier: {sl_multipliers}")
print(f" TP1 Close %: {tp1_close_percents}")
print(f" Trailing Multiplier: {trailing_multipliers}")
print(f" Vol Min: {vol_mins}")
print(f" Max Bars: {max_bars_list}")
print()
# Generate all parameter combinations
print("Generating parameter combinations...")
param_combos = list(itertools.product(
flip_thresholds, ma_gaps, adx_mins, long_pos_maxs, short_pos_mins, cooldowns,
position_sizes, tp1_multipliers, tp2_multipliers, sl_multipliers,
tp1_close_percents, trailing_multipliers, vol_mins, max_bars_list
))
print(f"Generated {len(param_combos):,} combinations")
print()
# Prepare arguments for multiprocessing
args_list = [(i, params, data_slice) for i, params in enumerate(param_combos)]
# Run multiprocessing sweep
num_workers = mp.cpu_count()
print(f"Starting sweep with {num_workers} workers...")
print(f"Progress logged to: sweep_progress.log")
print()
results = []
with mp.Pool(processes=num_workers) as pool:
completed = 0
for result in pool.imap_unordered(test_config, args_list, chunksize=10):
completed += 1
results.append(result)
# Log progress every 100 configs
if completed % 100 == 0:
pct = (completed / len(param_combos)) * 100
print(f"Progress: {completed:,}/{len(param_combos):,} ({pct:.1f}%)")
# Show best so far
if results:
best = max(results, key=lambda x: x[4])
print(f" Best so far: ${best[4]:.2f}/1k (trades={best[1]}, WR={best[2]:.1f}%)")
print()
print()
print("=" * 80)
print("SWEEP COMPLETE!")
print("=" * 80)
print()
# Sort by profitability per $1000
results.sort(key=lambda x: x[4], reverse=True)
# Show top 20 results
print("TOP 20 CONFIGURATIONS:")
print("-" * 80)
print(f"{'Rank':<6} {'Trades':<8} {'WR%':<8} {'P&L':<12} {'$/1k':<12} Parameters")
print("-" * 80)
for i, (config_id, trades, wr, pnl, pnl_per_1k, params) in enumerate(results[:20], 1):
flip_t, ma_g, adx, long_p, short_p, cool, pos_sz, tp1_m, tp2_m, sl_m, tp1_c, trail_m, vol_m, max_b = params
param_str = (
f"flip={flip_t:.1f}, ma={ma_g:.2f}, adx={adx:.0f}, "
f"pos={long_p:.0f}/{short_p:.0f}, cool={cool}, "
f"tp={tp1_m:.1f}/{tp2_m:.1f}, sl={sl_m:.1f}, "
f"tp1%={tp1_c:.0f}, trail={trail_m:.1f}, "
f"vol={vol_m:.1f}, bars={max_b}"
)
print(f"{i:<6} {trades:<8} {wr:<8.2f} ${pnl:<11.2f} ${pnl_per_1k:<11.2f} {param_str}")
print()
print("=" * 80)
print(f"Finished: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
# Save results to CSV
output_file = Path(__file__).parent.parent / 'sweep_comprehensive.csv'
with open(output_file, 'w') as f:
f.write("rank,trades,win_rate,total_pnl,pnl_per_1k,")
f.write("flip_threshold,ma_gap,adx_min,long_pos_max,short_pos_min,cooldown,")
f.write("position_size,tp1_mult,tp2_mult,sl_mult,tp1_close_pct,trailing_mult,vol_min,max_bars\n")
for i, (config_id, trades, wr, pnl, pnl_per_1k, params) in enumerate(results, 1):
f.write(f"{i},{trades},{wr:.2f},{pnl:.2f},{pnl_per_1k:.2f},")
f.write(",".join(str(p) for p in params))
f.write("\n")
print(f"Full results saved to: {output_file}")
if __name__ == '__main__':
main()