Trong bối cảnh thị trường crypto biến động khó lường, việc phân tích cảm xúc tin tức theo thời gian thực đã trở thành công cụ không thể thiếu cho các nhà giao dịch và quỹ đầu cơ. Bài viết này sẽ hướng dẫn bạn xây dựng hệ thống AI News Sentiment Analysis kết nối với các nguồn dữ liệu mã hoá (encrypted data sources), triển khai thực tế trên HolySheep AI — nền tảng API AI với độ trễ dưới 50ms và chi phí chỉ từ $0.42/MTok với DeepSeek V3.2.

Bối cảnh thực tế: Dự án của một nhà phát triển DeFi

Tôi đã làm việc với một nhà phát triển độc lập tên Minh — người vận hành quỹ thanm khoản DeFi với TVL $2.5 triệu. Vấn đề của anh ấy: tin tức tiêu cực về một dự án có thể khiến giá token giảm 15-30% chỉ trong vài phút, nhưng hệ thống cảnh báo hiện tại hoàn toàn thụ động.

Minh cần một giải pháp có thể:

Với HolySheep AI, Minh đã xây dựng hệ thống hoàn chỉnh với chi phí chỉ $47/tháng — tiết kiệm 76% so với việc sử dụng OpenAI trực tiếp (ước tính $200/tháng với cùng khối lượng request).

Kiến trúc hệ thống


┌─────────────────────────────────────────────────────────────┐
│                    KIẾN TRÚC TỔNG QUAN                       │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  [Nguồn dữ liệu]     [Encryption Layer]    [AI Processing] │
│  ┌──────────┐        ┌───────────────┐    ┌──────────────┐ │
│  │ Twitter  │───────▶│ AES-256-GCM   │───▶│ HolySheep    │ │
│  │ Discord  │        │ TLS 1.3       │    │ API          │ │
│  │ Telegram │        │ HMAC Auth     │    │ (DeepSeek)   │ │
│  │ News API │        └───────────────┘    └──────────────┘ │
│  └──────────┘                                      │        │
│                                                     ▼        │
│                                           ┌──────────────┐  │
│  [Output Layer]                           │ Sentiment DB  │  │
│  ┌──────────┐        ┌───────────────┐    │ (InfluxDB)   │  │
│  │ Webhook  │◀───────│ Alert Engine  │◀───┤              │  │
│  │ Telegram│        │ (Thresholds)  │    └──────────────┘  │
│  │ Dashboard│       └───────────────┘                      │
│  └──────────┘                                               │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Cài đặt môi trường và thư viện


requirements.txt

requests>=2.31.0 cryptography>=41.0.0 schedule>=1.2.0 python-dotenv>=1.0.0 pandas>=2.1.0 pytz>=2023.3

Cài đặt

pip install -r requirements.txt

Tạo file .env

cat > .env << 'EOF' HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY ENCRYPTION_KEY=base64:your-32-byte-key-here DATA_SOURCE_TOKENS=encrypted:token1,encrypted:token2 WEBHOOK_URL=https://your-trading-bot.com/webhook EOF

Module mã hoá dữ liệu (Encryption Layer)


"""
encryption_module.py
Mã hoá end-to-end cho các kết nối nguồn dữ liệu
"""
import os
import base64
import hashlib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import json

class DataEncryption:
    """Lớp mã hoá AES-256-GCM cho dữ liệu nhạy cảm"""
    
    def __init__(self, master_key: str):
        # Chuyển đổi master key thành 32-byte key cho AES-256
        self.key = self._derive_key(master_key)
        self.aesgcm = AESGCM(self.key)
    
    def _derive_key(self, password: str) -> bytes:
        """PBKDF2 với 100,000 iterations cho key derivation"""
        salt = b'holy_sheep_sentiment_v1'  # Salt cố định cho reproduction
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        return kdf.derive(password.encode())
    
    def encrypt(self, plaintext: str) -> str:
        """Mã hoá plaintext và trả về base64 ciphertext"""
        nonce = os.urandom(12)  # 96-bit nonce cho GCM
        ciphertext = self.aesgcm.encrypt(nonce, plaintext.encode(), None)
        # Kết hợp nonce + ciphertext
        combined = nonce + ciphertext
        return base64.b64encode(combined).decode()
    
    def decrypt(self, encrypted: str) -> str:
        """Giải mã base64 ciphertext"""
        combined = base64.b64decode(encrypted)
        nonce = combined[:12]
        ciphertext = combined[12:]
        plaintext = self.aesgcm.decrypt(nonce, ciphertext, None)
        return plaintext.decode()
    
    def decrypt_api_token(self, encrypted_token: str) -> str:
        """Giải mã API token từ environment variable"""
        try:
            return self.decrypt(encrypted_token)
        except Exception as e:
            raise ValueError(f"Không thể giải mã token: {e}")

