From 57e098c25b95b38670844bd08060b52b6a544aa0 Mon Sep 17 00:00:00 2001 From: Gemini CLI Date: Thu, 5 Mar 2026 23:30:38 +0100 Subject: [PATCH] fix: refine base_coin extraction to support BTCUSD and fix DB queries (v1.5.4) --- src/strategies/ping_pong_bot.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index ca31e00..a91f07d 100644 --- a/src/strategies/ping_pong_bot.py +++ b/src/strategies/ping_pong_bot.py @@ -86,7 +86,7 @@ class DatabaseManager: class PingPongBot: def __init__(self, config_path="config/ping_pong_config.yaml"): - self.version = "1.5.3" + self.version = "1.5.4" with open(config_path, 'r') as f: self.config = yaml.safe_load(f) @@ -113,12 +113,18 @@ class PingPongBot: password=os.getenv('DB_PASSWORD', '') ) - # Base settings - self.base_coin = self.config['symbol'].upper().replace("USDT", "").replace("USDC", "") # e.g. BTC + # Base settings - Improved extraction + raw_symbol = self.config['symbol'].upper() + # Remove common suffixes to get base coin (e.g., BTCUSD -> BTC, BTCUSDT -> BTC) + self.base_coin = raw_symbol.replace("USDT", "").replace("USDC", "").replace("USD", "") + self.db_symbol = self.base_coin self.interval = str(self.config['interval']) + # Map interval to DB format: '30' -> '30m' self.db_interval = self.interval + "m" if self.interval.isdigit() else self.interval + logger.info(f"Bot v{self.version} Initialized | DB Symbol: {self.db_symbol} | DB Interval: {self.db_interval}") + # Dynamic Strategy State self.direction = None # 'long' or 'short' self.category = None # 'linear' or 'inverse' @@ -145,7 +151,6 @@ class PingPongBot: self.console = Console() # Fixed Parameters from Config - self.tp_pct = float(self.config.get('take_profit_pct', 1.5)) / 100.0 self.partial_exit_pct = float(self.config.get('partial_exit_pct', 0.15)) self.min_val_usd = float(self.config.get('min_position_value_usd', 15.0)) self.pos_size_margin = float(self.config.get('pos_size_margin', 20.0)) @@ -186,13 +191,12 @@ class PingPongBot: async def update_direction(self): """Logic Point I: 1D MA44 check and Point II: Asset/Perp selection""" try: - logger.info("Checking direction based on SMA(44, 1D)...") - # Increase limit to ensure we get enough data even with potential gaps + logger.info(f"Checking direction based on SMA(44, 1D) for {self.db_symbol}...") candles_1d = await self.db.get_candles(self.db_symbol, "1d", limit=100) if not candles_1d or len(candles_1d) < 44: got = len(candles_1d) if candles_1d else 0 - logger.warning(f"Not enough 1D data for MA44. Got {got} candles for {self.db_symbol}.") + logger.warning(f"Not enough 1D data for MA44. Got {got} candles for {self.db_symbol} / 1d.") self.status_msg = f"Error: Need 44 1D candles (Got {got})" return False @@ -214,14 +218,11 @@ class PingPongBot: logger.info(f"DIRECTION CHANGE: {self.direction} -> {new_direction} (Price: {current_price:.2f}, MA44: {self.ma_44_val:.2f})") self.status_msg = f"Switching to {new_direction.upper()}" - # 1. Close all positions (Point III.3) if self.direction is not None: await self.close_all_positions() - # 2. Swap Assets on Spot (Point II) await self.swap_assets(new_direction) - # 3. Update configuration self.direction = new_direction if self.direction == "long": self.category = "inverse" @@ -231,7 +232,7 @@ class PingPongBot: self.symbol = f"{self.base_coin}USDC" logger.info(f"Bot configured for {self.direction.upper()} | Symbol: {self.symbol} | Category: {self.category}") - self.last_candle_time = None # Force indicator recalculation + self.last_candle_time = None return True return False @@ -276,7 +277,6 @@ class PingPongBot: usdc_bal = coins.get("USDC", 0) if usdc_bal > 1.0: logger.info(f"Spot: Buying {self.base_coin} with {usdc_bal} USDC") - # Spot Market Buy using orderAmount (spending USDC) await asyncio.to_thread(self.session.place_order, category="spot", symbol=spot_symbol, side="Buy", orderType="Market", qty=str(usdc_bal), marketUnit="quote" @@ -316,7 +316,6 @@ class PingPongBot: last, prev = df.iloc[-1], df.iloc[-2] rsi_cfg, hurst_cfg = self.config['rsi'], self.config['hurst'] - # Signals defined by crossover 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 \