Khi tôi bắt đầu xây dựng hệ thống giao dịch định lượng cho quỹ tiền mã hóa vào năm 2024, thách thức lớn nhất không phải là thuật toán hay chiến lược — mà là hạ tầng dữ liệu. Dữ liệu thị trường crypto có đặc thù riêng: tần suất cao, tính không liên tục (discontinuous), và yêu cầu độ trễ cực thấp. Bài viết này là bản blueprint chi tiết từ kinh nghiệm thực chiến của tôi, giúp bạn tránh những sai lầm tốn kém mà tôi đã mắc phải.

Tại Sao Chọn Tardis.dev Cho Data Feed?

Tardis.dev cung cấp normalized market data từ hơn 50 sàn giao dịch với unified API. Điều này giúp tôi tiết kiệm 60% thời gian phát triển so với việc tích hợp từng sàn riêng lẻ. Tuy nhiên, để đạt hiệu suất production-grade, bạn cần hiểu rõ cách Tardis xử lý data và tối ưu hóa pipeline của mình.

Ưu điểm của Tardis

Nhược điểm cần lưu ý

Kiến Trúc Hệ Thống Tổng Quan

Kiến trúc mà tôi đã triển khai cho quỹ với AUM $5M bao gồm 4 layers chính:


┌─────────────────────────────────────────────────────────────┐
│                     PRESENTATION LAYER                       │
│  Dashboard (Grafana) | Alert System | Report Generator       │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                      APPLICATION LAYER                       │
│  Trading Engine | Risk Calculator | Strategy Runner          │
│  (Python/FastAPI) + HolySheep AI (sentiment analysis)        │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                        DATA LAYER                            │
│  Tardis.dev (real-time) → Kafka → TimescaleDB + ClickHouse   │
│  Redis (hot cache) | S3 (cold storage)                       │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                     INFRASTRUCTURE LAYER                     │
│  AWS EC2 (trading) | AWS Lambda (batch) | Cloudflare (CDN)   │
└─────────────────────────────────────────────────────────────┘

Tích Hợp Tardis.dev Với Python

Đây là code production-ready cho việc kết nối Tardis WebSocket và xử lý real-time data. Tôi đã tối ưu để xử lý 10,000 messages/giây mà không drop.

import asyncio
import json
from datetime import datetime
from tardis.devices.exchanges import Exchange
from tardis.interfaces.exchange import AsyncioExchange
from tardis.configuration import configuration
from tardis import Tardis
import asyncpg
from redis.asyncio import Redis
from dataclasses import dataclass
from typing import Optional
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class TradingCandle:
    exchange: str
    symbol: str
    timestamp: datetime
    open: float
    high: float
    low: float
    close: float
    volume: float

class TardisDataPipeline:
    def __init__(
        self,
        tardis_api_key: str,
        db_url: str,
        redis_url: str
    ):
        self.tardis_key = tardis_api_key
        self.db_pool: Optional[asyncpg.Pool] = None
        self.redis: Optional[Redis] = None
        self.db_url = db_url
        self.redis_url = redis_url
        self.message_buffer = []
        self.buffer_size = 1000
        self._buffer_lock = asyncio.Lock()
        
    async def initialize(self):
        """Khởi tạo kết nối database và Redis"""
        self.db_pool = await asyncpg.create_pool(
            self.db_url,
            min_size=10,
            max_size=20,
            command_timeout=60
        )
        self.redis = await Redis.from_url(
            self.redis_url,
            encoding="utf-8",
            decode_responses=True
        )
        logger.info("Database and Redis connections initialized")
    
    async def process_message(self, message: dict):
        """Xử lý message từ Tardis, chuyển đổi sang normalized format"""
        try:
            # Tardis trả về normalized format
            exchange = message.get('exchange')
            symbol = message.get('symbol')
            timestamp = datetime.fromisoformat(
                message.get('timestamp', datetime.utcnow().isoformat())
            )
            
            # Xử lý trade message
            if message.get('type') == 'trade':
                candle = TradingCandle(
                    exchange=exchange,
                    symbol=symbol,
                    timestamp=timestamp,
                    open=float(message.get('price', 0)),
                    high=float(message.get('price', 0)),
                    low=float(message.get('price', 0)),
                    close=float(message.get('price', 0)),
                    volume=float(message.get('amount', 0))
                )
                
            # Xử lý orderbook message  
            elif message.get('type') == 'book':
                # Cập nhật Redis cache cho orderbook
                await self.redis.hset(
                    f"book:{exchange}:{symbol}",
                    mapping={
                        'bids': json.dumps(message.get('bids', [])[:10]),
                        'asks': json.dumps(message.get('asks', [])[:10]),
                        'timestamp': timestamp.isoformat()
                    }
                )
                await self.redis.expire(f"book:{exchange}:{symbol}", 60)
                return
            
            # Buffer messages để batch insert
            await self._buffer_message(candle)
            
        except Exception as e:
            logger.error(f"Error processing message: {e}")
    
    async def _buffer_message(self, candle: TradingCandle):
        """Buffer messages để batch insert, giảm I/O"""
        async with self._buffer_lock:
            self.message_buffer.append({
                'exchange': candle.exchange,
                'symbol': candle.symbol,
                'timestamp': candle.timestamp,
                'open': candle.open,
                'high': candle.high,
                'low': candle.low,
                'close': candle.close,
                'volume': candle.volume
            })
            
            if len(self.message_buffer) >= self.buffer_size:
                await self._flush_buffer()
    
    async def _flush_buffer(self):
        """Batch insert vào TimescaleDB"""
        if not self.message_buffer:
            return
            
        try:
            async with self.db_pool.acquire() as conn:
                await conn.copy_records_to_table(
                    'candles',
                    records=self.message_buffer,
                    columns=['exchange', 'symbol', 'timestamp', 
                            'open', 'high', 'low', 'close', 'volume']
                )
            logger.info(f"Flushed {len(self.message_buffer)} candles to DB")
            self.message_buffer = []
        except Exception as e:
            logger.error(f"Batch insert failed: {e}")
            # Retry logic ở đây
    
    async def start_streaming(self):
        """Bắt đầu streaming từ Tardis"""
        configuration.from_env()
        
        # Cấu hình exchanges muốn subscribe
        exchanges = [
            Exchange('binance'),
            Exchange('bybit'),
            Exchange('okx'),
            Exchange('deribit')
        ]
        
        async with Tardis(exchanges=exchanges) as tardis:
            async for machine in tardis:
                async for message in machine.stream():
                    await self.process_message(message)

Khởi chạy

async def main(): pipeline = TardisDataPipeline( tardis_api_key="YOUR_TARDIS_API_KEY", db_url="postgresql://user:pass@localhost:5432/trading", redis_url="redis://localhost:6379/0" ) await pipeline.initialize() await pipeline.start_streaming() if __name__ == "__main__": asyncio.run(main())

Database Architecture: TimescaleDB + ClickHouse

Kiến trúc database hybrid là chìa khóa cho hệ thống quantitative. Tôi sử dụng TimescaleDB cho hot data (7 ngày gần nhất) vì tính năng continuous aggregate và compression tuyệt vời, còn ClickHouse cho analytical queries trên historical data.

-- Schema cho TimescaleDB - Real-time candles
CREATE TABLE candles (
    time TIMESTAMPTZ NOT NULL,
    exchange TEXT NOT NULL,
    symbol TEXT NOT NULL,
    open NUMERIC(20, 8) NOT NULL,
    high NUMERIC(20, 8) NOT NULL,
    low NUMERIC(20, 8) NOT NULL,
    close NUMERIC(20, 8) NOT NULL,
    volume NUMERIC(20, 8) NOT NULL,
    PRIMARY KEY (time, exchange, symbol)
);

-- Convert sang TimescaleDB hypertable
SELECT create_hypertable('candles', 'time', 
    chunk_time_interval => INTERVAL '1 day');

-- Tạo continuous aggregate cho 1-minute candles
CREATE MATERIALIZED VIEW candles_1m
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 minute', time) AS bucket,
    exchange,
    symbol,
    first(open, time) AS open,
    max(high) AS high,
    min(low) AS low,
    last(close, time) AS close,
    sum(volume) AS volume
