diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index dff2942..acf5c17 100644 --- a/src/strategies/ping_pong_bot.py +++ b/src/strategies/ping_pong_bot.py @@ -86,10 +86,11 @@ class DatabaseManager: class PingPongBot: def __init__(self, config_path="config/ping_pong_config.yaml"): - self.version = "1.5.5" + self.version = "1.5.6" with open(config_path, 'r') as f: self.config = yaml.safe_load(f) + # Explicitly load from ENV to ensure they are available self.api_key = os.getenv("BYBIT_API_KEY") or os.getenv("API_KEY") self.api_secret = os.getenv("BYBIT_API_SECRET") or os.getenv("API_SECRET") @@ -103,6 +104,7 @@ class PingPongBot: timeout=10 ) + # Initialize DB with explicit credentials self.db = DatabaseManager( host=os.getenv('DB_HOST', '20.20.20.20'), port=os.getenv('DB_PORT', 5433), @@ -111,6 +113,7 @@ class PingPongBot: password=os.getenv('DB_PASSWORD', '') ) + # Base settings raw_symbol = self.config['symbol'].upper() self.base_coin = raw_symbol.replace("USDT", "").replace("USDC", "").replace("USD", "") self.db_symbol = self.base_coin @@ -143,6 +146,7 @@ class PingPongBot: self.start_time = datetime.now() self.console = Console() + # Fixed Parameters from Config 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)) @@ -154,12 +158,14 @@ 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)) @@ -179,6 +185,7 @@ class PingPongBot: return df async def update_direction(self): + """Logic Point I: 1D MA44 check and Point II: Asset/Perp selection""" try: 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) @@ -206,7 +213,9 @@ class PingPongBot: if self.direction is not None: await self.close_all_positions() - await self.swap_assets(new_direction) + # Update settings before swap to ensure we trade the right symbol/category if needed + old_symbol = self.symbol + old_category = self.category self.direction = new_direction if self.direction == "long": @@ -215,10 +224,12 @@ class PingPongBot: self.settle_coin = self.base_coin else: self.category = "linear" - # BTCPERP is the Bybit symbol for USDC Linear Perpetual (BTCUSDC display name) self.symbol = "BTCPERP" if self.base_coin == "BTC" else f"{self.base_coin}USDC" self.settle_coin = "USDC" + # Perform swap + await self.swap_assets(new_direction) + logger.info(f"Bot configured for {self.direction.upper()} | Symbol: {self.symbol} | Category: {self.category}") self.last_candle_time = None return True @@ -242,30 +253,38 @@ class PingPongBot: logger.error(f"Error closing positions: {e}") async def swap_assets(self, target_direction): + """Point II: Exchange BTC/USDC on Spot market using UNIFIED account type""" try: logger.info(f"Swapping assets for {target_direction.upper()} mode...") spot_symbol = f"{self.base_coin}USDC" - balance = await asyncio.to_thread(self.session.get_wallet_balance, category="spot", coin=f"{self.base_coin},USDC") + # Use accountType='UNIFIED' for UTA accounts + balance = await asyncio.to_thread(self.session.get_wallet_balance, accountType="UNIFIED", coin=f"{self.base_coin},USDC") + coins = {c['coin']: float(c['walletBalance']) for c in balance['result']['list'][0]['coin']} + logger.info(f"Current Balances: {coins}") if target_direction == "short": + # SHORT: Need USDC, Sell BTC btc_bal = coins.get(self.base_coin, 0) if btc_bal > 0.0001: logger.info(f"Spot: Selling {btc_bal} {self.base_coin} for USDC") - await asyncio.to_thread(self.session.place_order, + res = await asyncio.to_thread(self.session.place_order, category="spot", symbol=spot_symbol, side="Sell", orderType="Market", qty=str(btc_bal) ) + logger.info(f"Swap Result: {res['retMsg']}") else: + # LONG: Need BTC, Buy BTC with USDC usdc_bal = coins.get("USDC", 0) if usdc_bal > 1.0: logger.info(f"Spot: Buying {self.base_coin} with {usdc_bal} USDC") - await asyncio.to_thread(self.session.place_order, + res = await asyncio.to_thread(self.session.place_order, category="spot", symbol=spot_symbol, side="Buy", orderType="Market", qty=str(usdc_bal), marketUnit="quote" ) + logger.info(f"Swap Result: {res['retMsg']}") - await asyncio.sleep(2) + await asyncio.sleep(3) # Wait for spot settlement except Exception as e: logger.error(f"Asset Swap Error: {e}") @@ -281,8 +300,7 @@ class PingPongBot: active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0] self.position = active[0] if active else None - target_coin = self.settle_coin - wallet = await asyncio.to_thread(self.session.get_wallet_balance, category=self.category, accountType="UNIFIED", coin=target_coin) + wallet = await asyncio.to_thread(self.session.get_wallet_balance, category=self.category, accountType="UNIFIED", coin=self.settle_coin) if wallet['retCode'] == 0: res_list = wallet['result']['list'] if res_list: