From ca0e74d4023e582a155aa4ba2d3fde22d4289d93 Mon Sep 17 00:00:00 2001 From: Gemini CLI Date: Thu, 5 Mar 2026 22:37:28 +0100 Subject: [PATCH] fix: resolve pybit TypeError and implement async timeouts (v1.3.7) --- src/strategies/ping_pong_bot.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index ba499d7..c452bf5 100644 --- a/src/strategies/ping_pong_bot.py +++ b/src/strategies/ping_pong_bot.py @@ -68,7 +68,7 @@ class DatabaseManager: class PingPongBot: def __init__(self, config_path="config/ping_pong_config.yaml"): - self.version = "1.3.6" + self.version = "1.3.7" with open(config_path, 'r') as f: self.config = yaml.safe_load(f) @@ -78,12 +78,10 @@ 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") - # Added timeout to prevent hanging self.session = HTTP( testnet=False, api_key=self.api_key, - api_secret=self.api_secret, - request_timeout=10 + api_secret=self.api_secret ) self.db = DatabaseManager() @@ -159,17 +157,19 @@ class PingPongBot: return df async def update_exchange_data(self): + """Fetch Price, Balance, Position every 15s with timeout""" try: - ticker = self.session.get_tickers(category="linear", symbol=self.symbol) + # Wrap synchronous pybit calls in asyncio.to_thread for better async behavior + 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 = self.session.get_positions(category="linear", symbol=self.symbol, settleCoin="USDT") + 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] self.position = active[0] if active else None - wallet = self.session.get_wallet_balance(category="linear", accountType="UNIFIED", coin="USDT") + wallet = await asyncio.to_thread(self.session.get_wallet_balance, category="linear", accountType="UNIFIED", coin="USDT") if wallet['retCode'] == 0: result_list = wallet['result']['list'] if result_list: @@ -222,7 +222,7 @@ class PingPongBot: side = "Sell" if (self.direction == "long" and is_close) or (self.direction == "short" and not is_close) else "Buy" pos_idx = 1 if self.direction == "long" else 2 try: - res = self.session.place_order( + res = await asyncio.to_thread(self.session.place_order, category="linear", symbol=self.symbol, side=side, orderType="Market", qty=str(round(qty, 3)), reduceOnly=is_close, positionIdx=pos_idx ) @@ -235,8 +235,8 @@ class PingPongBot: logger.error(f"Trade Error: {e}") def print_dashboard(self): - """Prints a clean summary to the logs (replaces Live which can hang in Docker)""" - now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """Prints a clean summary to the logs""" + now = datetime.now().strftime("%H:%M:%S") self.last_heartbeat = datetime.now() # 1. Header @@ -264,6 +264,7 @@ class PingPongBot: pos.add_row("Entry Price", f"${float(self.position['avgPrice']):,.2f}") pos.add_row("Unrealized PnL", f"[bold {'green' if pnl>=0 else 'red'}]${pnl:,.2f}") else: + pos_row = "NONE" pos.add_row("Position", "NONE") pos.add_row("Status", f"[bold blue]{self.status_msg}[/]") @@ -287,7 +288,8 @@ class PingPongBot: # 1. Exchange Sync (15s) if now_ts - last_exchange_update >= 15: - await self.update_exchange_data() + # Wrapped in timeout to prevent hanging + await asyncio.wait_for(self.update_exchange_data(), timeout=10) last_exchange_update = now_ts # 2. DB Sync (5s) @@ -311,6 +313,9 @@ class PingPongBot: # 3. Print Dashboard self.print_dashboard() + except asyncio.TimeoutError: + logger.error("Exchange update timed out") + self.status_msg = "Exchange Update Timeout" except Exception as e: logger.exception(f"Loop Error: {e}") self.status_msg = f"Error: {str(e)[:50]}"