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
69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Debug MA gap scoring to understand parameter insensitivity."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
# Add backtester to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from backtester.data_loader import load_csv
|
|
from backtester.indicators.money_line import MoneyLineInputs, ema
|
|
|
|
def analyze_ma_gap_distribution(csv_path: str):
|
|
"""Analyze MA gap score distribution to understand parameter behavior."""
|
|
|
|
print("=" * 80)
|
|
print("MA GAP DISTRIBUTION ANALYSIS")
|
|
print("=" * 80)
|
|
|
|
# Load data
|
|
data_slice = load_csv(Path(csv_path), "SOL-PERP", "5m")
|
|
df = data_slice.data # DataSlice.data is the DataFrame
|
|
print(f"Loaded {len(df)} bars\n")
|
|
|
|
# Calculate EMAs
|
|
df["ema_fast"] = ema(df["close"], 50)
|
|
df["ema_slow"] = ema(df["close"], 200)
|
|
|
|
# Calculate raw MA gap percentage
|
|
ma_gap = 100.0 * (df["ema_fast"] - df["ema_slow"]) / df["close"]
|
|
|
|
print("RAW MA GAP (%) DISTRIBUTION:")
|
|
print(f" Min: {ma_gap.min():.4f}%")
|
|
print(f" 25th: {ma_gap.quantile(0.25):.4f}%")
|
|
print(f" Median: {ma_gap.median():.4f}%")
|
|
print(f" 75th: {ma_gap.quantile(0.75):.4f}%")
|
|
print(f" Max: {ma_gap.max():.4f}%")
|
|
print(f" Std: {ma_gap.std():.4f}%\n")
|
|
|
|
# Test different thresholds
|
|
for threshold in [0.2, 0.3, 0.35, 0.4, 0.5]:
|
|
gap_score = np.tanh(ma_gap / threshold)
|
|
|
|
print(f"\nTHRESHOLD = {threshold} (default: 0.35):")
|
|
print(f" gap_score min: {gap_score.min():.6f}")
|
|
print(f" gap_score max: {gap_score.max():.6f}")
|
|
print(f" gap_score median: {gap_score.median():.6f}")
|
|
|
|
# Check flip_threshold comparisons
|
|
for flip_pct in [0.4, 0.5, 0.6, 0.7]:
|
|
flip_threshold = flip_pct / 100.0 # Convert to decimal
|
|
|
|
# Count how many bars exceed threshold
|
|
long_signals = (gap_score > flip_threshold).sum()
|
|
short_signals = (gap_score < -flip_threshold).sum()
|
|
total_signals = long_signals + short_signals
|
|
|
|
print(f" flip_threshold={flip_pct}% → {total_signals} potential signals")
|
|
print(f" (LONG: {long_signals}, SHORT: {short_signals})")
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 2:
|
|
print("Usage: python debug_ma_gap.py <csv_path>")
|
|
sys.exit(1)
|
|
|
|
analyze_ma_gap_distribution(sys.argv[1])
|