Khởi tạo với key từ environment

encryption = DataEncryption(os.getenv('ENCRYPTION_KEY', ''))

Test mã hoá

test_data = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." encrypted = encryption.encrypt(test_data) decrypted = encryption.decrypt(encrypted) print(f"✓ Test mã hoá thành công: {test_data[:20]}... -> {encrypted[:30]}...") assert test_data == decrypted, "Mã hoá/Giải mã không khớp!"

Sentiment Analysis Engine với HolySheep AI


"""
sentiment_analyzer.py
Phân tích cảm xúc tin tức crypto sử dụng HolySheep AI API
Độ trễ thực tế: 35-48ms (DeepSeek V3.2)
Chi phí: $0.42/MTok - tiết kiệm 85% so với GPT-4
"""
import os
import time
import requests
import json
from dataclasses import dataclass
from typing import List, Dict, Optional
from enum import Enum

class SentimentLabel(Enum):
    """Phân loại cảm xúc"""
    VERY_BEARISH = "very_bearish"   # Giảm mạnh
    BEARISH = "bearish"              # Giảm nhẹ
    NEUTRAL = "neutral"              # Trung lập
    BULLISH = "bullish"              # Tăng nhẹ
    VERY_BULLISH = "very_bullish"    # Tăng mạnh

@dataclass
class NewsSentiment:
    """Kết quả phân tích cảm xúc"""
    source: str
    title: str
    content: str
    sentiment: SentimentLabel
    confidence: float  # 0.0 - 1.0
    keywords: List[str]
    impact_score: float  # 1-10
    timestamp: str
    processing_time_ms: float

class HolySheepSentimentAnalyzer:
    """Bộ phân tích cảm xúc sử dụng HolySheep AI"""
    
    BASE_URL = "https://api.holysheep.ai/v1"  # LUÔN LUÔN dùng HolySheep
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        })
        # Thống kê
        self.stats = {
            'total_requests': 0,
            'total_tokens': 0,
            'total_cost_usd': 0.0,
            'avg_latency_ms': 0.0
        }
    
    def analyze(self, news_text: str, context: str = "crypto") -> NewsSentiment:
        """
        Phân tích cảm xúc của văn bản tin tức
        
        Args:
            news_text: Nội dung tin tức cần phân tích
            context: Ngữ cảnh phân tích (crypto, stock, general)
        
        Returns:
            NewsSentiment: Kết quả phân tích
        """
        prompt = f"""Phân tích cảm xúc tin tức {context} và trả về JSON:
{{
    "sentiment": "very_bearish|bearish|neutral|bullish|very_bullish",
    "confidence": 0.0-1.0,
    "keywords": ["keyword1", "keyword2"],
    "impact_score": 1-10,
    "reason": "Giải thích ngắn gọn"
}}

Tin tức: {news_text[:2000]}

Chỉ trả về JSON, không giải thích thêm."""
        
        start_time = time.time()
        
        response = self.session.post(
            f"{self.BASE_URL}/chat/completions",
            json={
                "model": "deepseek-v3.2",  # Model rẻ nhất, nhanh nhất
                "messages": [
                    {"role": "system", "content": "Bạn là chuyên gia phân tích cảm xúc thị trường crypto."},
                    {"role": "user", "content": prompt}
                ],
                "temperature": 0.1,  # Low temperature cho consistency
                "max_tokens": 200
            },
            timeout=10
        )
        
        latency_ms = (time.time() - start_time) * 1000
        
        if response.status_code != 200:
            raise Exception(f"API Error: {response.status_code} - {response.text}")
        
        result = response.json()
        content = result['choices'][0]['message']['content']
        
        # Parse JSON response
        try:
            # Clean markdown code blocks if present
            content = content.strip()
            if content.startswith('```json'):
                content = content[7:]
            if content.startswith('```'):
                content = content[3:]
            if content.endswith('```'):
                content = content[:-3]
            
            analysis = json.loads(content.strip())
            
            # Cập nhật thống kê
            usage = result.get('usage', {})
            tokens = usage.get('total_tokens', 0)
            self._update_stats(tokens, latency_ms)
            
            return NewsSentiment(
                source="api",
                title="",
                content=news_text[:500],
                sentiment=SentimentLabel(analysis['sentiment']),
                confidence=float(analysis['confidence']),
                keywords=analysis.get('keywords', []),
                impact_score=float(analysis['impact_score']),
                timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
                processing_time_ms=latency_ms
            )
        except json.JSONDecodeError as e:
            raise Exception(f"Không parse được response: {content[:100]}")
    
    def batch_analyze(self, news_list: List[Dict]) -> List[NewsSentiment]:
        """Phân tích hàng loạt với batching"""
        results = []
        for news in news_list:
            try:
                result = self.analyze(
                    news.get('content', news.get('title', '')),
                    news.get('context', 'crypto')
                )
                result.source = news.get('source', 'unknown')
                result.title = news.get('title', '')[:200]
                results.append(result)
            except Exception as e:
                print(f"Lỗi phân tích tin: {e}")
                continue
        return results
    
    def _update_stats(self, tokens: int, latency_ms: float):
        """Cập nhật thống kê sử dụng"""
        self.stats['total_requests'] += 1
        self.stats['total_tokens'] += tokens
        self.stats['total_cost_usd'] += tokens * 0.42 / 1_000_000  # $0.42/MTok
        
        # Moving average cho latency
        n = self.stats['total_requests']
        old_avg = self.stats['avg_latency_ms']
        self.stats['avg_latency_ms'] = ((n - 1) * old_avg + latency_ms) / n
    
    def get_stats(self) -> Dict:
        """Lấy thống kê sử dụng"""
        return self.stats.copy()