FROM candles
GROUP BY bucket, exchange, symbol;

-- Compression policy - giảm 90% storage
ALTER TABLE candles SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'exchange,symbol'
);
SELECT add_compression_policy('candles', INTERVAL '7 days');

-- Index cho strategy queries
CREATE INDEX idx_candles_symbol_time 
ON candles (symbol, time DESC);
CREATE INDEX idx_candles_exchange_time 
ON candles (exchange, time DESC);

-- Retention policy - xóa data cũ hơn 90 ngày
SELECT add_retention_policy('candles', INTERVAL '90 days');

Benchmark Performance

Query TypeTimescaleDB (7 ngày)ClickHouse (2 năm)Notes
Latest candle2ms15msRedis cache: <1ms
1-hour window45ms120msContinuous aggregate
30-day history890ms340msClickHouse columnar
Full backtest (1 year)Timeout2.3sClickHouse excels
Insert rate50,000 rows/s100,000 rows/sBatch insert

Tối Ưu Hóa Chi Phí Cloud

Chi phí cloud là yếu tố quan trọng với quỹ nhỏ và vừa. Đây là breakdown chi phí thực tế của hệ thống tôi triển khai:

ServiceSpecificationMonthly CostAnnual Cost
AWS EC2 (trading)c6i.4xlarge, 16 vCPU$380$4,560
RDS PostgreSQLdb.r6g.2xlarge$520$6,240
ElastiCache Rediscache.r6g.large$120$1,440
S3 (cold storage)500GB/month$12$144
Cloudflare ProCDN + WAF$20$240
Tardis.devPro tier$800$9,600
TOTAL$1,852$22,224

Chiến Lược Tiết Kiệm 40%

Tích Hợp HolySheep AI Cho Sentiment Analysis

Một phần quan trọng trong hệ thống quantitative hiện đại là phân tích sentiment từ social media và news. Tôi tích hợp HolySheep AI vào pipeline để phân tích tin tức tiền mã hóa real-time. Với chi phí chỉ $0.42/1M tokens (DeepSeek V3.2), việc phân tích sentiment trở nên cực kỳ hiệu quả về chi phí.

import aiohttp
import json
from datetime import datetime
from typing import List, Dict, Optional
import asyncio
from dataclasses import dataclass
from enum import Enum

class SentimentScore(Enum):
    VERY_BEARISH = -2
    BEARISH = -1
    NEUTRAL = 0
    BULLISH = 1
    VERY_BULLISH = 2

@dataclass
class NewsItem:
    title: str
    content: str
    source: str
    timestamp: datetime
    url: str
    symbols: List[str]

@dataclass  
class SentimentResult:
    news_item: NewsItem
    sentiment: SentimentScore
    confidence: float
    key_themes: List[str]
    processed_at: datetime

