Skill: dorjenorbulim/crypto-sniping (version 1.0.0)

# Crypto Sniping — Binance CEX Trading Bot **Version:** 1.0.0 **Author:** Subhuti **Chains:** BTC, ETH, SOL (Binance Spot & Futures) --- ## Quick Start 1. **Set API keys:** ```bash export BINANCE_API_KEY="your_key" export BINANCE_API_SECRET="your_secret" ``` 2. **Test mode first:** ```bash openclaw skills crypto-sniping --test ``` 3. **Live trading:** ```bash openclaw skills crypto-sniping --live ``` --- ## Features ### 📊 Signal Generation - **Technical Indicators:** RSI, MACD, Bollinger Bands, EMA crossovers - **Volume Analysis:** Unusual volume spikes, order flow imbalance - **Support/Resistance:** Auto-detect key levels, breakout alerts ### 🤖 Auto-Trading - **Entry Conditions:** RSI < 30 (oversold), MACD bullish crossover, volume spike > 200% - **Exit Conditions:** RSI > 70 (overbought), take-profit hit, stop-loss triggered - **Position Sizing:** Fixed amount or % of portfolio per trade ### 🛡️ Risk Management - **Stop-Loss:** Fixed % (e.g., -5%) or trailing stop - **Take-Profit:** Tiered (sell 50% at 2x, 25% at 5x, 25% at 10x) - **Daily Limits:** Max trades per day, max loss per day ### 🐋 Whale Tracking - **Large Orders:** Alert when >$1M order hits order book - **Liquidation Cascades:** Monitor funding rates, liquidation clusters - **Smart Money Flow:** Track exchange inflows/outflows ### 📱 Notifications - **Telegram:** Real-time alerts for signals, fills, liquidations - **Web Dashboard:** View open positions, P&L, trade history --- ## Configuration Create `config.yaml`: ```yaml # Trading Settings trading: mode: paper # paper or live assets: [BTCUSDT, ETHUSDT, SOLUSDT] default_position_size: 100 # USDT per trade max_daily_trades: 5 max_daily_loss: 500 # USDT # Entry Signals entry: rsi_oversold: 30 rsi_period: 14 macd_fast: 12 macd_slow: 26 volume_spike_threshold: 2.0 # 200% of average min_confidence: 2 # Require 2+ signals to trigger # Risk Management risk: stop_loss_percent: 5 take_profit_tiers: - { percent: 100, sell: 50 } # At 100% profit, sell 50% - { percent: 400, sell: 25 } # At 400% profit, sell 25% - { percent: 900, sell: 25 } # At 900% profit, sell 25% trailing_stop: true trailing_stop_percent: 10 # Whale Alerts whale: min_order_size: 1000000 # USDT liquidation_threshold: 10000000 # USDT funding_rate_alert: 0.01 # 1% # Notifications notifications: telegram: enabled: true bot_token: "${TELEGRAM_BOT_TOKEN}" chat_id: "${TELEGRAM_CHAT_ID}" log_level: INFO ``` --- ## Commands ```bash # Start monitoring (paper trading) openclaw skills crypto-sniping start --config config.yaml # Check signals without trading openclaw skills crypto-sniping scan # View open positions openclaw skills crypto-sniping positions # View trade history openclaw skills crypto-sniping history --days 7 # View whale alerts openclaw skills crypto-sniping whales --threshold 1000000 # Stop bot openclaw skills crypto-sniping stop # Backtest strategy openclaw skills crypto-sniping backtest --asset BTCUSDT --days 30 ``` --- ## Architecture ``` crypto-sniping/ ├── src/ │ ├── binance_client.py # API wrapper │ ├── signals.py # Technical analysis │ ├── trader.py # Order execution │ ├── risk_manager.py # Position/risk management │ ├── whale_tracker.py # Large order monitoring │ └── notifier.py # Telegram alerts ├── config.yaml # User configuration ├── trades.db # SQLite trade log └── SKILL.md # This file ``` --- ## Safety First ⚠️ **Never share API keys in chat** ⚠️ **Start with paper trading** ⚠️ **Set daily loss limits** ⚠️ **Use restricted API keys** (trading only, no withdrawals) --- ## Troubleshooting **"Invalid API key"** - Check key permissions (Enable Spot & Futures Trading) - Ensure IP whitelist includes your machine **"Insufficient balance"** - Check USDT balance in Spot wallet - Paper mode uses virtual balance **"No signals generated"** - Lower confidence threshold in config - Check if markets are trending (signals work best in ranging markets) --- ## Future Enhancements - [ ] Machine learning signal prediction - [ ] Multi-exchange arbitrage - [ ] Options strategies - [ ] Copy trading (follow whale wallets) - [ ] Social sentiment analysis --- _"The market can stay irrational longer than you can stay solvent."_ — John Maynard Keynes

path: requirements.txt
content: requests>=2.31.0
pyyaml>=6.0
numpy>=1.24.0


path: src/binance_client.py
content: """Binance API client wrapper."""
import os
import hmac
import hashlib
import time
import requests
from typing import Dict, List, Optional
from urllib.parse import urlencode

