#!/usr/bin/env python3 """ Health Check Client for Multi-Personality Signal Generator Query the running signal generator status """ import socket import sys import json def check_health(socket_path="/tmp/signals_health.sock"): """Query signal generator health status""" try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.settimeout(5) sock.connect(socket_path) # Receive response response = b"" while True: chunk = sock.recv(4096) if not chunk: break response += chunk sock.close() # Parse and display health = json.loads(response.decode('utf-8')) print("=" * 70) print("MULTI-PERSONALITY SIGNAL GENERATOR HEALTH STATUS") print("=" * 70) print(f"Status: {health['status']}") print(f"Mode: {health.get('mode', 'single-personality')}") # Handle both old single-personality and new multi-personality format if 'personalities' in health: print(f"Personalities: {', '.join(health['personalities'])}") elif 'personality' in health: print(f"Personality: {health['personality']}") print(f"Timeframes: {', '.join(health['timeframes'])}") print(f"Uptime: {health['uptime_seconds']}s ({health['uptime_seconds']//60}m)") print(f"Connected Clients: {health['connected_clients']}") print(f"Debug Mode: {'ON' if health.get('debug_mode') else 'OFF'}") # Total stats (aggregated across all personalities) if 'total_stats' in health: total = health['total_stats'] print(f"\nAGGREGATED STATISTICS:") print(f" Total Signals: {total['total_signals']}") print(f" - Buy: {total['buy_signals']}") print(f" - Sell: {total['sell_signals']}") print(f" Errors: {total['errors']}") else: # Fallback for old format print(f"\nSTATISTICS:") print(f" Total Signals: {health.get('total_signals', 0)}") print(f" - Buy: {health.get('buy_signals', 0)}") print(f" - Sell: {health.get('sell_signals', 0)}") print(f" Errors: {health.get('errors', 0)}") # Per-personality breakdown (if available) if 'personality_stats' in health: print("\n" + "=" * 70) print("PER-PERSONALITY BREAKDOWN") print("=" * 70) for personality, stats in health['personality_stats'].items(): print(f"\n{personality.upper()}:") print(f" Total Signals: {stats['total_signals']}") print(f" - Buy: {stats['buy_signals']}") print(f" - Sell: {stats['sell_signals']}") print(f" Last Signal: {stats['last_signal'] or 'None'}") print(f" Errors: {stats['errors']}") if stats.get('recent_signals'): print(f" Recent Signals:") for sig in stats['recent_signals'][:3]: print(f" [{sig['timeframe']}] {sig['signal']} @ ${sig['price']:.2f} " f"(conf: {sig['confidence']:.2f})") else: # Old format print(f" Last Signal: {health.get('last_signal') or 'None'}") # Database Status print("\n" + "=" * 70) print("DATABASE STATUS") print("=" * 70) db = health.get('databases', {}) # Candles DB candles = db.get('candles_db', {}) print(f"Candles DB: {'✓ OK' if candles.get('accessible') else '✗ FAILED'}") if candles.get('error'): print(f" Error: {candles['error']}") else: print(f" Total Rows: {candles.get('row_count', 0)}") for tf, info in candles.get('timeframes', {}).items(): age = info.get('age_seconds') age_str = f"{age}s ago" if age is not None else "N/A" status = "⚠ STALE" if age and age > 300 else "" print(f" [{tf}]: {info['count']} rows, latest: {age_str} {status}") # Analysis DB analysis = db.get('analysis_db', {}) print(f"\nAnalysis DB: {'✓ OK' if analysis.get('accessible') else '✗ FAILED'}") if analysis.get('error'): print(f" Error: {analysis['error']}") else: print(f" Total Rows: {analysis.get('row_count', 0)}") for tf, info in analysis.get('timeframes', {}).items(): age = info.get('age_seconds') age_str = f"{age}s ago" if age is not None else "N/A" status = "⚠ STALE" if age and age > 300 else "" print(f" [{tf}]: {info['count']} rows, latest: {age_str} {status}") # Configuration print("\n" + "=" * 70) print("CONFIGURATION") print("=" * 70) cfg = health.get('config', {}) print(f"Min Confidence: {cfg.get('min_confidence', 'N/A')}") print(f"Cooldown: {cfg.get('cooldown_seconds', 'N/A')}s") print(f"Lookback: {cfg.get('lookback', 'N/A')} candles") print(f"Config Reloads: {cfg.get('reloads', 0)}") if cfg.get('weights'): # Multi-personality format if isinstance(cfg['weights'], dict) and any(k in cfg['weights'] for k in ['scalping', 'swing']): for personality, weights in cfg['weights'].items(): print(f"\n{personality.upper()} Weights:") for indicator, weight in weights.items(): print(f" {indicator:12s} {weight}") else: # Single personality format print(f"Weights:") for indicator, weight in cfg['weights'].items(): print(f" {indicator:12s} {weight}") # Recent signals (all personalities combined) recent = health.get('recent_signals', []) if recent: print("\n" + "=" * 70) print("RECENT SIGNALS (ALL PERSONALITIES)") print("=" * 70) for sig in recent[:10]: personality_tag = f"[{sig.get('personality', '?').upper()}]" print(f" {personality_tag:12s} [{sig['timeframe']}] {sig['signal']} @ ${sig['price']:.2f} " f"(conf: {sig['confidence']:.2f})") if sig.get('reasons'): reasons_str = ', '.join(sig['reasons'][:3]) if len(sig['reasons']) > 3: reasons_str += f" (+{len(sig['reasons'])-3} more)" print(f" Reasons: {reasons_str}") print("=" * 70) return 0 except FileNotFoundError: print(f"Error: Socket not found at {socket_path}") print("Is the signal generator running?") return 1 except ConnectionRefusedError: print(f"Error: Connection refused at {socket_path}") return 1 except KeyError as e: print(f"Error: Missing expected field in health response: {e}") print("\nRaw response:") print(json.dumps(health, indent=2)) return 1 except Exception as e: print(f"Error: {e}") import traceback traceback.print_exc() return 1 if __name__ == "__main__": socket_path = sys.argv[1] if len(sys.argv) > 1 else "/tmp/signals_health.sock" sys.exit(check_health(socket_path))