Files
trading_bot_v4/scripts/compare_v8_v9.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

143 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""
Quick comparison: v8 "Sticky Trend" vs v9 "MA Gap" baseline performance.
Runs both indicators with default parameters on the same dataset.
"""
from pathlib import Path
import pandas as pd
from backtester.data_loader import load_csv
from backtester.simulator import simulate_money_line, TradeConfig, SimulationResult
from backtester.indicators.money_line import money_line_signals, MoneyLineInputs
from backtester.indicators.money_line_v8 import money_line_v8_signals, MoneyLineV8Inputs
# Load data
print("Loading SOLUSDT 5m data (Aug 1 - Nov 28, 2025)...")
data_slice = load_csv(Path("data/solusdt_5m.csv"), "SOLUSDT", "5m")
df = data_slice.data
print(f"Loaded {len(df)} candles\n")
# Run v9 baseline
print("=" * 60)
print("v9 'MA Gap' - Baseline (Default Parameters)")
print("=" * 60)
v9_inputs = MoneyLineInputs(
flip_threshold_percent=0.6,
cooldown_bars=2,
ma_gap_threshold=0.35,
momentum_min_adx=23.0,
momentum_long_max_pos=70.0,
momentum_short_min_pos=25.0,
)
v9_result = simulate_money_line(df, "SOLUSDT", v9_inputs)
v9_trades = v9_result.trades
print(f"Generated {len(v9_trades)} trades")
wins_v9 = sum(1 for t in v9_trades if t.realized_pnl > 0)
losses_v9 = sum(1 for t in v9_trades if t.realized_pnl <= 0)
gross_wins_v9 = sum(t.realized_pnl for t in v9_trades if t.realized_pnl > 0)
gross_losses_v9 = abs(sum(t.realized_pnl for t in v9_trades if t.realized_pnl <= 0))
pf_v9 = gross_wins_v9 / gross_losses_v9 if gross_losses_v9 > 0 else 0.0
wr_v9 = (wins_v9 / len(v9_trades) * 100) if v9_trades else 0.0
avg_win_v9 = (gross_wins_v9 / wins_v9) if wins_v9 > 0 else 0.0
avg_loss_v9 = (gross_losses_v9 / losses_v9) if losses_v9 > 0 else 0.0
print(f"\nResults:")
print(f" Total P&L: ${v9_result.total_pnl:.2f}")
print(f" Total Trades: {len(v9_trades)}")
print(f" Win Rate: {wr_v9:.2f}%")
print(f" Profit Factor: {pf_v9:.3f}")
print(f" Max Drawdown: ${v9_result.max_drawdown:.2f}")
print(f" Avg Win: ${avg_win_v9:.2f}")
print(f" Avg Loss: ${avg_loss_v9:.2f}")
# Run v8 baseline
print("\n" + "=" * 60)
print("v8 'Sticky Trend' - Baseline (Default Parameters)")
print("=" * 60)
# Create custom simulator for v8 since it doesn't use MoneyLineInputs
v8_signals = money_line_v8_signals(df, MoneyLineV8Inputs())
print(f"Generated {len(v8_signals)} signals")
# Manually simulate v8 trades using same logic as v9
from backtester.simulator import _simulate_trade, TradeConfig
v8_trades = []
config = TradeConfig()
index_positions = {ts: idx for idx, ts in enumerate(df.index)}
next_available = 0
for sig in v8_signals:
if sig.timestamp not in index_positions:
continue
idx = index_positions[sig.timestamp]
if idx < next_available:
continue
# Convert v8 signal to v9 signal format for simulator
from backtester.indicators.money_line import MoneyLineSignal
ml_sig = MoneyLineSignal(
timestamp=sig.timestamp,
direction=sig.direction,
entry_price=sig.entry_price,
adx=sig.adx,
atr=sig.atr,
rsi=sig.rsi,
volume_ratio=sig.volume_ratio,
price_position=sig.price_position,
signal_type="primary"
)
trade = _simulate_trade(df, idx, ml_sig, "SOLUSDT", config)
if trade:
v8_trades.append(trade)
next_available = trade._exit_index
v8_total_pnl = sum(t.realized_pnl for t in v8_trades)
v8_max_dd = 0.0
equity = 0.0
peak = 0.0
for t in v8_trades:
equity += t.realized_pnl
peak = max(peak, equity)
v8_max_dd = min(v8_max_dd, equity - peak)
wins_v8 = sum(1 for t in v8_trades if t.realized_pnl > 0)
losses_v8 = sum(1 for t in v8_trades if t.realized_pnl <= 0)
gross_wins_v8 = sum(t.realized_pnl for t in v8_trades if t.realized_pnl > 0)
gross_losses_v8 = abs(sum(t.realized_pnl for t in v8_trades if t.realized_pnl <= 0))
pf_v8 = gross_wins_v8 / gross_losses_v8 if gross_losses_v8 > 0 else 0.0
wr_v8 = (wins_v8 / len(v8_trades) * 100) if v8_trades else 0.0
avg_win_v8 = (gross_wins_v8 / wins_v8) if wins_v8 > 0 else 0.0
avg_loss_v8 = (gross_losses_v8 / losses_v8) if losses_v8 > 0 else 0.0
print(f"\nResults:")
print(f" Total P&L: ${v8_total_pnl:.2f}")
print(f" Total Trades: {len(v8_trades)}")
print(f" Win Rate: {wr_v8:.2f}%")
print(f" Profit Factor: {pf_v8:.3f}")
print(f" Max Drawdown: ${v8_max_dd:.2f}")
print(f" Avg Win: ${avg_win_v8:.2f}")
print(f" Avg Loss: ${avg_loss_v8:.2f}")
# Comparison
print("\n" + "=" * 60)
print("HEAD-TO-HEAD COMPARISON")
print("=" * 60)
pnl_diff = v9_result.total_pnl - v8_total_pnl
pnl_diff_pct = (pnl_diff / abs(v8_total_pnl)) * 100 if v8_total_pnl != 0 else 0
trade_diff = len(v9_trades) - len(v8_trades)
wr_diff = wr_v9 - wr_v8
print(f"P&L: v9 ${v9_result.total_pnl:.2f} vs v8 ${v8_total_pnl:.2f} (Δ ${pnl_diff:+.2f}, {pnl_diff_pct:+.1f}%)")
print(f"Trades: v9 {len(v9_trades)} vs v8 {len(v8_trades)}{trade_diff:+d})")
print(f"Win Rate: v9 {wr_v9:.2f}% vs v8 {wr_v8:.2f}% (Δ {wr_diff:+.2f}%)")
print(f"Profit Factor: v9 {pf_v9:.3f} vs v8 {pf_v8:.3f}")
print("\n" + "=" * 60)
if pnl_diff > 0:
print(f"WINNER: v9 'MA Gap' by ${pnl_diff:.2f} ({pnl_diff_pct:.1f}%)")
print("v9's faster entries + MA gap context outperform v8's conservative approach")
else:
print(f"WINNER: v8 'Sticky Trend' by ${-pnl_diff:.2f} ({-pnl_diff_pct:.1f}%)")
print("v8's conservative confirmation bars outperform v9's speed")
print("=" * 60)