class BinanceClient:
    """Binance API client with rate limiting and error handling."""
    
    BASE_URL = "https://api.binance.com"
    FAPI_URL = "https://fapi.binance.com"  # Futures
    
    def __init__(self, [REDACTED_SECRET] = None, api_secret: Optional[str] = None, testnet: bool = False):
        self.[REDACTED_SECRET] or os.getenv("BINANCE_API_KEY")
        self.api_secret = api_secret or os.getenv("BINANCE_API_SECRET")
        self.testnet = testnet
        
        if not self.api_key or not self.api_secret:
            raise ValueError("Binance API key and secret required. Set BINANCE_API_KEY and BINANCE_API_SECRET env vars.")
        
        self.session = requests.Session()
        self.session.headers.update({
            "X-MBX-APIKEY": self.api_key
        })
    
    def _generate_signature(self, query_string: str) -> str:
        """Generate HMAC SHA256 signature."""
        return hmac.new(
            self.api_secret.encode("utf-8"),
            query_string.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()
    
    def _get_timestamp(self) -> int:
        """Get current timestamp in milliseconds."""
        return int(time.time() * 1000)
    
    def _request(self, method: str, endpoint: str, params: Optional[Dict] = None, signed: bool = False, futures: bool = False) -> Dict:
        """Make API request."""
        base = self.FAPI_URL if futures else self.BASE_URL
        url = f"{base}{endpoint}"
        
        if params is None:
            params = {}
        
        if signed:
            params["timestamp"] = self._get_timestamp()
            query_string = urlencode(params)
            params["signature"] = self._generate_signature(query_string)
        
        if method == "GET":
            response = self.session.get(url, params=params)
        elif method == "POST":
            response = self.session.post(url, data=params)
        elif method == "DELETE":
            response = self.session.delete(url, params=params)
        else:
            raise ValueError(f"Unsupported HTTP method: {method}")
        
        response.raise_for_status()
        return response.json()
    
    # === SPOT API ===
    
    def get_account(self) -> Dict:
        """Get account information."""
        return self._request("GET", "/api/v3/account", signed=True)
    
    def get_balance(self, asset: str) -> float:
        """Get balance for specific asset."""
        account = self.get_account()
        for balance in account.get("balances", []):
            if balance["asset"] == asset:
                return float(balance["free"]) + float(balance["locked"])
        return 0.0
    
    def get_symbol_price(self, symbol: str) -> float:
        """Get current price for symbol."""
        data = self._request("GET", "/api/v3/ticker/price", {"symbol": symbol})
        return float(data["price"])
    
    def get_klines(self, symbol: str, interval: str = "1h", limit: int = 100) -> List:
        """Get OHLCV data."""
        return self._request("GET", "/api/v3/klines", {
            "symbol": symbol,
            "interval": interval,
            "limit": limit
        })
    
    def get_orderbook(self, symbol: str, limit: int = 100) -> Dict:
        """Get order book."""
        return self._request("GET", "/api/v3/depth", {
            "symbol": symbol,
            "limit": limit
        })
    
    def place_order(self, symbol: str, side: str, order_type: str, 
                   quantity: Optional[float] = None, quote_quantity: Optional[float] = None,
                   price: Optional[float] = None, stop_price: Optional[float] = None) -> Dict:
        """Place order."""
        params = {
            "symbol": symbol,
            "side": side.upper(),
            "type": order_type.upper()
        }
        
        if quantity:
            params["quantity"] = quantity
        if quote_quantity:
            params["quoteOrderQty"] = quote_quantity
        if price:
            params["price"] = price
        if stop_price:
            params["stopPrice"] = stop_price
        
        return self._request("POST", "/api/v3/order", params, signed=True)
    
    def get_open_orders(self, symbol: Optional[str] = None) -> List:
        """Get open orders."""
        params = {}
        if symbol:
            params["symbol"] = symbol
        return self._request("GET", "/api/v3/openOrders", params, signed=True)
    
    def cancel_order(self, symbol: str, order_id: int) -> Dict:
        """Cancel order."""
        return self._request("DELETE", "/api/v3/order", {
            "symbol": symbol,
            "orderId": order_id
        }, signed=True)
    
    # === FUTURES API ===
    
    def get_futures_account(self) -> Dict:
        """Get futures account information."""
        return self._request("GET", "/fapi/v2/account", signed=True, futures=True)
    
    def get_futures_klines(self, symbol: str, interval: str = "1h", limit: int = 100) -> List:
        """Get futures OHLCV data."""
        return self._request("GET", "/fapi/v1/klines", {
            "symbol": symbol,
            "interval": interval,
            "limit": limit
        }, futures=True)
    
    def get_funding_rate(self, symbol: str) -> float:
        """Get current funding rate."""
        data = self._request("GET", "/fapi/v1/premiumIndex", {"symbol": symbol}, futures=True)
        return float(data.get("lastFundingRate", 0))
    
    def get_liquidations(self, symbol: str, start_time: Optional[int] = None, 
                        end_time: Optional[int] = None, limit: int = 100) -> List:
        """Get forced liquidation orders."""
        params = {"symbol": symbol, "limit": limit}
        if start_time:
            params["startTime"] = start_time
        if end_time:
            params["endTime"] = end_time
        return self._request("GET", "/fapi/v1/forceOrders", params, futures=True)
    
    def get_recent_trades(self, symbol: str, limit: int = 100) -> List:
        """Get recent trades."""
        return self._request("GET", "/api/v3/trades", {
            "symbol": symbol,
            "limit": limit
        })


path: src/bot.py
content: #!/usr/bin/env python3
"""Main trading bot runner."""
import os
import sys
import time
import logging
import argparse
import yaml
from typing import Dict, List
from datetime import datetime

from binance_client import BinanceClient
from signals import TechnicalAnalyzer, SignalType
from trader import Trader, RiskManager
from whale_tracker import WhaleTracker
from notifier import Notifier

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('trading_bot.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class TradingBot:
    """Main trading bot orchestrator."""
    
    def __init__(self, config_path: str = "config.yaml"):
        self.config = self._load_config(config_path)
        self.client = BinanceClient()
        self.analyzer = TechnicalAnalyzer(
            rsi_period=self.config['entry'].get('rsi_period', 14),
            macd_fast=self.config['entry'].get('macd_fast', 12),
            macd_slow=self.config['entry'].get('macd_slow', 26)
        )
        self.risk_manager = RiskManager(
            max_daily_trades=self.config['risk'].get('max_daily_trades', 5),
            max_daily_loss=self.config['risk'].get('max_daily_loss', 500),
            default_position_size=self.config['trading'].get('default_position_size', 100),
            stop_loss_percent=self.config['risk'].get('stop_loss_percent', 5),
            take_profit_tiers=self.config['risk'].get('take_profit_tiers', [
                {"percent": 100, "sell": 50},
                {"percent": 400, "sell": 25},
                {"percent": 900, "sell": 25}
            ])
        )
        self.trader = Trader(
            client=self.client,
            risk_manager=self.risk_manager,
            paper_mode=self.config['trading'].get('mode', 'paper') == 'paper'
        )
        self.whale_tracker = WhaleTracker(
            client=self.client,
            min_order_size=self.config['whale'].get('min_order_size', 1_000_000),
            liquidation_threshold=self.config['whale'].get('liquidation_threshold', 10_000_000),
            funding_rate_alert=self.config['whale'].get('funding_rate_alert', 0.01)
        )
        self.notifier = Notifier()
        self.symbols = self.config['trading'].get('assets', ['BTCUSDT', 'ETHUSDT', 'SOLUSDT'])
        self.running = False
    
    def _load_config(self, path: str) -> Dict:
        """Load configuration from YAML."""
        if not os.path.exists(path):
            logger.warning(f"Config file not found: {path}, using defaults")
            return self._default_config()
        
        with open(path, 'r') as f:
            return yaml.safe_load(f)
    
    def _default_config(self) -> Dict:
        """Return default configuration."""
        return {
            'trading': {
                'mode': 'paper',
                'assets': ['BTCUSDT', 'ETHUSDT', 'SOLUSDT'],
                'default_position_size': 100,
                'max_daily_trades': 5,
                'max_daily_loss': 500
            },
            'entry': {
                'rsi_oversold': 30,
                'rsi_period': 14,
                'macd_fast': 12,
                'macd_slow': 26,
                'volume_spike_threshold': 2.0,
                'min_confidence': 2
            },
            'risk': {
                'stop_loss_percent': 5,
                'take_profit_tiers': [
                    {'percent': 100, 'sell': 50},
                    {'percent': 400, 'sell': 25},
                    {'percent': 900, 'sell': 25}
                ],
                'trailing_stop': True,
                'trailing_stop_percent': 10
            },
            'whale': {
                'min_order_size': 1_000_000,
                'liquidation_threshold': 10_000_000,
                'funding_rate_alert': 0.01
            },
            'notifications': {
                'telegram': {'enabled': False},
                'log_level': 'INFO'
            }
        }
    
    def scan_signals(self) -> List[Dict]:
        """Scan all symbols for trading signals."""
        signals = []
        
        for symbol in self.symbols:
            try:
                # Get kline data
                klines = self.client.get_klines(symbol, interval='1h', limit=100)
                
                # Analyze
                signal = self.analyzer.analyze(klines)
                signal.symbol = symbol  # Attach symbol
                
                if signal.type != SignalType.HOLD and signal.confidence >= self.config['entry'].get('min_confidence', 2):
                    signals.append({
                        'symbol': symbol,
                        'signal': signal,
                        'indicators': self.analyzer.get_indicator_values(klines)
                    })
                    self.notifier.notify_signal(signal)
            
            except Exception as e:
                logger.error(f"Error scanning {symbol}: {e}")
        
        return signals
    
    def check_whale_activity(self):
        """Check for whale activity."""
        for symbol in self.symbols:
            try:
                # Scan orderbook
                alerts = self.whale_tracker.scan_orderbook(symbol)
                for alert in alerts:
                    self.notifier.notify_whale_alert(alert)
                
                # Check liquidations
                liq_alerts = self.whale_tracker.check_liquidations(symbol)
                for alert in liq_alerts:
                    self.notifier.notify_whale_alert(alert)
                
                # Check funding rates
                funding_alert = self.whale_tracker.check_funding_rate(symbol)
                if funding_alert:
                    self.notifier.notify_whale_alert(funding_alert)
            
            except Exception as e:
                logger.error(f"Error checking whale activity for {symbol}: {e}")
    
    def run(self):
        """Run the trading bot loop."""
        logger.info(f"Starting trading bot in {self.config['trading'].get('mode', 'paper')} mode")
        logger.info(f"Monitoring: {', '.join(self.symbols)}")
        
        self.running = True
        
        while self.running:
            try:
                # Check open positions
                self.trader.check_positions()
                
                # Scan for signals
                signals = self.scan_signals()
                
                # Execute trades for high-confidence signals
                for sig_data in signals:
                    signal = sig_data['signal']
                    if signal.confidence >= 4:  # Only auto-trade high confidence
                        trade = self.trader.execute_signal(signal)
                        if trade:
                            self.notifier.notify_trade_opened(trade)
                
                # Check whale activity every 5 minutes
                if int(time.time()) % 300 < 60:
                    self.check_whale_activity()
                
                # Sleep before next iteration
                time.sleep(60)  # Check every minute
                
            except KeyboardInterrupt:
                logger.info("Shutting down...")
                self.running = False
            except Exception as e:
                logger.error(f"Error in main loop: {e}")
                self.notifier.notify_error(str(e))
                time.sleep(60)
    
    def get_status(self) -> Dict:
        """Get current bot status."""
        open_positions = self.trader.get_open_positions()
        recent_trades = self.trader.get_trade_history(days=1)
        
        return {
            'mode': self.config['trading'].get('mode', 'paper'),
            'symbols': self.symbols,
            'open_positions': len(open_positions),
            'positions': [{'symbol': p.symbol, 'side': p.side, 'entry': p.entry_price} for p in open_positions],
            'today_trades': len(recent_trades),
            'today_pnl': sum(t.pnl or 0 for t in recent_trades)
        }

def main():
    parser = argparse.ArgumentParser(description='Crypto Sniping Bot')
    parser.add_argument('--config', default='config.yaml', help='Config file path')
    parser.add_argument('--scan', action='store_true', help='Scan signals only (no trading)')
    parser.add_argument('--status', action='store_true', help='Show bot status')
    parser.add_argument('--whales', action='store_true', help='Check whale activity')
    args = parser.parse_args()
    
    bot = TradingBot(args.config)
    
    if args.scan:
        print("Scanning for signals...")
        signals = bot.scan_signals()
        for sig in signals:
            print(f"\n{sig['symbol']}: {sig['signal'].type.value.upper()} (confidence: {sig['signal'].confidence})")
            print(f"  Indicators: {', '.join(sig['signal'].indicators)}")
            print(f"  Current indicators: {sig['indicators']}")
    
    elif args.status:
        status = bot.get_status()
        print(f"\nMode: {status['mode']}")
        print(f"Symbols: {', '.join(status['symbols'])}")
        print(f"Open positions: {status['open_positions']}")
        for pos in status['positions']:
            print(f"  - {pos['symbol']} {pos['side']} @ ${pos['entry']:.2f}")
        print(f"Today's trades: {status['today_trades']}")
        print(f"Today's P&L: ${status['today_pnl']:.2f}")
    
    elif args.whales:
        print("Checking whale activity...")
        bot.check_whale_activity()
        alerts = bot.whale_tracker.get_recent_alerts(minutes=60)
        for alert in alerts:
            print(f"\n{alert.type}: {alert.symbol} {alert.side} ${alert.total_value:,.0f}")
            print(f"  {alert.details}")
    
    else:
        bot.run()

if __name__ == "__main__":
    main()


path: src/notifier.py
content: """Notification system for alerts and trade updates."""
import logging
import os
from typing import Optional
import requests

from .signals import Signal
from .trader import Trade, Position
from .whale_tracker import WhaleAlert

class Notifier:
    """Send notifications via Telegram and logging."""
    
    def __init__(self, telegram_token: Optional[str] = None, 
                 telegram_chat_id: Optional[str] = None):
        self.telegram_token = telegram_token or os.getenv("TELEGRAM_BOT_TOKEN")
        self.telegram_chat_id = telegram_chat_id or os.getenv("TELEGRAM_CHAT_ID")
        self.enabled = bool(self.telegram_token and self.telegram_chat_id)
        
        if not self.enabled:
            logging.warning("Telegram notifications disabled - set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID")
    
    def _send_telegram(self, message: str):
        """Send message via Telegram."""
        if not self.enabled:
            return
        
        try:
            url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
            payload = {
                "chat_id": self.telegram_chat_id,
                "text": message,
                "parse_mode": "Markdown"
            }
            requests.post(url, json=payload, timeout=5)
        except Exception as e:
            logging.error(f"Failed to send Telegram message: {e}")
    
    def notify_signal(self, signal: Signal):
        """Notify about trading signal."""
        emoji = "🟢" if signal.type.value == "buy" else "🔴" if signal.type.value == "sell" else "⚪"
        confidence_stars = "⭐" * signal.confidence
        
        message = f"""
{emoji} **Trading Signal: {signal.type.value.upper()}**

**Confidence:** {confidence_stars} ({signal.confidence}/5)
**Price:** ${signal.price:,.2f}
**Indicators:** {', '.join(signal.indicators)}

_Execute with caution_
"""
        logging.info(f"Signal: {signal.type.value} {signal.symbol} @ ${signal.price}")
        self._send_telegram(message)
    
    def notify_trade_opened(self, trade: Trade):
        """Notify about opened trade."""
        emoji = "🚀" if trade.side == "BUY" else "📉"
        
        message = f"""
{emoji} **Position Opened**

**Symbol:** {trade.symbol}
**Side:** {trade.side}
**Entry:** ${trade.entry_price:,.2f}
**Size:** {trade.quantity:.6f}
**Value:** ${trade.quantity * trade.entry_price:,.2f}

_Good luck! 🎯_
"""
        logging.info(f"Opened {trade.side} {trade.symbol} @ ${trade.entry_price}")
        self._send_telegram(message)
    
    def notify_trade_closed(self, trade: Trade):
        """Notify about closed trade."""
        if trade.pnl is None:
            return
        
        emoji = "✅" if trade.pnl > 0 else "❌"
        pnl_emoji = "📈" if trade.pnl > 0 else "📉"
        
        message = f"""
{emoji} **Position Closed**

**Symbol:** {trade.symbol}
**Side:** {trade.side}
**Entry:** ${trade.entry_price:,.2f}
**Exit:** ${trade.exit_price:,.2f}
{pnl_emoji} **P&L:** ${trade.pnl:,.2f} ({trade.pnl_percent:+.2f}%)

_Next trade loading..._
"""
        logging.info(f"Closed {trade.symbol} P&L: ${trade.pnl:.2f}")
        self._send_telegram(message)
    
    def notify_whale_alert(self, alert: WhaleAlert):
        """Notify about whale activity."""
        emoji_map = {
            "LARGE_ORDER": "🐋",
            "LIQUIDATION": "💥",
            "FUNDING_SPIKE": "⚡"
        }
        emoji = emoji_map.get(alert.type, "📊")
        
        message = f"""
{emoji} **Whale Alert: {alert.type}**

**Symbol:** {alert.symbol}
**Side:** {alert.side}
**Size:** ${alert.total_value:,.0f}
**Price:** ${alert.price:,.2f}

_{alert.details}_
"""
        logging.info(f"Whale: {alert.type} {alert.symbol} ${alert.total_value:,.0f}")
        self._send_telegram(message)
    
    def notify_daily_summary(self, trades: int, pnl: float, open_positions: int):
        """Send daily trading summary."""
        emoji = "🟢" if pnl >= 0 else "🔴"
        
        message = f"""
{emoji} **Daily Trading Summary**

**Trades:** {trades}
**P&L:** ${pnl:,.2f}
**Open Positions:** {open_positions}

_Keep building 📈_
"""
        self._send_telegram(message)
    
    def notify_error(self, error_message: str):
        """Notify about error."""
        logging.error(error_message)
        
        message = f"""
⚠️ **Bot Error**

```{error_message}```

_Check logs for details_
"""
        self._send_telegram(message)


path: src/signals.py
content: """Technical analysis signal generation."""
import numpy as np
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from enum import Enum

class SignalType(Enum):
    BUY = "buy"
    SELL = "sell"
    HOLD = "hold"

@dataclass
class Signal:
    type: SignalType
    confidence: int  # 1-5 scale
    indicators: List[str]
    price: float
    timestamp: int

class TechnicalAnalyzer:
    """Generate trading signals from technical indicators."""
    
    def __init__(self, rsi_period: int = 14, 
                 macd_fast: int = 12, macd_slow: int = 26, macd_signal: int = 9,
                 bb_period: int = 20, bb_std: float = 2.0):
        self.rsi_period = rsi_period
        self.macd_fast = macd_fast
        self.macd_slow = macd_slow
        self.macd_signal = macd_signal
        self.bb_period = bb_period
        self.bb_std = bb_std
    
    def _calculate_rsi(self, closes: np.ndarray) -> float:
        """Calculate RSI."""
        if len(closes) < self.rsi_period + 1:
            return 50.0
        
        deltas = np.diff(closes)
        gains = np.where(deltas > 0, deltas, 0)
        losses = np.where(deltas < 0, -deltas, 0)
        
        avg_gains = np.mean(gains[-self.rsi_period:])
        avg_losses = np.mean(losses[-self.rsi_period:])
        
        if avg_losses == 0:
            return 100.0
        
        rs = avg_gains / avg_losses
        rsi = 100 - (100 / (1 + rs))
        return rsi
    
    def _calculate_macd(self, closes: np.ndarray) -> Tuple[float, float, float]:
        """Calculate MACD line, signal line, and histogram."""
        if len(closes) < self.macd_slow + self.macd_signal:
            return 0.0, 0.0, 0.0
        
        ema_fast = self._calculate_ema(closes, self.macd_fast)
        ema_slow = self._calculate_ema(closes, self.macd_slow)
        
        macd_line = ema_fast - ema_slow
        signal_line = self._calculate_ema(np.array([macd_line]), self.macd_signal)
        histogram = macd_line - signal_line
        
        return macd_line, signal_line, histogram
    
    def _calculate_ema(self, data: np.ndarray, period: int) -> float:
        """Calculate EMA."""
        if len(data) < period:
            return data[-1] if len(data) > 0 else 0.0
        
        alpha = 2 / (period + 1)
        ema = data[0]
        for price in data[1:]:
            ema = alpha * price + (1 - alpha) * ema
        return ema
    
    def _calculate_bollinger_bands(self, closes: np.ndarray) -> Tuple[float, float, float]:
        """Calculate Bollinger Bands."""
        if len(closes) < self.bb_period:
            middle = closes[-1] if len(closes) > 0 else 0.0
            return middle, middle, middle
        
        sma = np.mean(closes[-self.bb_period:])
        std = np.std(closes[-self.bb_period:])
        
        upper = sma + (self.bb_std * std)
        lower = sma - (self.bb_std * std)
        
        return upper, sma, lower
    
    def _calculate_volume_spike(self, volumes: np.ndarray) -> float:
        """Calculate volume spike ratio (current vs average)."""
        if len(volumes) < 20:
            return 1.0
        
        current_volume = volumes[-1]
        avg_volume = np.mean(volumes[-20:-1])
        
        if avg_volume == 0:
            return 1.0
        
        return current_volume / avg_volume
    
    def _detect_support_resistance(self, highs: np.ndarray, lows: np.ndarray, 
                                    closes: np.ndarray, lookback: int = 20) -> Tuple[float, float]:
        """Detect support and resistance levels."""
        if len(closes) < lookback:
            return closes[-1] * 0.95, closes[-1] * 1.05 if len(closes) > 0 else (0, 0)
        
        recent_highs = highs[-lookback:]
        recent_lows = lows[-lookback:]
        
        resistance = np.max(recent_highs)
        support = np.min(recent_lows)
        
        return support, resistance
    
    def analyze(self, klines: List[List]) -> Signal:
        """Generate trading signal from kline data."""
        if len(klines) < 50:
            return Signal(SignalType.HOLD, 0, [], 0.0, 0)
        
        # Parse klines: [timestamp, open, high, low, close, volume, ...]
        timestamps = np.array([k[0] for k in klines])
        opens = np.array([float(k[1]) for k in klines])
        highs = np.array([float(k[2]) for k in klines])
        lows = np.array([float(k[3]) for k in klines])
        closes = np.array([float(k[4]) for k in klines])
        volumes = np.array([float(k[5]) for k in klines])
        
        current_price = closes[-1]
        
        # Calculate indicators
        rsi = self._calculate_rsi(closes)
        macd_line, signal_line, macd_hist = self._calculate_macd(closes)
        bb_upper, bb_middle, bb_lower = self._calculate_bollinger_bands(closes)
        volume_spike = self._calculate_volume_spike(volumes)
        support, resistance = self._detect_support_resistance(highs, lows, closes)
        
        # Generate signals
        buy_signals = []
        sell_signals = []
        
        # RSI signals
        if rsi < 30:
            buy_signals.append("RSI_OVERSOLD")
        elif rsi > 70:
            sell_signals.append("RSI_OVERBOUGHT")
        
        # MACD signals
        if macd_hist > 0 and macd_hist > (macd_line * 0.1):  # Positive and growing
            buy_signals.append("MACD_BULLISH")
        elif macd_hist < 0:
            sell_signals.append("MACD_BEARISH")
        
        # Bollinger Band signals
        if current_price < bb_lower:
            buy_signals.append("BB_OVERSOLD")
        elif current_price > bb_upper:
            sell_signals.append("BB_OVERBOUGHT")
        
        # Volume spike
        if volume_spike > 2.0:
            if closes[-1] > closes[-2]:  # Price going up with volume
                buy_signals.append(f"VOLUME_SPIKE_{volume_spike:.1f}x")
            else:
                sell_signals.append(f"VOLUME_SPIKE_{volume_spike:.1f}x")
        
        # Support/Resistance bounce
        if current_price <= support * 1.02:  # Within 2% of support
            buy_signals.append("SUPPORT_BOUNCE")
        elif current_price >= resistance * 0.98:  # Within 2% of resistance
            sell_signals.append("RESISTANCE_REJECT")
        
        # EMA crossover (simple 9/21)
        ema_9 = self._calculate_ema(closes, 9)
        ema_21 = self._calculate_ema(closes, 21)
        if ema_9 > ema_21 * 1.005:  # Golden cross with buffer
            buy_signals.append("EMA_GOLDEN_CROSS")
        elif ema_9 < ema_21 * 0.995:
            sell_signals.append("EMA_DEATH_CROSS")
        
        # Determine final signal
        confidence = 0
        indicators = []
        signal_type = SignalType.HOLD
        
        if len(buy_signals) > len(sell_signals):
            confidence = min(len(buy_signals), 5)
            indicators = buy_signals
            signal_type = SignalType.BUY
        elif len(sell_signals) > len(buy_signals):
            confidence = min(len(sell_signals), 5)
            indicators = sell_signals
            signal_type = SignalType.SELL
        
        return Signal(
            type=signal_type,
            confidence=confidence,
            indicators=indicators,
            price=current_price,
            timestamp=int(timestamps[-1])
        )
    
    def get_indicator_values(self, klines: List[List]) -> Dict:
        """Get current indicator values for display."""
        if len(klines) < 50:
            return {}
        
        closes = np.array([float(k[4]) for k in klines])
        highs = np.array([float(k[2]) for k in klines])
        lows = np.array([float(k[3]) for k in klines])
        volumes = np.array([float(k[5]) for k in klines])
        
        return {
            "rsi": round(self._calculate_rsi(closes), 2),
            "macd": round(self._calculate_macd(closes)[0], 4),
            "macd_signal": round(self._calculate_macd(closes)[1], 4),
            "bb_upper": round(self._calculate_bollinger_bands(closes)[0], 2),
            "bb_middle": round(self._calculate_bollinger_bands(closes)[1], 2),
            "bb_lower": round(self._calculate_bollinger_bands(closes)[2], 2),
            "volume_spike": round(self._calculate_volume_spike(volumes), 2),
            "ema_9": round(self._calculate_ema(closes, 9), 2),
            "ema_21": round(self._calculate_ema(closes, 21), 2),
            "support": round(self._detect_support_resistance(highs, lows, closes)[0], 2),
            "resistance": round(self._detect_support_resistance(highs, lows, closes)[1], 2),
            "price": round(closes[-1], 2)
        }


path: src/trader.py
content: """Trading execution and risk management."""
import logging
import sqlite3
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
from datetime import datetime
import json

from .binance_client import BinanceClient
from .signals import Signal, SignalType

@dataclass
class Position:
    symbol: str
    entry_price: float
    quantity: float
    side: str  # "BUY" or "SELL"
    stop_loss: float
    take_profits: List[Dict]  # [{"price": float, "sell_percent": float}]
    timestamp: int
    order_id: Optional[int] = None

@dataclass
class Trade:
    id: int
    symbol: str
    side: str
    entry_price: float
    exit_price: Optional[float]
    quantity: float
    pnl: Optional[float]
    pnl_percent: Optional[float]
    timestamp: int
    status: str  # "OPEN", "CLOSED", "CANCELLED"

class RiskManager:
    """Manage position sizing and risk."""
    
    def __init__(self, max_daily_trades: int = 5, max_daily_loss: float = 500.0,
                 default_position_size: float = 100.0, stop_loss_percent: float = 5.0,
                 take_profit_tiers: Optional[List[Dict]] = None):
        self.max_daily_trades = max_daily_trades
        self.max_daily_loss = max_daily_loss
        self.default_position_size = default_position_size
        self.stop_loss_percent = stop_loss_percent
        
        # Default take profit tiers: 50% at 2x, 25% at 5x, 25% at 10x
        self.take_profit_tiers = take_profit_tiers or [
            {"percent": 100, "sell": 50},
            {"percent": 400, "sell": 25},
            {"percent": 900, "sell": 25}
        ]
    
    def calculate_position_size(self, signal_confidence: int, account_balance: float) -> float:
        """Calculate position size based on confidence and balance."""
        # Base size adjusted by confidence (1-5 scale)
        confidence_multiplier = signal_confidence / 3.0  # 0.33x to 1.67x
        size = self.default_position_size * confidence_multiplier
        
        # Cap at 10% of account
        max_size = account_balance * 0.10
        return min(size, max_size)
    
    def calculate_stop_loss(self, entry_price: float, side: str) -> float:
        """Calculate stop loss price."""
        if side == "BUY":
            return entry_price * (1 - self.stop_loss_percent / 100)
        else:
            return entry_price * (1 + self.stop_loss_percent / 100)
    
    def calculate_take_profits(self, entry_price: float, side: str) -> List[Dict]:
        """Calculate take profit levels."""
        take_profits = []
        for tier in self.take_profit_tiers:
            profit_percent = tier["percent"]
            sell_percent = tier["sell"]
            
            if side == "BUY":
                price = entry_price * (1 + profit_percent / 100)
            else:
                price = entry_price * (1 - profit_percent / 100)
            
            take_profits.append({
                "price": round(price, 2),
                "sell_percent": sell_percent,
                "hit": False
            })
        return take_profits
    
    def check_daily_limits(self, trades_today: int, pnl_today: float) -> bool:
        """Check if we can trade today."""
        if trades_today >= self.max_daily_trades:
            logging.warning(f"Daily trade limit reached: {trades_today}/{self.max_daily_trades}")
            return False
        
        if pnl_today <= -self.max_daily_loss:
            logging.warning(f"Daily loss limit hit: ${pnl_today:.2f}")
            return False
        
        return True

class Trader:
    """Execute trades and manage positions."""
    
    def __init__(self, client: BinanceClient, risk_manager: RiskManager, 
                 db_path: str = "trades.db", paper_mode: bool = True):
        self.client = client
        self.risk_manager = risk_manager
        self.db_path = db_path
        self.paper_mode = paper_mode
        self.positions: Dict[str, Position] = {}
        
        self._init_db()
        self._load_open_positions()
    
    def _init_db(self):
        """Initialize SQLite database."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS trades (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                symbol TEXT NOT NULL,
                side TEXT NOT NULL,
                entry_price REAL NOT NULL,
                exit_price REAL,
                quantity REAL NOT NULL,
                stop_loss REAL NOT NULL,
                take_profits TEXT NOT NULL,
                pnl REAL,
                pnl_percent REAL,
                timestamp INTEGER NOT NULL,
                status TEXT NOT NULL,
                order_id INTEGER,
                paper_mode BOOLEAN NOT NULL
            )
        """)
        
        conn.commit()
        conn.close()
    
    def _load_open_positions(self):
        """Load open positions from database."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("""
            SELECT symbol, entry_price, quantity, side, stop_loss, 
                   take_profits, timestamp, order_id
            FROM trades WHERE status = 'OPEN'
        """)
        
        for row in cursor.fetchall():
            position = Position(
                symbol=row[0],
                entry_price=row[1],
                quantity=row[2],
                side=row[3],
                stop_loss=row[4],
                take_profits=json.loads(row[5]),
                timestamp=row[6],
                order_id=row[7]
            )
            self.positions[position.symbol] = position
        
        conn.close()
    
    def execute_signal(self, signal: Signal) -> Optional[Trade]:
        """Execute a trading signal."""
        if signal.type == SignalType.HOLD:
            return None
        
        symbol = signal.symbol
        side = "BUY" if signal.type == SignalType.BUY else "SELL"
        
        # Check if we already have a position
        if symbol in self.positions:
            logging.info(f"Already have position in {symbol}, skipping signal")
            return None
        
        # Get account info for position sizing
        try:
            account = self.client.get_account()
            usdt_balance = float([b for b in account["balances"] if b["asset"] == "USDT"][0]["free"])
        except Exception as e:
            logging.error(f"Failed to get account balance: {e}")
            return None
        
        # Check daily limits
        trades_today, pnl_today = self._get_daily_stats()
        if not self.risk_manager.check_daily_limits(trades_today, pnl_today):
            return None
        
        # Calculate position size
        position_size = self.risk_manager.calculate_position_size(signal.confidence, usdt_balance)
        if position_size < 10:  # Minimum $10 trade
            logging.info(f"Position size too small: ${position_size:.2f}")
            return None
        
        # Calculate stop loss and take profits
        stop_loss = self.risk_manager.calculate_stop_loss(signal.price, side)
        take_profits = self.risk_manager.calculate_take_profits(signal.price, side)
        
        # Execute trade
        try:
            if self.paper_mode:
                order_id = self._paper_trade(symbol, side, position_size, signal.price)
            else:
                order = self.client.place_order(
                    symbol=symbol,
                    side=side,
                    order_type="MARKET",
                    quote_quantity=position_size
                )
                order_id = order["orderId"]
            
            # Record position
            position = Position(
                symbol=symbol,
                entry_price=signal.price,
                quantity=position_size / signal.price,
                side=side,
                stop_loss=stop_loss,
                take_profits=take_profits,
                timestamp=signal.timestamp,
                order_id=order_id
            )
            self.positions[symbol] = position
            self._save_position(position)
            
            logging.info(f"Opened {side} position in {symbol} at ${signal.price:.2f}")
            
            return Trade(
                id=0,  # Will be set by DB
                symbol=symbol,
                side=side,
                entry_price=signal.price,
                exit_price=None,
                quantity=position.quantity,
                pnl=None,
                pnl_percent=None,
                timestamp=signal.timestamp,
                status="OPEN"
            )
            
        except Exception as e:
            logging.error(f"Failed to execute trade: {e}")
            return None
    
    def _paper_trade(self, symbol: str, side: str, quantity: float, price: float) -> int:
        """Simulate a trade in paper mode."""
        import random
        return random.randint(1000000, 9999999)
    
    def _save_position(self, position: Position):
        """Save position to database."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("""
            INSERT INTO trades (symbol, side, entry_price, quantity, stop_loss,
                              take_profits, timestamp, status, order_id, paper_mode)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            position.symbol, position.side, position.entry_price, position.quantity,
            position.stop_loss, json.dumps(position.take_profits), position.timestamp,
            "OPEN", position.order_id, self.paper_mode
        ))
        
        conn.commit()
        conn.close()
    
    def check_positions(self):
        """Check open positions for exit conditions."""
        for symbol, position in list(self.positions.items()):
            try:
                current_price = self.client.get_symbol_price(symbol)
                self._evaluate_position(position, current_price)
            except Exception as e:
                logging.error(f"Failed to check position {symbol}: {e}")
    
    def _evaluate_position(self, position: Position, current_price: float):
        """Evaluate if position should be closed."""
        # Check stop loss
        if position.side == "BUY" and current_price <= position.stop_loss:
            self._close_position(position, current_price, "STOP_LOSS")
            return
        elif position.side == "SELL" and current_price >= position.stop_loss:
            self._close_position(position, current_price, "STOP_LOSS")
            return
        
        # Check take profits
        for tier in position.take_profits:
            if tier["hit"]:
                continue
            
            tp_price = tier["price"]
            if (position.side == "BUY" and current_price >= tp_price) or \
               (position.side == "SELL" and current_price <= tp_price):
                
                # In real trading, this would partially close the position
                tier["hit"] = True
                logging.info(f"Take profit hit for {position.symbol} at ${tp_price:.2f}")
                
                # If all TPs hit, close position
                if all(t["hit"] for t in position.take_profits):
                    self._close_position(position, current_price, "TAKE_PROFIT")
    
    def _close_position(self, position: Position, exit_price: float, reason: str):
        """Close a position."""
        # Calculate P&L
        if position.side == "BUY":
            pnl = (exit_price - position.entry_price) * position.quantity
            pnl_percent = (exit_price - position.entry_price) / position.entry_price * 100
        else:
            pnl = (position.entry_price - exit_price) * position.quantity
            pnl_percent = (position.entry_price - exit_price) / position.entry_price * 100
        
        # Update database
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("""
            UPDATE trades 
            SET exit_price = ?, pnl = ?, pnl_percent = ?, status = 'CLOSED'
            WHERE symbol = ? AND status = 'OPEN'
        """, (exit_price, pnl, pnl_percent, position.symbol))
        
        conn.commit()
        conn.close()
        
        # Remove from active positions
        del self.positions[position.symbol]
        
        logging.info(f"Closed {position.symbol} at ${exit_price:.2f} ({reason}). P&L: ${pnl:.2f} ({pnl_percent:.2f}%)")
    
    def _get_daily_stats(self) -> tuple:
        """Get today's trade count and P&L."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        today_start = int(datetime.now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000)
        
        cursor.execute("""
            SELECT COUNT(*), COALESCE(SUM(pnl), 0)
            FROM trades 
            WHERE timestamp >= ?
        """, (today_start,))
        
        result = cursor.fetchone()
        conn.close()
        
        return result[0], result[1]
    
    def get_open_positions(self) -> List[Position]:
        """Get all open positions."""
        return list(self.positions.values())
    
    def get_trade_history(self, days: int = 7) -> List[Trade]:
        """Get recent trade history."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        since = int((datetime.now().timestamp() - days * 86400) * 1000)
        
        cursor.execute("""
            SELECT id, symbol, side, entry_price, exit_price, quantity,
                   pnl, pnl_percent, timestamp, status
            FROM trades 
            WHERE timestamp >= ?
            ORDER BY timestamp DESC
        """, (since,))
        
        trades = []
        for row in cursor.fetchall():
            trades.append(Trade(
                id=row[0], symbol=row[1], side=row[2], entry_price=row[3],
                exit_price=row[4], quantity=row[5], pnl=row[6],
                pnl_percent=row[7], timestamp=row[8], status=row[9]
            ))
        
        conn.close()
        return trades