class HolySheepSentimentAnalyzer:
    """Tích hợp HolySheep AI cho sentiment analysis"""
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.model = "deepseek-v3.2"  # $0.42/1M tokens - tiết kiệm 85%!
    
    async def analyze_sentiment(
        self, 
        news_item: NewsItem
    ) -> Optional[SentimentResult]:
        """Phân tích sentiment của một tin tức"""
        
        prompt = f"""Analyze this crypto news and return sentiment analysis:
        
Title: {news_item.title}
Content: {news_item.content[:500]}

Respond in JSON format:
{{
    "sentiment": "VERY_BEARISH|BEARISH|NEUTRAL|BULLISH|VERY_BULLISH",
    "confidence": 0.0-1.0,
    "key_themes": ["theme1", "theme2"],
    "impact_symbols": ["BTC", "ETH"]
}}
"""
        
        async with aiohttp.ClientSession() as session:
            payload = {
                "model": self.model,
                "messages": [
                    {
                        "role": "system", 
                        "content": "You are a crypto market sentiment analyst."
                    },
                    {
                        "role": "user",
                        "content": prompt
                    }
                ],
                "temperature": 0.3,
                "max_tokens": 200
            }
            
            headers = {
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            }
            
            try:
                async with session.post(
                    f"{self.base_url}/chat/completions",
                    json=payload,
                    headers=headers,
                    timeout=aiohttp.ClientTimeout(total=5)
                ) as response:
                    if response.status == 200:
                        result = await response.json()
                        content = result['choices'][0]['message']['content']
                        analysis = json.loads(content)
                        
                        return SentimentResult(
                            news_item=news_item,
                            sentiment=SentimentScore[analysis['sentiment']],
                            confidence=analysis['confidence'],
                            key_themes=analysis['key_themes'],
                            processed_at=datetime.utcnow()
                        )
                    else:
                        return None
            except Exception as e:
                print(f"Sentiment analysis failed: {e}")
                return None
    
    async def batch_analyze(
        self, 
        news_items: List[NewsItem],
        max_concurrent: int = 10
    ) -> List[SentimentResult]:
        """Batch analyze với concurrency limit"""
        
        semaphore = asyncio.Semaphore(max_concurrent)
        
        async def bounded_analyze(item):
            async with semaphore:
                return await self.analyze_sentiment(item)
        
        tasks = [bounded_analyze(item) for item in news_items]
        results = await asyncio.gather(*tasks)
        return [r for r in results if r is not None]

    def aggregate_sentiment(
        self, 
        results: List[SentimentResult],
        symbol: str
    ) -> Dict:
        """Tổng hợp sentiment cho một symbol"""
        
        symbol_results = [
            r for r in results 
            if symbol in r.news_item.symbols
        ]
        
        if not symbol_results:
            return {"sentiment": "NEUTRAL", "score": 0, "count": 0}
        
        weighted_score = sum(
            r.sentiment.value * r.confidence 
            for r in symbol_results
        ) / len(symbol_results)
        
        avg_confidence = sum(
            r.confidence for r in symbol_results
        ) / len(symbol_results)
        
        return {
            "symbol": symbol,
            "sentiment": SentimentScore(int(weighted_score)).name,
            "weighted_score": weighted_score,
            "avg_confidence": avg_confidence,
            "news_count": len(symbol_results),
            "key_themes": list(set(
                theme 
                for r in symbol_results 
                for theme in r.key_themes
            ))[:5]
        }

Sử dụng

async def main(): analyzer = HolySheepSentimentAnalyzer( api_key="YOUR_HOLYSHEEP_API_KEY" ) # Mock news data news = [ NewsItem( title="Bitcoin surges past $100,000 amid institutional buying", content="Major institutions are accumulating Bitcoin...", source="CryptoNews", timestamp=datetime.utcnow(), url="https://example.com/btc-surge", symbols=["BTC"] ) ] result = await analyzer.analyze_sentiment(news[0]) print(f"Sentiment: {result.sentiment}, Confidence: {result.confidence}") # Batch processing - 1000 news/month với chi phí chỉ ~$0.50 # So với OpenAI GPT-4: ~$15 cho cùng объем if __name__ == "__main__": asyncio.run(main())

Bảng So Sánh Chi Phí AI APIs (2026)

Provider/ModelGiá/1M TokensUse CaseLatency P50Đánh giá
HolySheep DeepSeek V3.2$0.42Sentiment, Classification45ms⭐⭐⭐⭐⭐ Best value
HolySheep Gemini 2.5 Flash$2.50General tasks38ms⭐⭐⭐⭐ Fast
OpenAI GPT-4.1$8.00Complex reasoning890ms⭐⭐⭐ Premium
Anthropic Claude Sonnet 4.5$15.00Long context1,200ms⭐⭐⭐ Expensive
Google Gemini 2.5 Pro$7.00Multimodal950ms⭐⭐⭐ Good

Việc sử dụng HolySheep AI giúp tôi tiết kiệm 85-95% chi phí AI so với các provider phương Tây. Với hệ thống xử lý 10 triệu tokens/tháng cho sentiment analysis, chi phí chỉ $4.20 thay vì $80-150.

Xây Dựng Trading Engine Pipeline

Đây là architecture cho real-time trading signal generation, kết hợp data từ Tardis với AI analysis từ HolySheep:

import asyncio
import numpy as np
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime, timedelta
import redis.asyncio as redis
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy import select, text

