14. finishing connectors
This commit is contained in:
@ -134,6 +134,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- Write integration tests with existing orchestrator code
|
- Write integration tests with existing orchestrator code
|
||||||
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
|
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import json
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Dict, List, Optional, Callable, Any
|
from typing import Dict, List, Optional, Callable, Any
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from enmodels.core import ConnectionStatus, OrderBookSnapshot, TradeEvent
|
from ..models.core import ConnectionStatus, OrderBookSnapshot, TradeEvent
|
||||||
from ..utils.logging import get_logger, set_correlation_id
|
from ..utils.logging import get_logger, set_correlation_id
|
||||||
from ..utils.exceptions import ConnectionError, ValidationError
|
from ..utils.exceptions import ConnectionError, ValidationError
|
||||||
from ..utils.timing import get_current_timestamp
|
from ..utils.timing import get_current_timestamp
|
||||||
|
@ -43,7 +43,8 @@ class BitfinexConnector(BaseExchangeConnector):
|
|||||||
'subscribed': self._handle_subscription_response,
|
'subscribed': self._handle_subscription_response,
|
||||||
'unsubscribed': self._handle_unsubscription_response,
|
'unsubscribed': self._handle_unsubscription_response,
|
||||||
'error': self._handle_error_message,
|
'error': self._handle_error_message,
|
||||||
'info': self._handle_info_message
|
'info': self._handle_info_message,
|
||||||
|
'data': self._handle_data_message
|
||||||
})
|
})
|
||||||
|
|
||||||
# Channel management
|
# Channel management
|
||||||
@ -150,13 +151,75 @@ class BitfinexConnector(BaseExchangeConnector):
|
|||||||
|
|
||||||
async def unsubscribe_orderbook(self, symbol: str) -> None:
|
async def unsubscribe_orderbook(self, symbol: str) -> None:
|
||||||
"""Unsubscribe from order book updates."""
|
"""Unsubscribe from order book updates."""
|
||||||
# Implementation would find the channel ID and send unsubscribe message
|
try:
|
||||||
pass
|
bitfinex_symbol = self.normalize_symbol(symbol)
|
||||||
|
|
||||||
|
# Find channel ID for this symbol's order book
|
||||||
|
channel_id = None
|
||||||
|
for cid, info in self.channels.items():
|
||||||
|
if info.get('channel') == 'book' and info.get('symbol') == bitfinex_symbol:
|
||||||
|
channel_id = cid
|
||||||
|
break
|
||||||
|
|
||||||
|
if channel_id:
|
||||||
|
unsubscription_msg = {
|
||||||
|
"event": "unsubscribe",
|
||||||
|
"chanId": channel_id
|
||||||
|
}
|
||||||
|
|
||||||
|
success = await self._send_message(unsubscription_msg)
|
||||||
|
if success:
|
||||||
|
if symbol in self.subscriptions and 'orderbook' in self.subscriptions[symbol]:
|
||||||
|
self.subscriptions[symbol].remove('orderbook')
|
||||||
|
if not self.subscriptions[symbol]:
|
||||||
|
del self.subscriptions[symbol]
|
||||||
|
|
||||||
|
self.subscribed_symbols.discard(bitfinex_symbol)
|
||||||
|
logger.info(f"Unsubscribed from order book for {symbol} on Bitfinex")
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed to unsubscribe from order book for {symbol} on Bitfinex")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No active order book subscription found for {symbol}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error unsubscribing from order book for {symbol}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
async def unsubscribe_trades(self, symbol: str) -> None:
|
async def unsubscribe_trades(self, symbol: str) -> None:
|
||||||
"""Unsubscribe from trade updates."""
|
"""Unsubscribe from trade updates."""
|
||||||
# Implementation would find the channel ID and send unsubscribe message
|
try:
|
||||||
pass
|
bitfinex_symbol = self.normalize_symbol(symbol)
|
||||||
|
|
||||||
|
# Find channel ID for this symbol's trades
|
||||||
|
channel_id = None
|
||||||
|
for cid, info in self.channels.items():
|
||||||
|
if info.get('channel') == 'trades' and info.get('symbol') == bitfinex_symbol:
|
||||||
|
channel_id = cid
|
||||||
|
break
|
||||||
|
|
||||||
|
if channel_id:
|
||||||
|
unsubscription_msg = {
|
||||||
|
"event": "unsubscribe",
|
||||||
|
"chanId": channel_id
|
||||||
|
}
|
||||||
|
|
||||||
|
success = await self._send_message(unsubscription_msg)
|
||||||
|
if success:
|
||||||
|
if symbol in self.subscriptions and 'trades' in self.subscriptions[symbol]:
|
||||||
|
self.subscriptions[symbol].remove('trades')
|
||||||
|
if not self.subscriptions[symbol]:
|
||||||
|
del self.subscriptions[symbol]
|
||||||
|
|
||||||
|
self.subscribed_symbols.discard(bitfinex_symbol)
|
||||||
|
logger.info(f"Unsubscribed from trades for {symbol} on Bitfinex")
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed to unsubscribe from trades for {symbol} on Bitfinex")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No active trades subscription found for {symbol}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error unsubscribing from trades for {symbol}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
async def get_symbols(self) -> List[str]:
|
async def get_symbols(self) -> List[str]:
|
||||||
"""Get available symbols from Bitfinex."""
|
"""Get available symbols from Bitfinex."""
|
||||||
@ -256,6 +319,127 @@ class BitfinexConnector(BaseExchangeConnector):
|
|||||||
"""Handle info message."""
|
"""Handle info message."""
|
||||||
logger.info(f"Bitfinex info: {data}")
|
logger.info(f"Bitfinex info: {data}")
|
||||||
|
|
||||||
|
async def _handle_data_message(self, data: List) -> None:
|
||||||
|
"""Handle data message from Bitfinex."""
|
||||||
|
try:
|
||||||
|
if len(data) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
channel_id = data[0]
|
||||||
|
message_data = data[1]
|
||||||
|
|
||||||
|
if channel_id not in self.channels:
|
||||||
|
logger.warning(f"Received data for unknown channel: {channel_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
channel_info = self.channels[channel_id]
|
||||||
|
channel_type = channel_info.get('channel')
|
||||||
|
symbol = channel_info.get('symbol', '')
|
||||||
|
|
||||||
|
if channel_type == 'book':
|
||||||
|
await self._handle_orderbook_data(message_data, symbol)
|
||||||
|
elif channel_type == 'trades':
|
||||||
|
await self._handle_trades_data(message_data, symbol)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling data message: {e}")
|
||||||
|
|
||||||
|
async def _handle_orderbook_data(self, data, symbol: str) -> None:
|
||||||
|
"""Handle order book data from Bitfinex."""
|
||||||
|
try:
|
||||||
|
set_correlation_id()
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
return
|
||||||
|
|
||||||
|
standard_symbol = self._denormalize_symbol(symbol)
|
||||||
|
|
||||||
|
# Handle snapshot vs update
|
||||||
|
if len(data) > 0 and isinstance(data[0], list):
|
||||||
|
# Snapshot - array of [price, count, amount]
|
||||||
|
bids = []
|
||||||
|
asks = []
|
||||||
|
|
||||||
|
for level in data:
|
||||||
|
if len(level) >= 3:
|
||||||
|
price = float(level[0])
|
||||||
|
count = int(level[1])
|
||||||
|
amount = float(level[2])
|
||||||
|
|
||||||
|
if validate_price(price) and validate_volume(abs(amount)):
|
||||||
|
if amount > 0:
|
||||||
|
bids.append(PriceLevel(price=price, size=amount))
|
||||||
|
else:
|
||||||
|
asks.append(PriceLevel(price=price, size=abs(amount)))
|
||||||
|
|
||||||
|
orderbook = OrderBookSnapshot(
|
||||||
|
symbol=standard_symbol,
|
||||||
|
exchange=self.exchange_name,
|
||||||
|
timestamp=datetime.now(timezone.utc),
|
||||||
|
bids=bids,
|
||||||
|
asks=asks
|
||||||
|
)
|
||||||
|
|
||||||
|
self._notify_data_callbacks(orderbook)
|
||||||
|
logger.debug(f"Processed order book snapshot for {standard_symbol}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling order book data: {e}")
|
||||||
|
|
||||||
|
async def _handle_trades_data(self, data, symbol: str) -> None:
|
||||||
|
"""Handle trades data from Bitfinex."""
|
||||||
|
try:
|
||||||
|
set_correlation_id()
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
return
|
||||||
|
|
||||||
|
standard_symbol = self._denormalize_symbol(symbol)
|
||||||
|
|
||||||
|
# Handle snapshot vs update
|
||||||
|
if len(data) > 0 and isinstance(data[0], list):
|
||||||
|
# Snapshot - array of trades
|
||||||
|
for trade_data in data:
|
||||||
|
await self._process_single_trade(trade_data, standard_symbol)
|
||||||
|
elif len(data) >= 4:
|
||||||
|
# Single trade update
|
||||||
|
await self._process_single_trade(data, standard_symbol)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling trades data: {e}")
|
||||||
|
|
||||||
|
async def _process_single_trade(self, trade_data: List, symbol: str) -> None:
|
||||||
|
"""Process a single trade from Bitfinex."""
|
||||||
|
try:
|
||||||
|
if len(trade_data) < 4:
|
||||||
|
return
|
||||||
|
|
||||||
|
trade_id = str(trade_data[0])
|
||||||
|
timestamp = int(trade_data[1]) / 1000 # Convert to seconds
|
||||||
|
amount = float(trade_data[2])
|
||||||
|
price = float(trade_data[3])
|
||||||
|
|
||||||
|
if not validate_price(price) or not validate_volume(abs(amount)):
|
||||||
|
return
|
||||||
|
|
||||||
|
side = 'buy' if amount > 0 else 'sell'
|
||||||
|
|
||||||
|
trade = TradeEvent(
|
||||||
|
symbol=symbol,
|
||||||
|
exchange=self.exchange_name,
|
||||||
|
timestamp=datetime.fromtimestamp(timestamp, tz=timezone.utc),
|
||||||
|
price=price,
|
||||||
|
size=abs(amount),
|
||||||
|
side=side,
|
||||||
|
trade_id=trade_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self._notify_data_callbacks(trade)
|
||||||
|
logger.debug(f"Processed trade for {symbol}: {side} {abs(amount)} @ {price}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing single trade: {e}")
|
||||||
|
|
||||||
def get_bitfinex_stats(self) -> Dict[str, Any]:
|
def get_bitfinex_stats(self) -> Dict[str, Any]:
|
||||||
"""Get Bitfinex-specific statistics."""
|
"""Get Bitfinex-specific statistics."""
|
||||||
base_stats = self.get_stats()
|
base_stats = self.get_stats()
|
||||||
|
@ -251,23 +251,161 @@ class MEXCConnector(BaseExchangeConnector):
|
|||||||
|
|
||||||
async def _handle_orderbook_update(self, data: Dict) -> None:
|
async def _handle_orderbook_update(self, data: Dict) -> None:
|
||||||
"""Handle order book update from MEXC."""
|
"""Handle order book update from MEXC."""
|
||||||
# Implementation would parse MEXC-specific order book update format
|
try:
|
||||||
logger.debug("Received MEXC order book update")
|
set_correlation_id()
|
||||||
|
|
||||||
|
symbol_data = data.get('s', '') # Symbol
|
||||||
|
if not symbol_data:
|
||||||
|
logger.warning("Order book update missing symbol")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbol = symbol_data # Already in standard format
|
||||||
|
order_data = data.get('d', {})
|
||||||
|
|
||||||
|
# Parse bids and asks
|
||||||
|
bids = []
|
||||||
|
for bid_data in order_data.get('bids', []):
|
||||||
|
price = float(bid_data[0])
|
||||||
|
size = float(bid_data[1])
|
||||||
|
|
||||||
|
if validate_price(price) and validate_volume(size):
|
||||||
|
bids.append(PriceLevel(price=price, size=size))
|
||||||
|
|
||||||
|
asks = []
|
||||||
|
for ask_data in order_data.get('asks', []):
|
||||||
|
price = float(ask_data[0])
|
||||||
|
size = float(ask_data[1])
|
||||||
|
|
||||||
|
if validate_price(price) and validate_volume(size):
|
||||||
|
asks.append(PriceLevel(price=price, size=size))
|
||||||
|
|
||||||
|
# Create order book snapshot
|
||||||
|
orderbook = OrderBookSnapshot(
|
||||||
|
symbol=symbol,
|
||||||
|
exchange=self.exchange_name,
|
||||||
|
timestamp=datetime.fromtimestamp(int(data.get('t', 0)) / 1000, tz=timezone.utc),
|
||||||
|
bids=bids,
|
||||||
|
asks=asks,
|
||||||
|
sequence_id=order_data.get('lastUpdateId')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Notify callbacks
|
||||||
|
self._notify_data_callbacks(orderbook)
|
||||||
|
|
||||||
|
logger.debug(f"Processed order book update for {symbol}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling order book update: {e}")
|
||||||
|
|
||||||
async def _handle_orderbook_snapshot(self, data: Dict) -> None:
|
async def _handle_orderbook_snapshot(self, data: Dict) -> None:
|
||||||
"""Handle order book snapshot from MEXC."""
|
"""Handle order book snapshot from MEXC."""
|
||||||
# Implementation would parse MEXC-specific order book snapshot format
|
try:
|
||||||
logger.debug("Received MEXC order book snapshot")
|
set_correlation_id()
|
||||||
|
|
||||||
|
symbol_data = data.get('s', '') # Symbol
|
||||||
|
if not symbol_data:
|
||||||
|
logger.warning("Order book snapshot missing symbol")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbol = symbol_data # Already in standard format
|
||||||
|
order_data = data.get('d', {})
|
||||||
|
|
||||||
|
# Parse bids and asks
|
||||||
|
bids = []
|
||||||
|
for bid_data in order_data.get('bids', []):
|
||||||
|
price = float(bid_data[0])
|
||||||
|
size = float(bid_data[1])
|
||||||
|
|
||||||
|
if validate_price(price) and validate_volume(size):
|
||||||
|
bids.append(PriceLevel(price=price, size=size))
|
||||||
|
|
||||||
|
asks = []
|
||||||
|
for ask_data in order_data.get('asks', []):
|
||||||
|
price = float(ask_data[0])
|
||||||
|
size = float(ask_data[1])
|
||||||
|
|
||||||
|
if validate_price(price) and validate_volume(size):
|
||||||
|
asks.append(PriceLevel(price=price, size=size))
|
||||||
|
|
||||||
|
# Create order book snapshot
|
||||||
|
orderbook = OrderBookSnapshot(
|
||||||
|
symbol=symbol,
|
||||||
|
exchange=self.exchange_name,
|
||||||
|
timestamp=datetime.fromtimestamp(int(data.get('t', 0)) / 1000, tz=timezone.utc),
|
||||||
|
bids=bids,
|
||||||
|
asks=asks,
|
||||||
|
sequence_id=order_data.get('lastUpdateId')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Notify callbacks
|
||||||
|
self._notify_data_callbacks(orderbook)
|
||||||
|
|
||||||
|
logger.debug(f"Processed order book snapshot for {symbol}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling order book snapshot: {e}")
|
||||||
|
|
||||||
async def _handle_trade_update(self, data: Dict) -> None:
|
async def _handle_trade_update(self, data: Dict) -> None:
|
||||||
"""Handle trade update from MEXC."""
|
"""Handle trade update from MEXC."""
|
||||||
# Implementation would parse MEXC-specific trade format
|
try:
|
||||||
logger.debug("Received MEXC trade update")
|
set_correlation_id()
|
||||||
|
|
||||||
|
symbol_data = data.get('s', '') # Symbol
|
||||||
|
if not symbol_data:
|
||||||
|
logger.warning("Trade update missing symbol")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbol = symbol_data # Already in standard format
|
||||||
|
trade_data = data.get('d', {})
|
||||||
|
|
||||||
|
# MEXC trade data format
|
||||||
|
trades = trade_data.get('deals', [])
|
||||||
|
|
||||||
|
for trade_info in trades:
|
||||||
|
price = float(trade_info.get('p', 0))
|
||||||
|
quantity = float(trade_info.get('v', 0))
|
||||||
|
|
||||||
|
# Validate data
|
||||||
|
if not validate_price(price) or not validate_volume(quantity):
|
||||||
|
logger.warning(f"Invalid trade data: price={price}, quantity={quantity}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine side (MEXC uses 'S' field: 1=buy, 2=sell)
|
||||||
|
side_code = trade_info.get('S', 0)
|
||||||
|
side = 'buy' if side_code == 1 else 'sell'
|
||||||
|
|
||||||
|
# Create trade event
|
||||||
|
trade = TradeEvent(
|
||||||
|
symbol=symbol,
|
||||||
|
exchange=self.exchange_name,
|
||||||
|
timestamp=datetime.fromtimestamp(int(trade_info.get('t', 0)) / 1000, tz=timezone.utc),
|
||||||
|
price=price,
|
||||||
|
size=quantity,
|
||||||
|
side=side,
|
||||||
|
trade_id=str(trade_info.get('i', ''))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Notify callbacks
|
||||||
|
self._notify_data_callbacks(trade)
|
||||||
|
|
||||||
|
logger.debug(f"Processed trade for {symbol}: {side} {quantity} @ {price}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling trade update: {e}")
|
||||||
|
|
||||||
async def _handle_pong(self, data: Dict) -> None:
|
async def _handle_pong(self, data: Dict) -> None:
|
||||||
"""Handle pong response from MEXC."""
|
"""Handle pong response from MEXC."""
|
||||||
logger.debug("Received MEXC pong")
|
logger.debug("Received MEXC pong")
|
||||||
|
|
||||||
|
async def _send_ping(self) -> None:
|
||||||
|
"""Send ping to keep connection alive."""
|
||||||
|
try:
|
||||||
|
ping_msg = {"method": "PING"}
|
||||||
|
await self._send_message(ping_msg)
|
||||||
|
logger.debug("Sent ping to MEXC")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending ping: {e}")
|
||||||
|
|
||||||
def get_mexc_stats(self) -> Dict[str, Any]:
|
def get_mexc_stats(self) -> Dict[str, Any]:
|
||||||
"""Get MEXC-specific statistics."""
|
"""Get MEXC-specific statistics."""
|
||||||
base_stats = self.get_stats()
|
base_stats = self.get_stats()
|
||||||
|
@ -13,6 +13,10 @@ from ..connectors.kraken_connector import KrakenConnector
|
|||||||
from ..connectors.bybit_connector import BybitConnector
|
from ..connectors.bybit_connector import BybitConnector
|
||||||
from ..connectors.okx_connector import OKXConnector
|
from ..connectors.okx_connector import OKXConnector
|
||||||
from ..connectors.huobi_connector import HuobiConnector
|
from ..connectors.huobi_connector import HuobiConnector
|
||||||
|
from ..connectors.kucoin_connector import KuCoinConnector
|
||||||
|
from ..connectors.gateio_connector import GateIOConnector
|
||||||
|
from ..connectors.bitfinex_connector import BitfinexConnector
|
||||||
|
from ..connectors.mexc_connector import MEXCConnector
|
||||||
|
|
||||||
|
|
||||||
class TestAllConnectors:
|
class TestAllConnectors:
|
||||||
@ -27,12 +31,16 @@ class TestAllConnectors:
|
|||||||
'kraken': KrakenConnector(),
|
'kraken': KrakenConnector(),
|
||||||
'bybit': BybitConnector(use_testnet=True),
|
'bybit': BybitConnector(use_testnet=True),
|
||||||
'okx': OKXConnector(use_demo=True),
|
'okx': OKXConnector(use_demo=True),
|
||||||
'huobi': HuobiConnector()
|
'huobi': HuobiConnector(),
|
||||||
|
'kucoin': KuCoinConnector(use_sandbox=True),
|
||||||
|
'gateio': GateIOConnector(use_testnet=True),
|
||||||
|
'bitfinex': BitfinexConnector(),
|
||||||
|
'mexc': MEXCConnector()
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_all_connectors_initialization(self, all_connectors):
|
def test_all_connectors_initialization(self, all_connectors):
|
||||||
"""Test that all connectors initialize correctly."""
|
"""Test that all connectors initialize correctly."""
|
||||||
expected_names = ['binance', 'coinbase', 'kraken', 'bybit', 'okx', 'huobi']
|
expected_names = ['binance', 'coinbase', 'kraken', 'bybit', 'okx', 'huobi', 'kucoin', 'gateio', 'bitfinex', 'mexc']
|
||||||
|
|
||||||
for name, connector in all_connectors.items():
|
for name, connector in all_connectors.items():
|
||||||
assert connector.exchange_name == name
|
assert connector.exchange_name == name
|
||||||
@ -184,7 +192,11 @@ async def test_connector_compatibility():
|
|||||||
'kraken': KrakenConnector(),
|
'kraken': KrakenConnector(),
|
||||||
'bybit': BybitConnector(use_testnet=True),
|
'bybit': BybitConnector(use_testnet=True),
|
||||||
'okx': OKXConnector(use_demo=True),
|
'okx': OKXConnector(use_demo=True),
|
||||||
'huobi': HuobiConnector()
|
'huobi': HuobiConnector(),
|
||||||
|
'kucoin': KuCoinConnector(use_sandbox=True),
|
||||||
|
'gateio': GateIOConnector(use_testnet=True),
|
||||||
|
'bitfinex': BitfinexConnector(),
|
||||||
|
'mexc': MEXCConnector()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test basic functionality
|
# Test basic functionality
|
||||||
|
115
test_connectors_simple.py
Normal file
115
test_connectors_simple.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple test to verify all 10 exchange connectors are properly implemented.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add the current directory to Python path
|
||||||
|
sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
def test_all_connectors():
|
||||||
|
"""Test that all 10 exchange connectors can be imported and initialized."""
|
||||||
|
|
||||||
|
print("=== Testing All 10 Exchange Connectors ===\n")
|
||||||
|
|
||||||
|
connectors = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import all connectors
|
||||||
|
from COBY.connectors.binance_connector import BinanceConnector
|
||||||
|
from COBY.connectors.coinbase_connector import CoinbaseConnector
|
||||||
|
from COBY.connectors.kraken_connector import KrakenConnector
|
||||||
|
from COBY.connectors.bybit_connector import BybitConnector
|
||||||
|
from COBY.connectors.okx_connector import OKXConnector
|
||||||
|
from COBY.connectors.huobi_connector import HuobiConnector
|
||||||
|
from COBY.connectors.kucoin_connector import KuCoinConnector
|
||||||
|
from COBY.connectors.gateio_connector import GateIOConnector
|
||||||
|
from COBY.connectors.bitfinex_connector import BitfinexConnector
|
||||||
|
from COBY.connectors.mexc_connector import MEXCConnector
|
||||||
|
|
||||||
|
# Initialize all connectors
|
||||||
|
connectors = {
|
||||||
|
'binance': BinanceConnector(),
|
||||||
|
'coinbase': CoinbaseConnector(use_sandbox=True),
|
||||||
|
'kraken': KrakenConnector(),
|
||||||
|
'bybit': BybitConnector(use_testnet=True),
|
||||||
|
'okx': OKXConnector(use_demo=True),
|
||||||
|
'huobi': HuobiConnector(),
|
||||||
|
'kucoin': KuCoinConnector(use_sandbox=True),
|
||||||
|
'gateio': GateIOConnector(use_testnet=True),
|
||||||
|
'bitfinex': BitfinexConnector(),
|
||||||
|
'mexc': MEXCConnector()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("✅ All connectors imported successfully!\n")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to import connectors: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test each connector
|
||||||
|
success_count = 0
|
||||||
|
total_count = len(connectors)
|
||||||
|
|
||||||
|
for name, connector in connectors.items():
|
||||||
|
try:
|
||||||
|
print(f"Testing {name.upper()} connector:")
|
||||||
|
|
||||||
|
# Test basic properties
|
||||||
|
assert connector.exchange_name == name
|
||||||
|
assert hasattr(connector, 'websocket_url')
|
||||||
|
assert hasattr(connector, 'message_handlers')
|
||||||
|
assert hasattr(connector, 'subscriptions')
|
||||||
|
print(f" ✓ Basic properties: OK")
|
||||||
|
|
||||||
|
# Test symbol normalization
|
||||||
|
btc_symbol = connector.normalize_symbol('BTCUSDT')
|
||||||
|
eth_symbol = connector.normalize_symbol('ETHUSDT')
|
||||||
|
print(f" ✓ Symbol normalization: BTCUSDT -> {btc_symbol}, ETHUSDT -> {eth_symbol}")
|
||||||
|
|
||||||
|
# Test required methods exist
|
||||||
|
required_methods = [
|
||||||
|
'connect', 'disconnect', 'subscribe_orderbook', 'subscribe_trades',
|
||||||
|
'unsubscribe_orderbook', 'unsubscribe_trades', 'get_symbols',
|
||||||
|
'get_orderbook_snapshot', 'get_connection_status'
|
||||||
|
]
|
||||||
|
|
||||||
|
for method in required_methods:
|
||||||
|
assert hasattr(connector, method), f"Missing method: {method}"
|
||||||
|
assert callable(getattr(connector, method)), f"Method not callable: {method}"
|
||||||
|
print(f" ✓ Required methods: All {len(required_methods)} methods present")
|
||||||
|
|
||||||
|
# Test statistics
|
||||||
|
stats = connector.get_stats()
|
||||||
|
assert isinstance(stats, dict)
|
||||||
|
assert 'exchange' in stats
|
||||||
|
assert stats['exchange'] == name
|
||||||
|
print(f" ✓ Statistics: {len(stats)} fields")
|
||||||
|
|
||||||
|
# Test connection status
|
||||||
|
status = connector.get_connection_status()
|
||||||
|
print(f" ✓ Connection status: {status.value}")
|
||||||
|
|
||||||
|
print(f" ✅ {name.upper()} connector: ALL TESTS PASSED\n")
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ {name.upper()} connector: FAILED - {e}\n")
|
||||||
|
|
||||||
|
print(f"=== SUMMARY ===")
|
||||||
|
print(f"Total connectors: {total_count}")
|
||||||
|
print(f"Successful: {success_count}")
|
||||||
|
print(f"Failed: {total_count - success_count}")
|
||||||
|
|
||||||
|
if success_count == total_count:
|
||||||
|
print("🎉 ALL 10 EXCHANGE CONNECTORS WORKING PERFECTLY!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"⚠️ {total_count - success_count} connectors need attention")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_all_connectors()
|
||||||
|
sys.exit(0 if success else 1)
|
Reference in New Issue
Block a user