path: src/whale_tracker.py
content: """Whale tracking and large order monitoring."""
import logging
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime, timedelta

from .binance_client import BinanceClient

@dataclass
class WhaleAlert:
    symbol: str
    type: str  # "LARGE_ORDER", "LIQUIDATION", "FUNDING_SPIKE"
    side: str  # "BUY" or "SELL"
    size: float
    price: float
    total_value: float
    timestamp: int
    details: str

class WhaleTracker:
    """Track whale movements and large market events."""
    
    def __init__(self, client: BinanceClient, 
                 min_order_size: float = 1_000_000,
                 liquidation_threshold: float = 10_000_000,
                 funding_rate_alert: float = 0.01):
        self.client = client
        self.min_order_size = min_order_size
        self.liquidation_threshold = liquidation_threshold
        self.funding_rate_alert = funding_rate_alert
        self.recent_alerts: List[WhaleAlert] = []
    
    def scan_orderbook(self, symbol: str) -> List[WhaleAlert]:
        """Scan orderbook for large orders."""
        alerts = []
        
        try:
            orderbook = self.client.get_orderbook(symbol, limit=100)
            current_price = self.client.get_symbol_price(symbol)
            
            # Check bids (buy orders)
            for bid in orderbook.get("bids", [])[:20]:
                price, quantity = float(bid[0]), float(bid[1])
                value = price * quantity
                
                if value >= self.min_order_size:
                    alert = WhaleAlert(
                        symbol=symbol,
                        type="LARGE_ORDER",
                        side="BUY",
                        size=quantity,
                        price=price,
                        total_value=value,
                        timestamp=int(datetime.now().timestamp() * 1000),
                        details=f"Large bid: {quantity:.4f} @ ${price:,.2f} (${value:,.0f})"
                    )
                    alerts.append(alert)
                    self.recent_alerts.append(alert)
            
            # Check asks (sell orders)
            for ask in orderbook.get("asks", [])[:20]:
                price, quantity = float(ask[0]), float(ask[1])
                value = price * quantity
                
                if value >= self.min_order_size:
                    alert = WhaleAlert(
                        symbol=symbol,
                        type="LARGE_ORDER",
                        side="SELL",
                        size=quantity,
                        price=price,
                        total_value=value,
                        timestamp=int(datetime.now().timestamp() * 1000),
                        details=f"Large ask: {quantity:.4f} @ ${price:,.2f} (${value:,.0f})"
                    )
                    alerts.append(alert)
                    self.recent_alerts.append(alert)
        
        except Exception as e:
            logging.error(f"Failed to scan orderbook for {symbol}: {e}")
        
        return alerts
    
    def check_liquidations(self, symbol: str) -> List[WhaleAlert]:
        """Check for recent liquidations."""
        alerts = []
        
        try:
            # Get liquidations from last hour
            end_time = int(datetime.now().timestamp() * 1000)
            start_time = end_time - (60 * 60 * 1000)  # 1 hour ago
            
            liquidations = self.client.get_liquidations(symbol, start_time, end_time, limit=100)
            
            for liq in liquidations:
                value = float(liq.get("executedQty", 0)) * float(liq.get("avgPrice", 0))
                
                if value >= self.liquidation_threshold:
                    alert = WhaleAlert(
                        symbol=symbol,
                        type="LIQUIDATION",
                        side=liq.get("side", "UNKNOWN"),
                        size=float(liq.get("executedQty", 0)),
                        price=float(liq.get("avgPrice", 0)),
                        total_value=value,
                        timestamp=liq.get("time", int(datetime.now().timestamp() * 1000)),
                        details=f"Liquidation: {liq.get('side')} {value:,.0f} USDT"
                    )
                    alerts.append(alert)
                    self.recent_alerts.append(alert)
        
        except Exception as e:
            logging.error(f"Failed to check liquidations for {symbol}: {e}")
        
        return alerts
    
    def check_funding_rate(self, symbol: str) -> Optional[WhaleAlert]:
        """Check for unusual funding rates."""
        try:
            funding_rate = self.client.get_funding_rate(symbol)
            
            if abs(funding_rate) >= self.funding_rate_alert:
                direction = "positive" if funding_rate > 0 else "negative"
                
                alert = WhaleAlert(
                    symbol=symbol,
                    type="FUNDING_SPIKE",
                    side="LONGS_PAY" if funding_rate > 0 else "SHORTS_PAY",
                    size=0,
                    price=0,
                    total_value=abs(funding_rate),
                    timestamp=int(datetime.now().timestamp() * 1000),
                    details=f"High funding rate: {funding_rate:.4%} ({direction}) - {symbol}"
                )
                self.recent_alerts.append(alert)
                return alert
        
        except Exception as e:
            logging.error(f"Failed to check funding rate for {symbol}: {e}")
        
        return None
    
    def get_recent_alerts(self, minutes: int = 60) -> List[WhaleAlert]:
        """Get alerts from last N minutes."""
        cutoff = int((datetime.now() - timedelta(minutes=minutes)).timestamp() * 1000)
        return [a for a in self.recent_alerts if a.timestamp >= cutoff]
    
    def analyze_whale_activity(self, symbol: str) -> Dict:
        """Analyze overall whale activity for a symbol."""
        alerts = self.get_recent_alerts(minutes=60)
        symbol_alerts = [a for a in alerts if a.symbol == symbol]
        
        buy_pressure = sum(a.total_value for a in symbol_alerts if a.side == "BUY")
        sell_pressure = sum(a.total_value for a in symbol_alerts if a.side == "SELL")
        
        return {
            "symbol": symbol,
            "buy_pressure": buy_pressure,
            "sell_pressure": sell_pressure,
            "net_pressure": buy_pressure - sell_pressure,
            "alert_count": len(symbol_alerts),
            "dominant_side": "BUY" if buy_pressure > sell_pressure else "SELL",
            "significant": max(buy_pressure, sell_pressure) > 5_000_000
        }


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)