@dataclass
class TradingSignal:
    symbol: str
    signal_type: str  # LONG, SHORT, CLOSE
    entry_price: float
    stop_loss: float
    take_profit: float
    confidence: float
    timestamp: datetime
    indicators: Dict
    ai_sentiment: Optional[Dict]

class TradingEngine:
    def __init__(
        self,
        redis_url: str,
        db_url: str,
        holysheep_analyzer,  # Từ class ở trên
        config: Dict
    ):
        self.redis = redis.from_url(redis_url)
        self.db_engine = create_async_engine(db_url)
        self.Session = sessionmaker(
            self.db_engine, 
            class_=AsyncSession, 
            expire_on_commit=False
        )
        self.analyzer = holysheep_analyzer
        self.config = config
        
        # Technical indicators parameters
        self.rsi_period = config.get('rsi_period', 14)
        self.ma_short = config.get('ma_short', 20)
        self.ma_long = config.get('ma_long', 50)
    
    async def calculate_indicators(
        self, 
        symbol: str, 
        timeframe: str = '1h'
    ) -> Dict:
        """Tính toán technical indicators từ database"""
        
        async with self.Session() as session:
            # Query 100 candles gần nhất
            query = text("""
                SELECT time, open, high, low, close, volume
                FROM candles
                WHERE symbol = :symbol 
                AND time > NOW() - INTERVAL '100 hours'
                ORDER BY time DESC
                LIMIT 100
            """)
            
            result = await session.execute(
                query, 
                {'symbol': symbol}
            )
            rows = result.fetchall()
            
            if len(rows) < 50:
                return {}
            
            # Chuyển sang numpy arrays
            closes = np.array([r[4] for r in rows[::-1]])
            highs = np.array([r[2] for r in rows[::-1]])
            lows = np.array([r[3] for r in rows[::-1]])
            volumes = np.array([r[5] for r in rows[::-1]])
            
            # RSI
            delta = np.diff(closes, prepend=closes[0])
            gain = np.where(delta > 0, delta, 0)
            loss = np.where(delta < 0, -delta, 0)
            avg_gain = np.mean(gain[-self.rsi_period:])
            avg_loss = np.mean(loss[-self.rsi_period:])
            rs = avg_gain / (avg_loss + 1e-10)
            rsi = 100 - (100 / (1 + rs))
            
            # Moving Averages
            ma20 = np.mean(closes[-self.ma_short:])
            ma50 = np.mean(closes[-self.ma_long:])
            
            # Bollinger Bands
            std = np.std(closes[-20:])
            bb_upper = ma20 + 2 * std
            bb_lower = ma20 - 2 * std
            
            # MACD
            ema12 = self._ema(closes, 12)
            ema26 = self._ema(closes, 26)
            macd = ema12 - ema26
            signal = self._ema(np.array([macd]*len(closes)), 9)
            
            # Volume profile
            avg_volume = np.mean(volumes[-20:])
            current_volume = volumes[-1]
            volume_ratio = current_volume / (avg_volume + 1e-10)
            
            return {
                'rsi': float(rsi),
                'ma20': float(ma20),
                'ma50': float(ma50),
                'bb_upper': float(bb_upper),
                'bb_lower': float(bb_lower),
                'macd': float(macd),
                'macd_signal': float(signal[-1]),
                'volume_ratio': float(volume_ratio),
                'current_price': float(closes[-1]),
                'trend': 'BULLISH' if ma20 > ma50 else 'BEARISH'
            }
    
    def _ema(self, data: np.array, period: int) -> float:
        """Tính Exponential Moving Average"""
        multiplier = 2 / (period + 1)
        ema = data[0]
        for value in data[1:]:
            ema = (value * multiplier) + (ema * (1 - multiplier))
        return ema
    
    async def generate_signal(
        self, 
        symbol: str,
        indicators: Dict,
        sentiment: Optional[Dict] = None
    ) -> Optional[TradingSignal]:
        """Generate trading signal từ indicators + AI sentiment"""
        
        price = indicators['current_price']
        rsi = indicators['rsi']
        trend = indicators['trend']
        
        # Logic signal generation
        signal_type = None
        confidence = 0.5
        stop_loss_pct = 0.02
        take_profit_pct = 0.04
        
        # Long signal: RSI oversold + bullish trend
        if rsi < 30 and trend == 'BULLISH':
            signal_type = 'LONG'
            confidence = 0.7 + (30 - rsi) / 100
            stop_loss_pct = 0.015
            take_profit_pct = 0.05
        
        # Short signal: RSI overbought + bearish trend  
        elif rsi > 70 and trend == 'BEARISH':
            signal_type = 'SHORT'
            confidence = 0.7 + (rsi - 70) / 100
            stop_loss_pct = 0.015
            take_profit_pct = 0.05
        
        # Kết hợp với AI sentiment
        if sentiment and signal_type:
            sentiment_score = sentiment.get('weighted_score', 0)
            sentiment_confidence = sentiment.get('avg_confidence', 0.5)
            
            # Adjust confidence dựa trên sentiment
            if sentiment_score > 0.5 and signal_type == 'LONG':
                confidence += sentiment_confidence * 0.2
            elif sentiment_score < -0.5 and signal_type == 'SHORT':
                confidence += sentiment_confidence * 0.2
        
        if not signal_type:
            return None
        
        return TradingSignal(
            symbol=symbol,
            signal_type=signal_type,
            entry_price=price,
            stop_loss=price * (1 - stop_loss_pct if signal_type == 'LONG' else 1 + stop_loss_pct),
            take_profit=price * (1 + take_profit_pct if signal_type == 'LONG' else 1 - take_profit_pct),
            confidence=min(confidence, 0.95),
            timestamp=datetime.utcnow(),
            indicators=indicators,
            ai_sentiment=sentiment
        )
    
    async def run(self):
        """Main loop cho trading engine"""
        symbols = ['BTC-USDT', 'ETH-USDT', 'SOL-USDT']
        
        while True:
            try:
                for symbol in symbols:
                    # Calculate indicators
                    indicators = await self.calculate_indicators(symbol)
                    
                    if not indicators:
                        continue
                    
                    # Get sentiment from cache
                    sentiment_data = await self.redis.get(
                        f"sentiment:{symbol}"
                    )
                    sentiment = json.loads(sentiment_data) if sentiment_data else None
                    
                    # Generate signal
                    signal = await self.generate_signal(
                        symbol, indicators, sentiment
                    )
                    
                    if signal:
                        # Publish signal
                        await self.redis.publish(
                            f"signals:{symbol}",
                            json.dumps({
                                'type': signal.signal_type,
                                'price': signal.entry_price,
                                'sl': signal.stop_loss,
                                'tp': signal.take_profit,
                                'confidence': signal.confidence
                            })
                        )
                        print(f"Signal generated: {symbol} {signal.signal_type}")
                
                await asyncio.sleep(60)  # Chạy mỗi phút
                
            except Exception as e:
                print(f"Error in trading loop: {e}")
                await asyncio.sleep(5)