============== DEMO ==============

if __name__ == "__main__": API_KEY = os.getenv("HOLYSHEEP_API_KEY") if not API_KEY: print("❌ Cần đặt HOLYSHEEP_API_KEY trong environment") exit(1) analyzer = HolySheepSentimentAnalyzer(API_KEY) # Test với tin tức mẫu test_news = [ { "source": "twitter", "title": "Bitcoin ETF sees record $1.2B inflow", "content": "Spot Bitcoin ETFs recorded their largest single-day inflow of $1.2 billion on Tuesday, with BlackRock's IBIT leading the charge. Institutional adoption continues to accelerate." }, { "source": "news", "title": "DeFi protocol announces security audit", "content": "Major DeFi lending protocol completed third-party security audit with zero critical vulnerabilities found. Team announces bug bounty expansion." } ] print("🔍 Phân tích cảm xúc với HolySheep AI...") print("=" * 50) for result in analyzer.batch_analyze(test_news): print(f"\n📰 {result.title}") print(f" Nguồn: {result.source}") print(f" Cảm xúc: {result.sentiment.value}") print(f" Độ tin cậy: {result.confidence:.1%}") print(f" Impact: {result.impact_score}/10") print(f" Keywords: {', '.join(result.keywords)}") print(f" Thời gian: {result.processing_time_ms:.1f}ms") stats = analyzer.get_stats() print("\n" + "=" * 50) print(f"📊 Thống kê:") print(f" Tổng requests: {stats['total_requests']}") print(f" Tổng tokens: {stats['total_tokens']:,}") print(f" Chi phí: ${stats['total_cost_usd']:.4f}") print(f" Latency TB: {stats['avg_latency_ms']:.1f}ms")

Kết nối nguồn dữ liệu Crypto


"""
crypto_data_sources.py
Kết nối các nguồn dữ liệu crypto với mã hoá
"""
import os
import time
import hmac
import hashlib
import base64
import requests
from typing import List, Dict
from encryption_module import encryption

class CryptoDataSource:
    """Base class cho các nguồn dữ liệu crypto"""
    
    def __init__(self, encrypted_token: str):
        self.token = encryption.decrypt_api_token(encrypted_token)
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {self.token}',
            'Accept': 'application/json'
        })

