From 5b2dd3b0b826b4acdff3ce0c3e8f5ff442ad1aaf Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 14 Jul 2025 23:20:01 +0300 Subject: [PATCH] bybit ballance working --- .../bybit/debug/test_bybit_balance.py | 81 +++++++++++++++++++ NN/exchanges/bybit_interface.py | 42 +++++++++- core/trading_executor.py | 75 ++++++++--------- 3 files changed, 152 insertions(+), 46 deletions(-) create mode 100644 NN/exchanges/bybit/debug/test_bybit_balance.py diff --git a/NN/exchanges/bybit/debug/test_bybit_balance.py b/NN/exchanges/bybit/debug/test_bybit_balance.py new file mode 100644 index 0000000..a210fa3 --- /dev/null +++ b/NN/exchanges/bybit/debug/test_bybit_balance.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +import os +import sys +import asyncio +from pathlib import Path + +# Add project root to path +project_root = Path(__file__).parent +sys.path.insert(0, str(project_root)) + +from NN.exchanges.bybit_interface import BybitInterface + +async def test_bybit_balance(): + """Test if we can read real balance from Bybit""" + + print("Testing Bybit Balance Reading...") + print("=" * 50) + + # Initialize Bybit interface + bybit = BybitInterface() + + try: + # Connect to Bybit + print("Connecting to Bybit...") + success = await bybit.connect() + + if not success: + print("ERROR: Failed to connect to Bybit") + return + + print("✓ Connected to Bybit successfully") + + # Test get_balance for USDT + print("\nTesting get_balance('USDT')...") + usdt_balance = await bybit.get_balance('USDT') + print(f"USDT Balance: {usdt_balance}") + + # Test get_all_balances + print("\nTesting get_all_balances()...") + all_balances = await bybit.get_all_balances() + print(f"All Balances: {all_balances}") + + # Check if we have any non-zero balances + print("\nBalance Analysis:") + if isinstance(all_balances, dict): + for symbol, balance in all_balances.items(): + if isinstance(balance, (int, float)) and balance > 0: + print(f" {symbol}: {balance}") + elif isinstance(balance, dict): + # Handle nested balance structure + total = balance.get('total', 0) or balance.get('available', 0) + if total > 0: + print(f" {symbol}: {total}") + + # Test account info if available + print("\nTesting account info...") + try: + if hasattr(bybit, 'client') and bybit.client: + # Try to get account info + account_info = bybit.client.get_wallet_balance(accountType="UNIFIED") + print(f"Account Info: {account_info}") + except Exception as e: + print(f"Account info error: {e}") + + except Exception as e: + print(f"ERROR: {e}") + import traceback + traceback.print_exc() + + finally: + # Cleanup + if hasattr(bybit, 'client') and bybit.client: + try: + await bybit.client.close() + except: + pass + +if __name__ == "__main__": + # Run the test + asyncio.run(test_bybit_balance()) \ No newline at end of file diff --git a/NN/exchanges/bybit_interface.py b/NN/exchanges/bybit_interface.py index a106268..b182bd6 100644 --- a/NN/exchanges/bybit_interface.py +++ b/NN/exchanges/bybit_interface.py @@ -168,7 +168,20 @@ class BybitInterface(ExchangeInterface): coins = account.get('coin', []) for coin in coins: if coin.get('coin', '').upper() == asset.upper(): - available_balance = float(coin.get('availableToWithdraw', 0)) + # Try availableToWithdraw first, then equity, then walletBalance + available_str = coin.get('availableToWithdraw', '') + if available_str: + available_balance = float(available_str) + else: + # Use equity if availableToWithdraw is empty + equity_str = coin.get('equity', '') + if equity_str: + available_balance = float(equity_str) + else: + # Fall back to walletBalance + wallet_str = coin.get('walletBalance', '0') + available_balance = float(wallet_str) if wallet_str else 0.0 + logger.debug(f"Balance for {asset}: {available_balance}") return available_balance @@ -198,6 +211,14 @@ class BybitInterface(ExchangeInterface): except Exception as e: logger.error(f"Error getting account summary: {e}") return {} + + def get_account_info(self) -> Dict[str, Any]: + """Get account information (alias for get_account_summary for compatibility). + + Returns: + Dictionary with account information + """ + return self.get_account_summary() def get_all_balances(self) -> Dict[str, Dict[str, float]]: """Get all account balances in the format expected by trading executor. @@ -217,8 +238,23 @@ class BybitInterface(ExchangeInterface): asset = coin.get('coin', '') if asset: # Convert Bybit balance format to MEXC-compatible format - available = float(coin.get('availableToWithdraw', 0)) - locked = float(coin.get('locked', 0)) + # Handle empty string values that cause conversion errors + available_str = coin.get('availableToWithdraw', '') + locked_str = coin.get('locked', '') + equity_str = coin.get('equity', '') + wallet_str = coin.get('walletBalance', '') + + # Use equity or walletBalance if availableToWithdraw is empty + if available_str: + available = float(available_str) + elif equity_str: + available = float(equity_str) + elif wallet_str: + available = float(wallet_str) + else: + available = 0.0 + + locked = float(locked_str) if locked_str else 0.0 balances[asset] = { 'free': available, diff --git a/core/trading_executor.py b/core/trading_executor.py index 6a3fa08..2f10019 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -99,6 +99,10 @@ class TradingExecutor: self.primary_name = self.exchanges_config.get('primary', 'mexc') self.primary_config = self.exchanges_config.get(self.primary_name, {}) + # Set exchange config for compatibility (replaces mexc_config) + self.exchange_config = self.primary_config + self.mexc_config = self.primary_config # Legacy compatibility + # Initialize config synchronizer with the primary exchange self.config_sync = ConfigSynchronizer( config_path=config_path, @@ -1166,14 +1170,13 @@ class TradingExecutor: logger.info("Daily trading statistics reset") def get_account_balance(self) -> Dict[str, Dict[str, float]]: - """Get account balance information from MEXC, including spot and futures. + """Get account balance information from the primary exchange (universal method). Returns: Dict with asset balances in format: { 'USDT': {'free': 100.0, 'locked': 0.0, 'total': 100.0, 'type': 'spot'}, 'ETH': {'free': 0.5, 'locked': 0.0, 'total': 0.5, 'type': 'spot'}, - 'FUTURES_USDT': {'free': 500.0, 'locked': 50.0, 'total': 550.0, 'type': 'futures'} ... } """ @@ -1182,47 +1185,29 @@ class TradingExecutor: logger.error("Exchange interface not available") return {} - combined_balances = {} - - # 1. Get Spot Account Info - spot_account_info = self.exchange.get_account_info() - if spot_account_info and 'balances' in spot_account_info: - for balance in spot_account_info['balances']: - asset = balance.get('asset', '') - free = float(balance.get('free', 0)) - locked = float(balance.get('locked', 0)) - if free > 0 or locked > 0: - combined_balances[asset] = { - 'free': free, - 'locked': locked, - 'total': free + locked, - 'type': 'spot' - } + # Use the universal get_all_balances method that works with all exchanges + if hasattr(self.exchange, 'get_all_balances'): + raw_balances = self.exchange.get_all_balances() + if raw_balances: + # Convert to the expected format with 'type' field + combined_balances = {} + for asset, balance_data in raw_balances.items(): + if isinstance(balance_data, dict): + combined_balances[asset] = { + 'free': balance_data.get('free', 0.0), + 'locked': balance_data.get('locked', 0.0), + 'total': balance_data.get('total', 0.0), + 'type': 'spot' # Default to spot for now + } + + logger.info(f"Retrieved balances for {len(combined_balances)} assets from {self.primary_name}") + return combined_balances + else: + logger.warning(f"No balances returned from {self.primary_name} exchange") + return {} else: - logger.warning("Failed to get spot account info from MEXC or no balances found.") - - # 2. Get Futures Account Info (commented out until futures API is implemented) - # futures_account_info = self.exchange.get_futures_account_info() - # if futures_account_info: - # for currency, asset_data in futures_account_info.items(): - # # MEXC Futures API returns 'availableBalance' and 'frozenBalance' - # free = float(asset_data.get('availableBalance', 0)) - # locked = float(asset_data.get('frozenBalance', 0)) - # total = free + locked # total is the sum of available and frozen - # if free > 0 or locked > 0: - # # Prefix with 'FUTURES_' to distinguish from spot, or decide on a unified key - # # For now, let's keep them distinct for clarity - # combined_balances[f'FUTURES_{currency}'] = { - # 'free': free, - # 'locked': locked, - # 'total': total, - # 'type': 'futures' - # } - # else: - # logger.warning("Failed to get futures account info from MEXC or no futures assets found.") - - logger.info(f"Retrieved combined balances for {len(combined_balances)} assets.") - return combined_balances + logger.error(f"Exchange {self.primary_name} does not support get_all_balances method") + return {} except Exception as e: logger.error(f"Error getting account balance: {e}") @@ -1426,7 +1411,11 @@ class TradingExecutor: if sync_result.get('changes_made'): logger.info("TRADING EXECUTOR: Reloading config after fee sync") self.config = get_config(self.config_synchronizer.config_path) - self.mexc_config = self.config.get('mexc_trading', {}) + # Update to use primary exchange config + self.exchanges_config = self.config.get('exchanges', {}) + self.primary_name = self.exchanges_config.get('primary', 'mexc') + self.primary_config = self.exchanges_config.get(self.primary_name, {}) + self.mexc_config = self.primary_config # Legacy compatibility return sync_result