diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index 22bffb7..65f049b 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.5.0" + self.version = "1.5.1" with open(config_path, 'r') as f: self.config = yaml.safe_load(f) @@ -170,8 +170,11 @@ class PingPongBot: df_1d['close'] = df_1d['close'].astype(float) self.ma_44_val = df_1d['close'].rolling(window=44).mean().iloc[-1] - # Get current price from exchange - ticker = await asyncio.to_thread(self.session.get_tickers, category="linear", symbol=f"{self.base_coin}USDC") + # Use BTCUSDT for price check if not initialized, otherwise use current symbol + ticker_symbol = self.symbol if self.symbol else f"{self.base_coin}USDT" + ticker_cat = self.category if self.category else "linear" + + ticker = await asyncio.to_thread(self.session.get_tickers, category=ticker_cat, symbol=ticker_symbol) current_price = float(ticker['result']['list'][0]['lastPrice']) self.market_price = current_price @@ -210,6 +213,7 @@ class PingPongBot: async def close_all_positions(self): """Closes any active position in the current category/symbol""" try: + if not self.category or not self.symbol: return pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol) if pos['retCode'] == 0: for p in pos['result']['list']: @@ -254,12 +258,15 @@ class PingPongBot: async def update_exchange_data(self): """Fetch Price, Balance, Position every 15s""" + if not self.category or not self.symbol: return try: ticker = await asyncio.to_thread(self.session.get_tickers, category=self.category, 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=self.category, symbol=self.symbol, settleCoin="USDT" if self.category == "linear" else None) + # settleCoin is only for linear perpetuals + settle_coin = "USDC" if self.category == "linear" else None + pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol, settleCoin=settle_coin) if pos['retCode'] == 0: active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0] self.position = active[0] if active else None @@ -270,13 +277,12 @@ class PingPongBot: if wallet['retCode'] == 0: res_list = wallet['result']['list'] if res_list: - # In inverse, we value in BTC, but dashboard usually shows USD. - # We'll stick to totalWalletBalance which is usually USD-equiv in UTA self.wallet_balance = float(res_list[0].get('totalWalletBalance', 0)) except Exception as e: logger.error(f"Exchange Sync Error: {e}") def check_signals(self, df): + if len(df) < 2: return None last, prev = df.iloc[-1], df.iloc[-2] rsi_cfg, hurst_cfg = self.config['rsi'], self.config['hurst'] @@ -297,7 +303,7 @@ class PingPongBot: return "open" if s_open else ("close" if s_close else None) async def execute_trade(self, signal): - if not signal: return + if not signal or not self.market_price: return last_price = self.market_price if signal == "close" and self.position: @@ -308,15 +314,14 @@ class PingPongBot: elif signal == "open": cur_qty = float(self.position['size']) if self.position else 0 - # Notional calculation differs between Linear and Inverse if self.category == "linear": cur_notional = cur_qty * last_price ping_notional = self.pos_size_margin * self.leverage qty_to_open = ping_notional / last_price else: # Inverse - cur_notional = cur_qty # Inverse size is in USD usually, but Bybit V5 size is in contracts (USD) + cur_notional = cur_qty ping_notional = self.pos_size_margin * self.leverage - qty_to_open = ping_notional # For Inverse BTCUSD, Qty is USD amount + qty_to_open = ping_notional if (cur_notional + ping_notional) / max(self.wallet_balance, 1) <= self.max_eff_lev: await self.place_order(qty_to_open, is_close=False) @@ -324,10 +329,10 @@ class PingPongBot: self.status_msg = "Max Leverage Reached" async def place_order(self, qty, is_close=False): + if not self.category or not self.symbol: return 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: - # Rounding: Linear BTC needs 3 decimals, Inverse BTCUSD needs integers (USD) qty_str = str(int(qty)) if self.category == "inverse" else str(round(qty, 3)) res = await asyncio.to_thread(self.session.place_order, @@ -371,14 +376,14 @@ class PingPongBot: async def run(self): await self.db.connect() - await self.update_direction() # Initial point I + await self.update_direction() last_exchange_update = 0 while True: try: now = time.time() - # 1. Periodically check direction (every 2m - Point III.2) + # 1. Periodically check direction (every 2m) if now - self.last_ma_check_time >= 120: await self.update_direction() self.last_ma_check_time = now