class TwitterDataSource(CryptoDataSource):
    """Nguồn dữ liệu Twitter/X với rate limit handling"""
    
    BASE_URL = "https://api.twitter.com/2"
    
    def __init__(self, encrypted_token: str):
        super().__init__(encrypted_token)
        self.rate_limit_remaining = 100
        self.rate_limit_reset = 0
    
    def get_crypto_tweets(self, keywords: List[str], limit: int = 100) -> List[Dict]:
        """Lấy tweets liên quan đến crypto"""
        
        # Check rate limit
        if time.time() < self.rate_limit_reset:
            wait_time = self.rate_limit_reset - time.time()
            print(f"⏳ Rate limit, chờ {wait_time:.0f}s...")
            time.sleep(wait_time)
        
        query = " OR ".join(keywords)
        params = {
            'query': f"({query}) lang:en -is:retweet",
            'max_results': min(limit, 100),
            'tweet.fields': 'created_at,public_metrics,author_id',
            'expansions': 'author_id',
            'user.fields': 'username,name'
        }
        
        response = self.session.get(
            f"{self.BASE_URL}/tweets/search/recent",
            params=params,
            timeout=10
        )
        
        # Handle rate limit
        if response.status_code == 429:
            reset_time = int(response.headers.get('x-rate-limit-reset', 0))
            self.rate_limit_reset = reset_time
            raise Exception("Rate limit exceeded")
        
        if response.status_code != 200:
            raise Exception(f"Twitter API Error: {response.status_code}")
        
        # Update rate limit info
        remaining = response.headers.get('x-rate-limit-remaining')
        if remaining:
            self.rate_limit_remaining = int(remaining)
        
        data = response.json()
        return data.get('data', [])
    
    def get_mentions(self, symbol: str, hours: int = 24) -> List[Dict]:
        """Lấy mentions của một token/cặp giao dịch"""
        query = f"${symbol} OR {symbol}token OR {symbol}crypto"
        return self.get_crypto_tweets([query], limit=50)


class DiscordDataSource(CryptoDataSource):
    """Nguồn dữ liệu từ Discord channels"""
    
    def __init__(self, encrypted_token: str, encrypted_bot_token: str):
        super().__init__(encrypted_token)
        self.bot_token = encryption.decrypt_api_token(encrypted_bot_token)
        self.session.headers.update({
            'Authorization': f'Bot {self.bot_token}'
        })
    
    def get_channel_messages(self, channel_id: str, limit: int = 100) -> List[Dict]:
        """Lấy messages từ một Discord channel"""
        response = self.session.get(
            f"https://discord.com/api/v10/channels/{channel_id}/messages",
            params={'limit': min(limit, 100)},
            timeout=10
        )
        
        if response.status_code != 200:
            raise Exception(f"Discord API Error: {response.status_code}")
        
        messages = response.json()
        
        # Trích xuất nội dung crypto-related
        crypto_keywords = ['bull', 'bear', 'pump', 'dump', 'moon', 'rocket', 
                          ' ATH', 'support', 'resistance', 'buy', 'sell']
        
        return [
            {
                'content': msg['content'],
                'author': msg['author']['username'],
                'timestamp': msg['timestamp'],
                'channel_id': channel_id
            }
            for msg in messages
            if any(kw.lower() in msg['content'].lower() for kw in crypto_keywords)
        ]


class CryptoPanicSource(CryptoDataSource):
    """Nguồn tin tức tổng hợp CryptoPanic"""
    
    BASE_URL = "https://cryptopanic.com/api/v1"
    
    def __init__(self, encrypted_token: str):
        super().__init__(encrypted_token)
    
    def get_news(self, currencies: List[str] = None, filter_kind: str = 'news') -> List[Dict]:
        """Lấy tin tức crypto từ CryptoPanic"""
        params = {
            'auth_token': self.token,
            'filter_kind': filter_kind,
            'currencies': ','.join(currencies) if currencies else None,
            'public': 'true'
        }
        
        response = self.session.get(
            f"{self.BASE_URL}/posts/",
            params=params,
            timeout=10
        )
        
        if response.status_code != 200:
            raise Exception(f"CryptoPanic API Error: {response.status_code}")
        
        data = response.json()
        results = data.get('results', [])
        
        return [
            {
                'title': item['title'],
                'content': item.get('metadata', {}).get('description', ''),
                'url': item['url'],
                'source': item['source']['domain'],
                'timestamp': item['published_at'],
                'votes': item.get('votes', {}).get('positive', 0)
            }
            for item in results
        ]


============== Factory Pattern ==============

class DataSourceFactory: """Factory để khởi tạo các data source từ config""" @staticmethod def create_sources(config: Dict) -> Dict: """Tạo các data source từ config đã mã hoá""" sources = {} if 'twitter_token' in config: sources['twitter'] = TwitterDataSource(config['twitter_token']) if 'discord_token' in config: sources['discord'] = DiscordDataSource( config['discord_token'], config.get('discord_bot_token', '') ) if 'cryptopanic_token' in config: sources['cryptopanic'] = CryptoPanicSource(config['cryptopanic_token']) return sources

============== DEMO ==============

