217 lines
5.5 KiB
Markdown
217 lines
5.5 KiB
Markdown
|
|
# Adding New Exchange Adapters
|
||
|
|
|
||
|
|
This guide explains how to add support for new cryptocurrency exchanges.
|
||
|
|
|
||
|
|
## Exchange Adapter Architecture
|
||
|
|
|
||
|
|
All exchange adapters inherit from `BaseExchange` and implement a standardized interface. This allows the trading engine to work with any exchange through a common API.
|
||
|
|
|
||
|
|
## Implementation Steps
|
||
|
|
|
||
|
|
### 1. Create Exchange Module
|
||
|
|
|
||
|
|
Create a new file in `src/exchanges/`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# src/exchanges/your_exchange.py
|
||
|
|
from src.exchanges.base import BaseExchange
|
||
|
|
from src.exchanges.factory import ExchangeFactory
|
||
|
|
import ccxt.pro as ccxt
|
||
|
|
import logging
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
class YourExchangeAdapter(BaseExchange):
|
||
|
|
"""Adapter for Your Exchange."""
|
||
|
|
|
||
|
|
def __init__(self, name: str, api_key: str, secret_key: str, password: str = None):
|
||
|
|
super().__init__(name, api_key, secret_key, password)
|
||
|
|
self.exchange = ccxt.yourexchange({
|
||
|
|
'apiKey': self.api_key,
|
||
|
|
'secret': self.secret_key,
|
||
|
|
'enableRateLimit': True,
|
||
|
|
})
|
||
|
|
|
||
|
|
async def connect(self):
|
||
|
|
"""Establish connection to exchange."""
|
||
|
|
await self.exchange.load_markets()
|
||
|
|
self.is_connected = True
|
||
|
|
logger.info(f"Connected to {self.name}")
|
||
|
|
|
||
|
|
# Implement all required methods from BaseExchange
|
||
|
|
# ...
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Implement Required Methods
|
||
|
|
|
||
|
|
Implement all abstract methods from `BaseExchange`:
|
||
|
|
|
||
|
|
- `connect()` - Establish connection
|
||
|
|
- `disconnect()` - Close connection
|
||
|
|
- `fetch_balance()` - Get account balance
|
||
|
|
- `place_order()` - Place order
|
||
|
|
- `cancel_order()` - Cancel order
|
||
|
|
- `fetch_order_status()` - Get order status
|
||
|
|
- `fetch_ohlcv()` - Get historical data
|
||
|
|
- `subscribe_ohlcv()` - Real-time OHLCV
|
||
|
|
- `subscribe_trades()` - Real-time trades
|
||
|
|
- `subscribe_order_book()` - Real-time order book
|
||
|
|
- `fetch_open_orders()` - Get open orders
|
||
|
|
- `fetch_positions()` - Get positions (futures)
|
||
|
|
- `fetch_markets()` - Get available markets
|
||
|
|
|
||
|
|
### 3. Register Exchange
|
||
|
|
|
||
|
|
Register the exchange in `src/exchanges/__init__.py`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from .your_exchange import YourExchangeAdapter
|
||
|
|
from .factory import ExchangeFactory
|
||
|
|
|
||
|
|
ExchangeFactory.register_exchange("your_exchange", YourExchangeAdapter)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Handle Exchange-Specific Features
|
||
|
|
|
||
|
|
Some exchanges have unique features:
|
||
|
|
|
||
|
|
- **Authentication**: Some exchanges use different auth methods
|
||
|
|
- **Rate Limits**: Respect exchange rate limits
|
||
|
|
- **WebSocket**: Implement exchange-specific WebSocket protocol
|
||
|
|
- **Order Types**: Support exchange-specific order types
|
||
|
|
|
||
|
|
### 5. Write Tests
|
||
|
|
|
||
|
|
Create tests in `tests/unit/exchanges/test_your_exchange.py`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
import pytest
|
||
|
|
from unittest.mock import Mock, patch
|
||
|
|
from src.exchanges.your_exchange import YourExchangeAdapter
|
||
|
|
|
||
|
|
class TestYourExchangeAdapter:
|
||
|
|
"""Tests for Your Exchange adapter."""
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def adapter(self):
|
||
|
|
return YourExchangeAdapter(
|
||
|
|
name="test",
|
||
|
|
api_key="test_key",
|
||
|
|
secret_key="test_secret"
|
||
|
|
)
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_connect(self, adapter):
|
||
|
|
"""Test connection."""
|
||
|
|
with patch.object(adapter.exchange, 'load_markets'):
|
||
|
|
await adapter.connect()
|
||
|
|
assert adapter.is_connected
|
||
|
|
|
||
|
|
# Add more tests...
|
||
|
|
```
|
||
|
|
|
||
|
|
## Using CCXT
|
||
|
|
|
||
|
|
Most exchanges can use the `ccxt` library:
|
||
|
|
|
||
|
|
```python
|
||
|
|
import ccxt.pro as ccxt
|
||
|
|
|
||
|
|
exchange = ccxt.pro.yourexchange({
|
||
|
|
'apiKey': api_key,
|
||
|
|
'secret': secret_key,
|
||
|
|
'enableRateLimit': True,
|
||
|
|
})
|
||
|
|
|
||
|
|
# Use ccxt methods
|
||
|
|
balance = await exchange.fetch_balance()
|
||
|
|
order = await exchange.create_order(...)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Exchange-Specific Considerations
|
||
|
|
|
||
|
|
### Authentication
|
||
|
|
|
||
|
|
Some exchanges require additional authentication:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Example: Exchange requiring passphrase
|
||
|
|
self.exchange = ccxt.coinbase({
|
||
|
|
'apiKey': api_key,
|
||
|
|
'secret': secret_key,
|
||
|
|
'password': passphrase, # Coinbase requires this
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
### Rate Limits
|
||
|
|
|
||
|
|
Always enable rate limiting:
|
||
|
|
|
||
|
|
```python
|
||
|
|
self.exchange = ccxt.yourexchange({
|
||
|
|
'enableRateLimit': True, # Important!
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
### WebSocket Support
|
||
|
|
|
||
|
|
For real-time data, implement WebSocket connections:
|
||
|
|
|
||
|
|
```python
|
||
|
|
async def subscribe_ohlcv(self, symbol: str, timeframe: str, callback):
|
||
|
|
"""Subscribe to OHLCV updates."""
|
||
|
|
# Exchange-specific WebSocket implementation
|
||
|
|
await self.exchange.watch_ohlcv(symbol, timeframe, callback)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing Your Adapter
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
|
||
|
|
Test all methods with mocked exchange responses:
|
||
|
|
|
||
|
|
```python
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_fetch_balance(self, adapter):
|
||
|
|
"""Test balance fetching."""
|
||
|
|
mock_balance = {'BTC': {'free': 1.0, 'used': 0.0, 'total': 1.0}}
|
||
|
|
with patch.object(adapter.exchange, 'fetch_balance', return_value=mock_balance):
|
||
|
|
balance = await adapter.fetch_balance()
|
||
|
|
assert 'BTC' in balance
|
||
|
|
```
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
|
||
|
|
Test with real exchange (use testnet/sandbox if available):
|
||
|
|
|
||
|
|
```python
|
||
|
|
@pytest.mark.integration
|
||
|
|
async def test_real_connection(self):
|
||
|
|
"""Test real connection (requires API keys)."""
|
||
|
|
adapter = YourExchangeAdapter(...)
|
||
|
|
await adapter.connect()
|
||
|
|
assert adapter.is_connected
|
||
|
|
```
|
||
|
|
|
||
|
|
## Documentation
|
||
|
|
|
||
|
|
Document your exchange adapter:
|
||
|
|
|
||
|
|
1. Add docstrings to all methods
|
||
|
|
2. Document exchange-specific features
|
||
|
|
3. Add usage examples
|
||
|
|
4. Update API documentation
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Error Handling**: Handle exchange-specific errors
|
||
|
|
2. **Rate Limiting**: Always respect rate limits
|
||
|
|
3. **Retry Logic**: Implement retry for transient failures
|
||
|
|
4. **Logging**: Log important operations
|
||
|
|
5. **Testing**: Test thoroughly before submitting
|
||
|
|
|
||
|
|
## Example: Complete Exchange Adapter
|
||
|
|
|
||
|
|
See `src/exchanges/coinbase.py` for a complete example implementation.
|
||
|
|
|