From cf0ccbcad54fe10c86ff97222d9f6c3f6577d94d Mon Sep 17 00:00:00 2001 From: Gemini CLI Date: Thu, 5 Mar 2026 22:50:40 +0100 Subject: [PATCH] revert: remove all blinking related Live UI changes (v1.4.1) --- src/strategies/ping_pong_bot.py | 142 +++++++++++++++----------------- 1 file changed, 68 insertions(+), 74 deletions(-) diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index db0950c..ed14161 100644 --- a/src/strategies/ping_pong_bot.py +++ b/src/strategies/ping_pong_bot.py @@ -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,79 +240,64 @@ 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})") - - # 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") + 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']) + + 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() - - # 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 - - # 2. DB Sync (5s) - candles = await self.db.get_candles(self.db_symbol, self.db_interval, limit=100) - if candles: - latest = candles[0] - if latest['time'] != self.last_candle_time: - df = pd.DataFrame(candles[::-1]) - 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) - self.last_candle_time = latest['time'] - self.last_candle_price = latest['close'] - self.status_msg = f"Last Candle: {latest['time'].strftime('%H:%M:%S')}" - else: - self.status_msg = "Waiting for candles..." - - # 3. Update Dashboard - live.update(self.get_dashboard()) - - except asyncio.TimeoutError: - self.status_msg = "Exchange Timeout" - except Exception as e: - logger.error(f"Loop error: {e}") - self.status_msg = f"Error: {str(e)[:40]}" + while True: + try: + now = time.time() + # 1. Exchange Sync (15s) + if now - last_exchange_update >= 15: + await self.update_exchange_data() + last_exchange_update = now - await asyncio.sleep(5) + # 2. DB Sync (5s) + candles = await self.db.get_candles(self.db_symbol, self.db_interval, limit=100) + if candles: + latest = candles[0] + if latest['time'] != self.last_candle_time: + df = pd.DataFrame(candles[::-1]) + 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: await self.execute_trade(signal) + self.last_candle_time = latest['time'] + self.last_candle_price = latest['close'] + self.status_msg = f"New Candle: {latest['time']}" + else: + self.status_msg = f"No candles found for {self.db_symbol}/{self.db_interval}" + + self.render_dashboard() + except Exception as e: + logger.error(f"Loop error: {e}") + self.status_msg = f"Error: {str(e)[:40]}" + + await asyncio.sleep(5) if __name__ == "__main__": bot = PingPongBot()