#!/usr/bin/env python3 import socket import json import time import os from datetime import datetime from rich.live import Live from rich.table import Table from rich.layout import Layout from rich.panel import Panel from rich.console import Console from rich.text import Text from rich.columns import Columns # --- CONFIGURATION --- INPUT_SOCKET = "/tmp/streamer.sock" ONRAMP_HOST = "127.0.0.1" ONRAMP_PORT = 9999 REFRESH_RATE = 1.0 console = Console() def query_input_go(): if not os.path.exists(INPUT_SOCKET): return None try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: s.settimeout(0.2) s.connect(INPUT_SOCKET) return s.recv(1024).decode('utf-8').strip() except: return None def query_onramp(command): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # Increase timeout slightly for the larger 'live' payload s.settimeout(1.0) s.connect((ONRAMP_HOST, ONRAMP_PORT)) s.sendall(command.encode('utf-8')) chunks = [] while True: chunk = s.recv(4096) # Read in 4KB chunks if not chunk: break # Server closed connection, we have everything chunks.append(chunk) full_data = b"".join(chunks).decode('utf-8') return json.loads(full_data) except Exception as e: # For debugging, you can uncomment the line below: # print(f"Socket Error: {e}") return None def make_layout(): layout = Layout() layout.split( Layout(name="header", size=3), Layout(name="main", size=10), Layout(name="market", size=12), Layout(name="footer", size=3), ) layout["main"].split_row( Layout(name="input_svc"), Layout(name="onramp_svc"), ) return layout def get_input_panel(): raw = query_input_go() if not raw: return Panel(Text("OFFLINE", style="bold red"), title="[1] Input Service (Go)", border_style="red") # The Go app returns: "Uptime: 1m2s | Total Msgs: 500 | Rate: 10.00 msg/min" parts = raw.split("|") content = "\n".join([p.strip() for p in parts]) return Panel(content, title="[1] Input Service (Go)", border_style="green") def get_onramp_panel(): data = query_onramp("status") if not data: return Panel(Text("OFFLINE", style="bold red"), title="[2] Onramp Service (Python)", border_style="red") last_ts = data.get('last_ts', 0) / 1000 lag = time.time() - last_ts if last_ts > 0 else 0 lag_style = "green" if lag < 2 else "yellow" if lag < 5 else "bold red" content = Text() content.append(f"Uptime Start : {data.get('uptime_start')}\n") content.append(f"Total Trades : {data.get('total_trades')}\n") content.append(f"Current File : {os.path.basename(str(data.get('last_file')))}\n") content.append("Lag : ", style="white") content.append(f"{lag:.2f}s", style=lag_style) return Panel(content, title="[2] Onramp Service (Python)", border_style="blue") def get_market_table(): res = query_onramp("live") table = Table(expand=True, border_style="cyan", header_style="bold cyan") table.add_column("TF", justify="center", style="bold yellow") table.add_column("Last Update", justify="center") table.add_column("Open", justify="right") table.add_column("High", justify="right") table.add_column("Low", justify="right") table.add_column("Close", justify="right") table.add_column("Volume", justify="right", style="magenta") table.add_column("Buy %", justify="right") if res and "data" in res: candles_data = res["data"] # We want to show these specific rows for tf in ["1m", "5m", "15m", "1h"]: if tf in candles_data and candles_data[tf]: # Get all timestamps, convert to int to find the latest one all_timestamps = [int(ts) for ts in candles_data[tf].keys()] latest_ts = str(max(all_timestamps)) c = candles_data[tf][latest_ts] # Format time ts_str = datetime.fromtimestamp(int(latest_ts)).strftime('%H:%M:%S') # Price Color (Bullish vs Bearish) color = "green" if c['close'] >= c['open'] else "red" # Calculate Buy Volume Percentage buy_pct = (c['buy_volume'] / c['volume'] * 100) if c['volume'] > 0 else 0 buy_color = "green" if buy_pct > 50 else "red" table.add_row( tf, ts_str, f"{c['open']:.2f}", f"{c['high']:.2f}", f"{c['low']:.2f}", Text(f"{c['close']:.2f}", style=f"bold {color}"), f"{c['volume']:.2f}", Text(f"{buy_pct:.1f}%", style=buy_color) ) time.sleep(1) else: # Placeholder if service is offline or data not ready table.add_row("waiting...", "-", "-", "-", "-", "-", "-", "-") return table def main(): layout = make_layout() with Live(layout, refresh_per_second=2, screen=True): while True: # Header header_text = Text(f"BYBIT BTC UNIFIED MONITOR | {datetime.now().strftime('%H:%M:%S')}", justify="center", style="bold white on blue") layout["header"].update(Panel(header_text)) # Body Panels layout["input_svc"].update(get_input_panel()) layout["onramp_svc"].update(get_onramp_panel()) # Market Table layout["market"].update(get_market_table()) # Footer footer_text = Text("Press Ctrl+C to exit | Monitoring: publicTrade.BTCUSDT", justify="center", style="dim") layout["footer"].update(footer_text) time.sleep(REFRESH_RATE) if __name__ == "__main__": main()