Lỗi Thường Gặp và Cách Khắc Phục

1. Lỗi: WebSocket Disconnection liên tục

Mô tả lỗi: Tardis WebSocket bị disconnect sau 5-10 phút, gây mất data và signal lag.

# Nguyên nhân: Heartbeat timeout hoặc network issue

Giải pháp: Implement reconnection logic với exponential backoff

import asyncio from typing import Optional import logging logger = logging.getLogger(__name__) class ReconnectingWebSocket: def __init__( self, url: str, max_retries: int = 10, base_delay: float = 1.0, max_delay: float = 60.0 ): self.url = url self.max_retries = max_retries self.base_delay = base_delay self.max_delay = max_delay self.ws: Optional[any] = None self._running = False self._retry_count = 0 async def connect(self): """Kết nối với reconnection logic""" self._running = True self._retry_count = 0 while self._running and self._retry_count < self.max_retries: try: async with aiohttp.ClientSession() as session: async with session.ws_connect( self.url, heartbeat=30 # Ping/pong mỗi 30s ) as ws: self.ws = ws self._retry_count = 0 # Reset on success logger.info(f"WebSocket connected: {self.url}") await self._receive_loop() except aiohttp.ClientError as e: self._retry_count += 1 delay = min( self.base_delay * (2 ** self._