Files
aitbc/apps/exchange/real_exchange_integration.py
oib 15427c96c0 chore: update file permissions to executable across repository
- Change file mode from 644 to 755 for all project files
- Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet"
- Rename Miner.extra_meta_data to extra_metadata for consistency
2026-03-06 22:17:54 +01:00

329 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Real Exchange Integration for AITBC
Connects to Binance, Coinbase, and Kraken APIs for live trading
"""
import asyncio
import ccxt
import json
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass
from enum import Enum
import logging
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ExchangeStatus(str, Enum):
"""Exchange connection status"""
CONNECTED = "connected"
DISCONNECTED = "disconnected"
ERROR = "error"
MAINTENANCE = "maintenance"
class OrderSide(str, Enum):
"""Order side"""
BUY = "buy"
SELL = "sell"
@dataclass
class ExchangeCredentials:
"""Exchange API credentials"""
api_key: str
secret: str
sandbox: bool = True
passphrase: Optional[str] = None # For Coinbase
@dataclass
class ExchangeHealth:
"""Exchange health metrics"""
status: ExchangeStatus
latency_ms: float
last_check: datetime
error_message: Optional[str] = None
@dataclass
class OrderRequest:
"""Unified order request"""
exchange: str
symbol: str
side: OrderSide
amount: float
price: Optional[float] = None # None for market orders
type: str = "limit" # limit, market
class RealExchangeManager:
"""Manages connections to real exchanges"""
def __init__(self):
self.exchanges: Dict[str, ccxt.Exchange] = {}
self.credentials: Dict[str, ExchangeCredentials] = {}
self.health_status: Dict[str, ExchangeHealth] = {}
self.supported_exchanges = ["binance", "coinbasepro", "kraken"]
async def connect_exchange(self, exchange_name: str, credentials: ExchangeCredentials) -> bool:
"""Connect to an exchange"""
try:
if exchange_name not in self.supported_exchanges:
raise ValueError(f"Unsupported exchange: {exchange_name}")
# Create exchange instance
if exchange_name == "binance":
exchange = ccxt.binance({
'apiKey': credentials.api_key,
'secret': credentials.secret,
'sandbox': credentials.sandbox,
'enableRateLimit': True,
})
elif exchange_name == "coinbasepro":
exchange = ccxt.coinbasepro({
'apiKey': credentials.api_key,
'secret': credentials.secret,
'passphrase': credentials.passphrase,
'sandbox': credentials.sandbox,
'enableRateLimit': True,
})
elif exchange_name == "kraken":
exchange = ccxt.kraken({
'apiKey': credentials.api_key,
'secret': credentials.secret,
'sandbox': credentials.sandbox,
'enableRateLimit': True,
})
# Test connection
await self._test_connection(exchange, exchange_name)
# Store connection
self.exchanges[exchange_name] = exchange
self.credentials[exchange_name] = credentials
# Set initial health status
self.health_status[exchange_name] = ExchangeHealth(
status=ExchangeStatus.CONNECTED,
latency_ms=0.0,
last_check=datetime.utcnow()
)
logger.info(f"✅ Connected to {exchange_name}")
return True
except Exception as e:
logger.error(f"❌ Failed to connect to {exchange_name}: {str(e)}")
self.health_status[exchange_name] = ExchangeHealth(
status=ExchangeStatus.ERROR,
latency_ms=0.0,
last_check=datetime.utcnow(),
error_message=str(e)
)
return False
async def _test_connection(self, exchange: ccxt.Exchange, exchange_name: str):
"""Test exchange connection"""
start_time = time.time()
try:
# Test with fetchMarkets (lightweight call)
if hasattr(exchange, 'load_markets'):
if asyncio.iscoroutinefunction(exchange.load_markets):
await exchange.load_markets()
else:
exchange.load_markets()
latency = (time.time() - start_time) * 1000
logger.info(f"🔗 {exchange_name} connection test successful ({latency:.2f}ms)")
except Exception as e:
raise Exception(f"Connection test failed: {str(e)}")
async def disconnect_exchange(self, exchange_name: str) -> bool:
"""Disconnect from an exchange"""
try:
if exchange_name in self.exchanges:
del self.exchanges[exchange_name]
del self.credentials[exchange_name]
self.health_status[exchange_name] = ExchangeHealth(
status=ExchangeStatus.DISCONNECTED,
latency_ms=0.0,
last_check=datetime.now()
)
logger.info(f"🔌 Disconnected from {exchange_name}")
return True
else:
logger.warning(f"⚠️ {exchange_name} was not connected")
return False
except Exception as e:
logger.error(f"❌ Failed to disconnect from {exchange_name}: {str(e)}")
return False
async def check_exchange_health(self, exchange_name: str) -> ExchangeHealth:
"""Check exchange health and latency"""
if exchange_name not in self.exchanges:
return ExchangeHealth(
status=ExchangeStatus.DISCONNECTED,
latency_ms=0.0,
last_check=datetime.now(),
error_message="Not connected"
)
try:
start_time = time.time()
exchange = self.exchanges[exchange_name]
# Lightweight health check
if hasattr(exchange, 'fetch_status'):
if asyncio.iscoroutinefunction(exchange.fetch_status):
await exchange.fetch_status()
else:
exchange.fetch_status()
latency = (time.time() - start_time) * 1000
health = ExchangeHealth(
status=ExchangeStatus.CONNECTED,
latency_ms=latency,
last_check=datetime.now()
)
self.health_status[exchange_name] = health
return health
except Exception as e:
health = ExchangeHealth(
status=ExchangeStatus.ERROR,
latency_ms=0.0,
last_check=datetime.now(),
error_message=str(e)
)
self.health_status[exchange_name] = health
return health
async def get_all_health_status(self) -> Dict[str, ExchangeHealth]:
"""Get health status of all connected exchanges"""
for exchange_name in list(self.exchanges.keys()):
await self.check_exchange_health(exchange_name)
return self.health_status
async def place_order(self, order_request: OrderRequest) -> Dict[str, Any]:
"""Place an order on the specified exchange"""
try:
if order_request.exchange not in self.exchanges:
raise ValueError(f"Exchange {order_request.exchange} not connected")
exchange = self.exchanges[order_request.exchange]
# Prepare order parameters
order_params = {
'symbol': order_request.symbol,
'type': order_request.type,
'side': order_request.side.value,
'amount': order_request.amount,
}
if order_request.type == 'limit' and order_request.price:
order_params['price'] = order_request.price
# Place order
order = await exchange.create_order(**order_params)
logger.info(f"📈 Order placed on {order_request.exchange}: {order['id']}")
return order
except Exception as e:
logger.error(f"❌ Failed to place order: {str(e)}")
raise
async def get_order_book(self, exchange_name: str, symbol: str, limit: int = 20) -> Dict[str, Any]:
"""Get order book for a symbol"""
try:
if exchange_name not in self.exchanges:
raise ValueError(f"Exchange {exchange_name} not connected")
exchange = self.exchanges[exchange_name]
orderbook = await exchange.fetch_order_book(symbol, limit)
return orderbook
except Exception as e:
logger.error(f"❌ Failed to get order book: {str(e)}")
raise
async def get_balance(self, exchange_name: str) -> Dict[str, Any]:
"""Get account balance"""
try:
if exchange_name not in self.exchanges:
raise ValueError(f"Exchange {exchange_name} not connected")
exchange = self.exchanges[exchange_name]
balance = await exchange.fetch_balance()
return balance
except Exception as e:
logger.error(f"❌ Failed to get balance: {str(e)}")
raise
# Global instance
exchange_manager = RealExchangeManager()
# CLI Interface Functions
async def connect_to_exchange(exchange_name: str, api_key: str, secret: str,
sandbox: bool = True, passphrase: str = None) -> bool:
"""CLI function to connect to exchange"""
credentials = ExchangeCredentials(
api_key=api_key,
secret=secret,
sandbox=sandbox,
passphrase=passphrase
)
return await exchange_manager.connect_exchange(exchange_name, credentials)
async def disconnect_from_exchange(exchange_name: str) -> bool:
"""CLI function to disconnect from exchange"""
return await exchange_manager.disconnect_exchange(exchange_name)
async def get_exchange_status(exchange_name: str = None) -> Dict[str, Any]:
"""CLI function to get exchange status"""
if exchange_name:
health = await exchange_manager.check_exchange_health(exchange_name)
return {exchange_name: health}
else:
return await exchange_manager.get_all_health_status()
# Test function
async def test_real_exchange_integration():
"""Test the real exchange integration"""
print("🧪 Testing Real Exchange Integration...")
# Test with Binance sandbox
test_credentials = ExchangeCredentials(
api_key="test_api_key",
secret="test_secret",
sandbox=True
)
try:
# This will fail with test credentials, but tests the structure
success = await exchange_manager.connect_exchange("binance", test_credentials)
print(f"Connection test result: {success}")
# Get health status
health = await exchange_manager.check_exchange_health("binance")
print(f"Health status: {health}")
except Exception as e:
print(f"Expected error with test credentials: {str(e)}")
print("✅ Integration structure working correctly")
if __name__ == "__main__":
asyncio.run(test_real_exchange_integration())