Better monitor, uses rich library for TUI.

This commit is contained in:
Kalzu Rekku
2026-01-13 21:41:27 +02:00
parent abb60348f6
commit ac4f2cfcc8

View File

@@ -6,96 +6,170 @@ import time
import os import os
from datetime import datetime 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 --- # --- CONFIGURATION ---
# Adjust these paths/ports to match your config.json files
INPUT_SOCKET = "/tmp/streamer.sock" INPUT_SOCKET = "/tmp/streamer.sock"
ONRAMP_HOST = "127.0.0.1" ONRAMP_HOST = "127.0.0.1"
ONRAMP_PORT = 9999 ONRAMP_PORT = 9999
REFRESH_RATE = 1.0 # Seconds REFRESH_RATE = 1.0
console = Console()
def query_input_go(): def query_input_go():
"""Queries the Go Input service via Unix Socket."""
if not os.path.exists(INPUT_SOCKET): if not os.path.exists(INPUT_SOCKET):
return "OFFLINE (Socket not found)" return None
try: try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(0.5) s.settimeout(0.2)
s.connect(INPUT_SOCKET) s.connect(INPUT_SOCKET)
data = s.recv(1024) return s.recv(1024).decode('utf-8').strip()
return data.decode('utf-8').strip()
except Exception as e:
return f"OFFLINE ({e})"
def query_onramp_json(command):
"""Queries the Python Onramp service via TCP."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.5)
s.connect((ONRAMP_HOST, ONRAMP_PORT))
s.sendall(command.encode('utf-8'))
data = s.recv(8192) # Larger buffer for 'live' data
return json.loads(data.decode('utf-8'))
except: except:
return None return None
def format_candle(candle): def query_onramp(command):
"""Formats a single candle dictionary into a readable line.""" try:
return (f"O: {candle['open']:.2f} | H: {candle['high']:.2f} | " with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
f"L: {candle['low']:.2f} | C: {candle['close']:.2f} | V: {candle['volume']:.2f}") # 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(): def main():
try: layout = make_layout()
with Live(layout, refresh_per_second=2, screen=True):
while True: while True:
# 1. Collect Data # Header
input_status = query_input_go() header_text = Text(f"BYBIT BTC UNIFIED MONITOR | {datetime.now().strftime('%H:%M:%S')}",
onramp_status = query_onramp_json("status") justify="center", style="bold white on blue")
onramp_live = query_onramp_json("live") layout["header"].update(Panel(header_text))
# 2. Clear Screen # Body Panels
print("\033[2J\033[H", end="") layout["input_svc"].update(get_input_panel())
layout["onramp_svc"].update(get_onramp_panel())
print("="*70)
print(f" UNIFIED MONITOR - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*70)
# 3. Display Input (Go) Section # Market Table
print(f"\n[1] INPUT SERVICE (Go)") layout["market"].update(get_market_table())
print(f" Status: {input_status}")
# 4. Display Onramp (Python) Section # Footer
print(f"\n[2] ONRAMP SERVICE (Python)") footer_text = Text("Press Ctrl+C to exit | Monitoring: publicTrade.BTCUSDT", justify="center", style="dim")
if onramp_status: layout["footer"].update(footer_text)
stats = onramp_status
# Calculate lag
last_ts = stats.get('last_ts', 0) / 1000
lag = time.time() - last_ts if last_ts > 0 else 0
print(f" Uptime Start : {stats.get('uptime_start')}")
print(f" Processed : {stats.get('total_trades')} trades")
print(f" Current File : {os.path.basename(str(stats.get('last_file')))}")
print(f" Data Lag : {lag:.2f}s")
else:
print(" Status: OFFLINE")
# 5. Display Live Market Snapshot
if onramp_live and "data" in onramp_live:
print(f"\n[3] LIVE MARKET SNAPSHOT")
candles = onramp_live["data"]
# Show 1m and 1h as examples
for tf in ["1m", "1h"]:
if tf in candles:
# Get the most recent timestamp in that timeframe
latest_ts = max(candles[tf].keys())
c = candles[tf][latest_ts]
ts_str = datetime.fromtimestamp(int(latest_ts)).strftime('%H:%M')
print(f" {tf} ({ts_str}) >> {format_candle(c)}")
print("\n" + "="*70)
print(" (Ctrl+C to exit)")
time.sleep(REFRESH_RATE) time.sleep(REFRESH_RATE)
except KeyboardInterrupt:
print("\nClosing monitor...")
if __name__ == "__main__": if __name__ == "__main__":
main() main()