revert: remove all blinking related Live UI changes (v1.4.1)

This commit is contained in:
Gemini CLI
2026-03-05 22:50:40 +01:00
parent b44847cbbd
commit cf0ccbcad5

View File

@ -11,11 +11,10 @@ import numpy as np
from datetime import datetime, timezone
from typing import List, Dict, Any, Optional
from dotenv import load_dotenv
from rich.console import Console, Group
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich.live import Live
from rich import box
import asyncpg
@ -41,6 +40,7 @@ logging.basicConfig(
logger = logging.getLogger("PingPongBot")
class DatabaseManager:
"""Minimal Database Manager for the bot"""
def __init__(self):
self.host = os.getenv('DB_HOST', '20.20.20.20')
self.port = int(os.getenv('DB_PORT', 5433))
@ -68,7 +68,7 @@ class DatabaseManager:
class PingPongBot:
def __init__(self, config_path="config/ping_pong_config.yaml"):
self.version = "1.3.9"
self.version = "1.4.1"
with open(config_path, 'r') as f:
self.config = yaml.safe_load(f)
@ -78,7 +78,6 @@ class PingPongBot:
if not self.api_key or not self.api_secret:
raise ValueError("API_KEY and API_SECRET must be set in .env file")
# Corrected timeout parameter for pybit V5 HTTP
self.session = HTTP(
testnet=False,
api_key=self.api_key,
@ -87,10 +86,13 @@ class PingPongBot:
)
self.db = DatabaseManager()
self.symbol = self.config['symbol'].upper()
self.db_symbol = self.symbol.replace("USDT", "")
self.symbol = self.config['symbol'].upper() # e.g. BTCUSDT
self.db_symbol = self.symbol.replace("USDT", "") # e.g. BTC
self.interval = str(self.config['interval'])
# Map interval to DB format: '1' -> '1m'
self.db_interval = self.interval + "m" if self.interval.isdigit() else self.interval
self.direction = self.config['direction'].lower()
# State
@ -122,16 +124,19 @@ class PingPongBot:
return series.ewm(alpha=alpha, adjust=False).mean()
def calculate_indicators(self, df):
# RSI
rsi_cfg = self.config['rsi']
delta = df['close'].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
df['rsi'] = 100 - (100 / (1 + (self.rma(gain, rsi_cfg['period']) / self.rma(loss, rsi_cfg['period']))))
# Hurst
hurst_cfg = self.config['hurst']
mcl = hurst_cfg['period'] / 2
mcl_2 = int(round(mcl / 2))
# Vectorized TR calculation
df['tr'] = np.maximum(
df['high'] - df['low'],
np.maximum(
@ -156,23 +161,25 @@ class PingPongBot:
return df
async def update_exchange_data(self):
"""Fetch Price, Balance, Position every 15s"""
try:
# Wrap synchronous pybit calls in asyncio.to_thread
ticker = await asyncio.to_thread(self.session.get_tickers, category="linear", symbol=self.symbol)
if ticker['retCode'] == 0:
self.market_price = float(ticker['result']['list'][0]['lastPrice'])
pos = await asyncio.to_thread(self.session.get_positions, category="linear", symbol=self.symbol, settleCoin="USDT")
if pos['retCode'] == 0:
active = [p for p in pos['result']['list'] if float(p['size']) > 0]
active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0]
self.position = active[0] if active else None
wallet = await asyncio.to_thread(self.session.get_wallet_balance, category="linear", accountType="UNIFIED", coin="USDT")
if wallet['retCode'] == 0:
res_list = wallet['result']['list']
if res_list:
self.wallet_balance = float(res_list[0].get('totalWalletBalance', 0))
result_list = wallet['result']['list']
if result_list:
self.wallet_balance = float(result_list[0].get('totalWalletBalance', 0))
if self.wallet_balance == 0:
coin_info = res_list[0].get('coin', [])
coin_info = result_list[0].get('coin', [])
if coin_info:
self.wallet_balance = float(coin_info[0].get('walletBalance', 0))
except Exception as e:
@ -182,11 +189,13 @@ class PingPongBot:
last, prev = df.iloc[-1], df.iloc[-2]
rsi_cfg, hurst_cfg = self.config['rsi'], self.config['hurst']
# Long Signals
l_open = (rsi_cfg['enabled_for_open'] and prev['rsi'] < rsi_cfg['oversold'] and last['rsi'] >= rsi_cfg['oversold']) or \
(hurst_cfg['enabled_for_open'] and prev['close'] > prev['hurst_lower'] and last['close'] <= last['hurst_lower'])
l_close = (rsi_cfg['enabled_for_close'] and prev['rsi'] > rsi_cfg['overbought'] and last['rsi'] <= rsi_cfg['overbought']) or \
(hurst_cfg['enabled_for_close'] and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
# Short Signals
s_open = (rsi_cfg['enabled_for_open'] and prev['rsi'] > rsi_cfg['overbought'] and last['rsi'] <= rsi_cfg['overbought']) or \
(hurst_cfg['enabled_for_open'] and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
s_close = (rsi_cfg['enabled_for_close'] and prev['rsi'] < rsi_cfg['oversold'] and last['rsi'] >= rsi_cfg['oversold']) or \
@ -231,50 +240,41 @@ class PingPongBot:
except Exception as e:
logger.error(f"Trade Error: {e}")
def get_dashboard(self):
"""Generates a single consolidated Panel for the dashboard"""
now = datetime.now().strftime("%H:%M:%S")
def render_dashboard(self):
# standard print based dashboard
self.console.print("\n" + "="*60)
cfg_table = Table(title=f"PING-PONG BOT v{self.version} [{self.direction.upper()}]", box=box.ROUNDED, expand=True)
cfg_table.add_column("Property"); cfg_table.add_column("Value")
cfg_table.add_row("Symbol", self.symbol); cfg_table.add_row("Price", f"${self.market_price:.2f}")
cfg_table.add_row("Last Candle", f"{self.last_candle_time} (@${self.last_candle_price:.2f})")
# 1. Info Table
info = Table(box=None, expand=True, show_header=False)
info.add_column("K", style="cyan"); info.add_column("V", style="white")
info.add_row("Symbol", f"{self.symbol} [{self.direction.upper()}]")
info.add_row("Price", f"${self.market_price:,.2f}")
info.add_row("Candle", f"{self.last_candle_time} (@${self.last_candle_price:,.2f})")
ind_table = Table(title="INDICATORS", box=box.ROUNDED, expand=True)
ind_table.add_column("Indicator"); ind_table.add_column("Value"); ind_table.add_column("Updated")
for k, v in self.current_indicators.items():
ind_table.add_row(k.upper(), f"{v['value']:.2f}", v['timestamp'])
# 2. Indicators Table
inds = Table(box=box.SIMPLE, expand=True)
inds.add_column("Indicator", style="dim"); inds.add_column("Value", justify="right"); inds.add_column("Updated", justify="center")
inds.add_row("HURST UPPER", f"{self.current_indicators['hurst_upper']['value']:.2f}", self.current_indicators['hurst_upper']['timestamp'])
inds.add_row("HURST LOWER", f"{self.current_indicators['hurst_lower']['value']:.2f}", self.current_indicators['hurst_lower']['timestamp'])
inds.add_row("RSI", f"{self.current_indicators['rsi']['value']:.2f}", self.current_indicators['rsi']['timestamp'])
# 3. Position Table
pos = Table(box=box.SIMPLE, expand=True)
pos.add_column("Wallet", justify="center"); pos.add_column("Size", justify="center"); pos.add_column("PnL", justify="center")
pos_table = Table(title="POSITION", box=box.ROUNDED, expand=True)
pos_table.add_column("Wallet"); pos_table.add_column("Size"); pos_table.add_column("Entry"); pos_table.add_column("PnL")
if self.position:
pnl = float(self.position['unrealisedPnl'])
pos.add_row(f"${self.wallet_balance:,.2f}", str(self.position['size']), f"[bold {'green' if pnl>=0 else 'red'}]${pnl:,.2f}")
pos_table.add_row(f"${self.wallet_balance:.2f}", self.position['size'], self.position['avgPrice'], f"[bold {'green' if pnl>=0 else 'red'}]${pnl:.2f}")
else:
pos.add_row(f"${self.wallet_balance:,.2f}", "0", "-")
pos_table.add_row(f"${self.wallet_balance:.2f}", "0", "-", "-")
content = Group(info, inds, pos, f"[dim]Status: {self.status_msg} | Signal: {self.last_signal or 'None'} | Heartbeat: {now}[/]")
return Panel(content, title=f"[bold cyan]PING-PONG BOT v{self.version}[/]", border_style="blue")
self.console.print(cfg_table); self.console.print(ind_table); self.console.print(pos_table)
self.console.print(f"[dim]Status: {self.status_msg} | Signal: {self.last_signal}[/]")
self.console.print("="*60 + "\n")
async def run(self):
logger.info(f"Bot v{self.version} starting...")
await self.db.connect()
last_exchange_update = 0
with Live(self.get_dashboard(), console=self.console, refresh_per_second=1, screen=False) as live:
while True:
try:
now_ts = time.time()
now = time.time()
# 1. Exchange Sync (15s)
if now_ts - last_exchange_update >= 15:
await asyncio.wait_for(self.update_exchange_data(), timeout=10)
last_exchange_update = now_ts
if now - last_exchange_update >= 15:
await self.update_exchange_data()
last_exchange_update = now
# 2. DB Sync (5s)
candles = await self.db.get_candles(self.db_symbol, self.db_interval, limit=100)
@ -285,20 +285,14 @@ class PingPongBot:
df = df.astype({'open': float, 'high': float, 'low': float, 'close': float, 'volume': float})
df = self.calculate_indicators(df)
signal = self.check_signals(df)
if signal:
logger.info(f"SIGNAL: {signal}")
await self.execute_trade(signal)
if signal: await self.execute_trade(signal)
self.last_candle_time = latest['time']
self.last_candle_price = latest['close']
self.status_msg = f"Last Candle: {latest['time'].strftime('%H:%M:%S')}"
self.status_msg = f"New Candle: {latest['time']}"
else:
self.status_msg = "Waiting for candles..."
self.status_msg = f"No candles found for {self.db_symbol}/{self.db_interval}"
# 3. Update Dashboard
live.update(self.get_dashboard())
except asyncio.TimeoutError:
self.status_msg = "Exchange Timeout"
self.render_dashboard()
except Exception as e:
logger.error(f"Loop error: {e}")
self.status_msg = f"Error: {str(e)[:40]}"