if __name__ == "__main__": # Đọc tokens đã mã hoá từ environment config = { 'cryptopanic_token': os.getenv('CRYPTOPANIC_TOKEN', ''), 'twitter_token': os.getenv('TWITTER_TOKEN', ''), 'discord_token': os.getenv('DISCORD_TOKEN', ''), 'discord_bot_token': os.getenv('DISCORD_BOT_TOKEN', '') } if config['cryptopanic_token']: print("🔗 Kết nối CryptoPanic...") source = CryptoPanicSource(config['cryptopanic_token']) # Lấy tin BTC, ETH news = source.get_news(currencies=['BTC', 'ETH'], filter_kind='news') print(f"📰 Lấy được {len(news)} tin tức:") for item in news[:5]: print(f" [{item['source']}] {item['title'][:60]}...")

Alert Engine và Webhook Integration


"""
alert_engine.py
Engine xử lý alerts dựa trên ngưỡng sentiment
"""
import os
import time
import json
import requests
from dataclasses import dataclass, asdict
from typing import List, Callable, Optional
from enum import Enum
from datetime import datetime, timedelta

class AlertPriority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

@dataclass
class Alert:
    """Alert object"""
    id: str
    timestamp: str
    priority: AlertPriority
    title: str
    message: str
    sentiment_before: str
    sentiment_after: str
    sentiment_change: float
    source: str
    action_required: bool
    metadata: dict

class AlertThresholds:
    """Ngưỡng cảnh báo có thể cấu hình"""
    
    DEFAULTS = {
        'very_bearish_impact': 8.0,      # Impact >= 8 + very_bearish = CRITICAL
        'very_bullish_impact': 8.0,       # Impact >= 8 + very_bullish = HIGH
        'bearish_impact': 7.0,            # Impact >= 7 + bearish = HIGH
        'bullish_impact': 7.0,            # Impact >= 7 + bullish = HIGH
        'sentiment_change_threshold': 0.4,  # Thay đổi sentiment > 40%
        'volume_spike_multiplier': 3.0,   # Volume tăng 3x = alert
    }

class AlertEngine:
    """Engine xử lý alerts và gửi webhook"""
    
    def __init__(self, webhook_url: str, thresholds: dict = None):
        self.webhook_url = webhook_url
        self.thresholds = thresholds or AlertThresholds.DEFAULTS
        self.alert_history: List[Alert] = []
        self.last_sentiment: dict = {}  # Lưu sentiment theo source/symbol
        self.session = requests.Session()
        
        # Cooldown để tránh spam alerts
        self.cooldown_seconds = 300  # 5 phút
    
    def evaluate(self, sentiment_result, symbol: str = None) -> Optional[Alert]:
        """
        Đánh giá kết quả sentiment và tạo alert nếu cần
        
        Returns:
            Alert object nếu cần cảnh báo, None nếu không
        """
        source_key = f"{sentiment_result.source}:{symbol or 'general'}"
        
        # Kiểm tra cooldown
        if self._is_in_cooldown(source_key):
            return None
        
        # Xác định priority
        priority = self._calculate_priority(sentiment_result)
        
        # Kiểm tra thay đổi sentiment đột ngột
        sentiment_change = 0.0
        if source_key in self.last_sentiment:
            old_sentiment = self.last_sentiment[source_key]
            sentiment_change = abs(sentiment_result.confidence - old_sentiment)
        
        # Tạo alert nếu vượt ngưỡng
        if (priority.value >= AlertPriority.HIGH.value or 
            sentiment_change >= self.thresholds['sentiment_change_threshold']):
            
            alert = Alert(
                id=f"{source_key}_{int(time.time())}",
                timestamp=datetime.now().isoformat(),
                priority=priority,
                title=self._generate_title(sentiment_result, priority),
                message=self._generate_message(sentiment_result, sentiment_change),
                sentiment_before=str(self.last_sentiment.get(source_key, 'N/A')),
                sentiment_after=f"{sentiment_result.sentiment.value} ({sentiment_result.confidence:.1%})",
                sentiment_change=sentiment_change,
                source=sentiment_result.source,
                action_required=priority.value >= AlertPriority.HIGH.value,
                metadata={
                    'keywords': sentiment_result.keywords,
                    'impact_score': sentiment_result.impact_score,
                    'processing_time_ms': sentiment_result.processing_time_ms
                }
            )
            
            # Lưu và gửi
            self.alert_history.append(alert)
            self.last_sentiment[source_key] = sentiment_result.confidence
            self._last_alert_time[source_key] = time.time()
            
            return alert
        
        return None
    
    def send_webhook(self, alert: Alert) -> bool:
        """Gửi alert đến webhook endpoint"""
        payload = {
            'event': 'sentiment_alert',
            'alert': asdict(alert),
            'webhook_time': datetime.now().isoformat()
        }
        
        try:
            response = self.session.post(
                self.webhook_url,
                json=payload,
                timeout=5,
                headers={'Content-Type': 'application/json'}
            )
            
            if response.status_code == 200:
                print(f"✅ Alert {alert.id} đã gửi: [{alert.priority.name}] {alert.title}")
                return True
            else:
                print(f"❌ Webhook lỗi: {response.status_code}")
                return False
                
        except requests.exceptions.RequestException as e:
            print(f"❌ Webhook exception: {e}")
            return False
    
    def _calculate_priority(self, sentiment) -> AlertPriority:
        """Tính toán priority dựa trên sentiment và impact"""
        sentiment_str = sentiment.sentiment.value
        impact = sentiment.impact_score
        
        if impact >= self.thresholds['very_bearish_impact'] and 'bearish' in sentiment_str:
            return AlertPriority.CRITICAL
        if impact >= self.thresholds['very_bullish_impact'] and 'bullish' in sentiment_str:
            return AlertPriority.HIGH
        if impact >= self.thresholds['bearish_impact'] and 'bearish' in sentiment_str:
            return AlertPriority.HIGH
        if impact >= self.thresholds['bullish_impact'] and 'bullish' in sentiment_str:
            return AlertPriority.HIGH
        if 'bearish' in sentiment_str or 'bullish' in sentiment_str:
            return AlertPriority.MEDIUM
        
        return AlertPriority.LOW
    
    def _generate_title(self, sentiment, priority: AlertPriority) -> str:
        """Tạo title cho alert"""
        emoji = {
            AlertPriority.CRITICAL: "🚨",
            AlertPriority.HIGH: "⚠️",
            AlertPriority.MEDIUM: "📊",
            AlertPriority.LOW: "ℹ️"
        }
        
        sentiment_emoji = {
            'very_bearish': '📉📉',
            'bearish': '📉',
            'neutral': '➡️',
            'bullish': '📈',
            'very_bullish': '📈📈'
        }
        
        return (f"{emoji.get(priority, '📊')} "
                f"{sentiment_emoji.get(sentiment.sentiment.value, '➡️')} "
                f"{sentiment.sentiment.value.upper()} - "
                f"Impact: {sentiment.impact_score}/10")
    
    def _generate_message(self, sentiment, change: float) -> str:
        """Tạo message chi tiết"""
        msg = f"Cảm xúc thị trường: {sentiment.sentiment.value}\n"
        msg += f"Độ tin cậy: {sentiment.confidence:.1%}\n"
        msg += f"Impact Score: {sentiment.impact_score}/10\n"
        msg += f"Từ khoá: {', '.join(sentiment.keywords[:5])}\n"
        
        if change > 0:
            msg += f"\n⚡ Thay đổi: {change:.1%} so với lần đọc trước"
        
        return msg
    
    @property
    def _last_alert_time(self) -> dict:
        if not hasattr(self, '_last_alert_dict'):
            self._last_alert_dict = {}
        return self._last_alert_dict
    
    def _is_in_cooldown(self, source_key: str) -> bool:
        """Kiểm tra xem có đang trong cooldown không"""
        last_time = self._last_alert_time.get(source_key, 0)
        return (time.time() - last_time) < self.cooldown_seconds


============== DEMO ==============

if __name__ == "__main__": webhook_url = os.getenv('WEBHOOK_URL', 'https://httpbin.org/post') engine = AlertEngine(webhook_url) # Mock sentiment results from sentiment_analyzer import NewsSentiment, SentimentLabel test_sentiments = [ NewsSentiment( source="cryptopanic", title="Bitcoin ETF record inflow", content="...", sentiment=SentimentLabel.VERY_BULLISH, confidence=0.92, keywords=["BTC", "ETF", "inflow", "BlackRock"], impact_score=8.5, timestamp=datetime.now().isoformat(), processing_time_ms=42.3 ), NewsSentiment( source="twitter", title="Altcoin dump incoming?", content="...", sentiment=SentimentLabel.BEARISH, confidence=0.78, keywords=["altcoin", "dump", "sell"], impact_score=7.2, timestamp=datetime.now().isoformat(), processing_time_ms=38.1 ) ] print("🔔 Test Alert Engine:") print("="