Skill: srikanthbellary/binance-spot-trader (version 1.0.0)

--- name: binance-spot-trader description: Autonomous Binance spot trading bot with LLM-powered market analysis. Supports momentum trading, mean reversion, and DCA strategies on any Binance spot pair. Use when user wants to trade on Binance, set up automated crypto trading, build a spot trading bot, or automate DCA buying. Features technical analysis, LLM sentiment evaluation, position sizing, and portfolio tracking. metadata: {"openclaw": {"requires": {"env": ["BINANCE_API_KEY", "BINANCE_SECRET_KEY", "LLM_API_KEY"]}, "primaryEnv": "BINANCE_API_KEY", "homepage": "https://github.com/srikanthbellary"}} --- # Binance Spot Trader Autonomous spot trading bot for Binance. Combines technical indicators with LLM-powered market sentiment analysis to execute trades on any Binance spot pair. ## Prerequisites - **Binance account** with API keys (spot trading enabled, withdrawal DISABLED) - **Anthropic API key** (uses Haiku ~$0.001/eval) - Python 3.10+ ## Setup ### 1. Install ```bash bash {baseDir}/scripts/setup.sh ``` ### 2. Configure Create `.env`: ``` BINANCE_API_KEY=<your-api-key> BINANCE_SECRET_KEY=<your-secret-key> LLM_API_KEY=<anthropic-api-key> PAIRS=BTCUSDT,ETHUSDT,SOLUSDT STRATEGY=momentum TRADE_SIZE_PCT=5 MAX_POSITIONS=5 ``` ### 3. Run ```bash python3 {baseDir}/scripts/trader.py ``` Or via cron: ``` */5 * * * * cd /opt/trader && python3 trader.py >> trader.log 2>&1 ``` ## Strategies ### Momentum (default) - Buys when price crosses above 20-EMA with volume spike - Sells when price crosses below 20-EMA or hits TP/SL - Best for trending markets (BTC, ETH, SOL) ### Mean Reversion - Buys when RSI < 30 (oversold) and price near Bollinger Band lower - Sells when RSI > 70 (overbought) or price near upper band - Best for range-bound markets ### DCA (Dollar Cost Average) - Buys fixed amount at regular intervals regardless of price - Configurable interval (hourly, daily, weekly) - Lowest risk strategy for long-term accumulation ### LLM-Enhanced (all strategies) - Before each trade, asks Claude Haiku for market sentiment - Evaluates: recent news, price action, volume patterns, market structure - Can veto a trade signal if sentiment is strongly against ## Trading Parameters | Parameter | Default | Description | |-----------|---------|-------------| | `PAIRS` | `BTCUSDT` | Comma-separated trading pairs | | `STRATEGY` | `momentum` | `momentum`, `mean_reversion`, or `dca` | | `TRADE_SIZE_PCT` | `5` | % of portfolio per trade | | `MAX_POSITIONS` | `5` | Max concurrent open positions | | `TAKE_PROFIT_PCT` | `5` | Take profit % | | `STOP_LOSS_PCT` | `3` | Stop loss % | | `DCA_INTERVAL` | `daily` | For DCA: `hourly`, `daily`, `weekly` | | `DCA_AMOUNT_USDT` | `50` | USDT per DCA buy | | `USE_LLM` | `true` | Enable LLM sentiment filter | ## Monitoring ```bash # Check portfolio python3 {baseDir}/scripts/portfolio.py # View trade history tail -50 trades.jsonl # Check logs tail -f trader.log ``` ## ⚠️ Security Considerations - **NEVER enable withdrawal on API keys** — trading only - **IP-restrict your API keys** on Binance - Use a sub-account with limited funds for bot trading - Start with tiny amounts ($50-100) and paper trade first - Monitor actively during first 24 hours - Set up Binance email alerts for all trades - **API keys on disk** — secure your server (SSH keys only, firewall, chmod 600) ## References - See `references/binance-api.md` for REST API docs - See `references/indicators.md` for technical analysis details

path: references/binance-api.md
content: # Binance REST API Reference

## Base URL
`https://api.binance.com`

## Authentication
- API key in header: `X-MBX-APIKEY`
- HMAC SHA256 signature of query string using secret key
- Timestamp required within 5000ms of server time

## Key Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/v3/ticker/price` | No | Current price |
| GET | `/api/v3/klines` | No | Candlestick data |
| GET | `/api/v3/account` | Yes | Account balances |
| POST | `/api/v3/order` | Yes | Place order |
| GET | `/api/v3/openOrders` | Yes | Open orders |
| DELETE | `/api/v3/order` | Yes | Cancel order |

## Order Types
- `MARKET` — immediate execution at best price
- `LIMIT` — execute at specified price or better
- `STOP_LOSS_LIMIT` — triggered stop loss

## Minimum Order
- Most pairs: $10 USDT equivalent
- Check `/api/v3/exchangeInfo` for exact filters per symbol


path: references/indicators.md
content: # Technical Indicators

## EMA (Exponential Moving Average)
- Period 20: short-term trend
- Price > EMA = bullish, Price < EMA = bearish
- Formula: EMA = Price × k + EMA_prev × (1-k), where k = 2/(period+1)

## RSI (Relative Strength Index)
- Range 0-100
- < 30 = oversold (buy signal), > 70 = overbought (sell signal)
- Period 14 default

## Bollinger Bands
- Middle = 20-period SMA
- Upper/Lower = SMA ± 2 standard deviations
- Price at lower band + low RSI = strong buy
- Price at upper band + high RSI = strong sell

## Volume Spike
- Current volume > 1.5× average 20-period volume
- Confirms momentum signals


path: scripts/portfolio.py
content: #!/usr/bin/env python3
"""Portfolio tracker for Binance Spot Trader."""
import os, json, hmac, hashlib, time
from urllib.parse import urlencode
from dotenv import load_dotenv
import httpx

load_dotenv()
[REDACTED_SECRET]"BINANCE_API_KEY"]
SECRET_KEY = os.environ["BINANCE_SECRET_KEY"]
BASE = "https://api.binance.com"

def sign(params):
    params["timestamp"] = int(time.time() * 1000)
    query = urlencode(params)
    params["signature"] = hmac.new(SECRET_KEY.encode(), query.encode(), hashlib.sha256).hexdigest()
    return params

with httpx.Client(timeout=15) as c:
    info = c.get(f"{BASE}/api/v3/account", params=sign({}), headers={"X-MBX-APIKEY": API_KEY}).json()
    total = 0
    print(f"{'Asset':>8} {'Free':>12} {'Locked':>12} {'USD Value':>12}")
    print("=" * 48)
    for b in info.get("balances", []):
        free = float(b["free"])
        locked = float(b["locked"])
        if free + locked < 0.00001: continue
        # Get price
        usd = 0
        if b["asset"] == "USDT":
            usd = free + locked
        else:
            try:
                p = c.get(f"{BASE}/api/v3/ticker/price", params={"symbol": f"{b['asset']}USDT"}).json()
                usd = (free + locked) * float(p.get("price", 0))
            except: pass
        total += usd
        print(f"{b['asset']:>8} {free:>12.4f} {locked:>12.4f} ${usd:>11.2f}")
    print("=" * 48)
    print(f"{'TOTAL':>8} {'':>12} {'':>12} ${total:>11.2f}")


path: scripts/setup.sh
content: #!/bin/bash
set -e
echo "=== Binance Spot Trader Setup ==="
pip install httpx==0.27.0 python-dotenv==1.0.1
echo "Done. Create .env with BINANCE_API_KEY, BINANCE_SECRET_KEY, and LLM_API_KEY"


path: scripts/trader.py
content: #!/usr/bin/env python3
"""Binance Spot Trader — LLM-enhanced autonomous trading bot."""
import os, sys, json, time, logging, hmac, hashlib
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urlencode
from dotenv import load_dotenv
import httpx

load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("trader")

[REDACTED_SECRET]"BINANCE_API_KEY"]
SECRET_KEY = os.environ["BINANCE_SECRET_KEY"]
LLM_API_KEY = os.environ["LLM_API_KEY"]
PAIRS = os.getenv("PAIRS", "BTCUSDT").split(",")
STRATEGY = os.getenv("STRATEGY", "momentum")
TRADE_SIZE_PCT = float(os.getenv("TRADE_SIZE_PCT", "5"))
MAX_POSITIONS = int(os.getenv("MAX_POSITIONS", "5"))
TP_PCT = float(os.getenv("TAKE_PROFIT_PCT", "5"))
SL_PCT = float(os.getenv("STOP_LOSS_PCT", "3"))
USE_LLM = os.getenv("USE_LLM", "true").lower() == "true"
DCA_AMOUNT = float(os.getenv("DCA_AMOUNT_USDT", "50"))

BASE = "https://api.binance.com"
TRADES_LOG = Path("trades.jsonl")

def sign(params: dict) -> dict:
    params["timestamp"] = int(time.time() * 1000)
    query = urlencode(params)
    params["signature"] = hmac.new(SECRET_KEY.encode(), query.encode(), hashlib.sha256).hexdigest()
    return params

def api_get(path, params=None):
    with httpx.Client(timeout=15) as c:
        if params and "signature" in params:
            r = c.get(f"{BASE}{path}", params=params, headers={"X-MBX-APIKEY": API_KEY})
        else:
            r = c.get(f"{BASE}{path}", params=params or {})
        return r.json()

def api_post(path, params):
    with httpx.Client(timeout=15) as c:
        r = c.post(f"{BASE}{path}", params=sign(params), headers={"X-MBX-APIKEY": API_KEY})
        return r.json()

def get_klines(symbol, interval="1h", limit=50):
    data = api_get("/api/v3/klines", {"symbol": symbol, "interval": interval, "limit": limit})
    return [{"t": k[0], "o": float(k[1]), "h": float(k[2]), "l": float(k[3]), "c": float(k[4]), "v": float(k[5])} for k in data]

def ema(prices, period):
    k = 2 / (period + 1)
    e = prices[0]
    for p in prices[1:]:
        e = p * k + e * (1 - k)
    return e

def rsi(prices, period=14):
    deltas = [prices[i+1] - prices[i] for i in range(len(prices)-1)]
    gains = [d if d > 0 else 0 for d in deltas[-period:]]
    losses = [-d if d < 0 else 0 for d in deltas[-period:]]
    avg_gain = sum(gains) / period
    avg_loss = sum(losses) / period
    if avg_loss == 0: return 100
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))

def bollinger(prices, period=20, std_mult=2):
    sma = sum(prices[-period:]) / period
    variance = sum((p - sma) ** 2 for p in prices[-period:]) / period
    std = variance ** 0.5
    return sma - std_mult * std, sma, sma + std_mult * std

def llm_sentiment(symbol, klines):
    if not USE_LLM: return 0.5
    prices = [k["c"] for k in klines[-10:]]
    vol = [k["v"] for k in klines[-10:]]
    prompt = f"""Rate market sentiment for {symbol} (0.0=very bearish, 1.0=very bullish):
Last 10 closes: {[round(p,2) for p in prices]}
Volume trend: {'increasing' if vol[-1] > sum(vol[:-1])/len(vol[:-1]) else 'decreasing'}
Current RSI: {rsi(prices):.0f}
Reply ONLY a number."""

    with httpx.Client(timeout=30) as c:
        resp = c.post("https://api.anthropic.com/v1/messages",
            headers={"x-api-key": LLM_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
            json={"model": "claude-3-5-haiku-20241022", "max_tokens": 20, "messages": [{"role": "user", "content": prompt}]})
        text = resp.json().get("content", [{}])[0].get("text", "0.5").strip()
        try: return float(text.split()[0].strip(".,"))
        except: return 0.5

def get_balance(asset="USDT"):
    info = api_get("/api/v3/account", sign({}))
    for b in info.get("balances", []):
        if b["asset"] == asset:
            return float(b["free"])
    return 0

def place_order(symbol, side, quantity):
    params = {"symbol": symbol, "side": side, "type": "MARKET", "quantity": f"{quantity:.6f}"}
    result = api_post("/api/v3/order", params)
    log.info(f"ORDER {side}: {symbol} qty={quantity:.6f} -> {result.get('status', 'UNKNOWN')}")
    with open(TRADES_LOG, "a") as f:
        f.write(json.dumps({"ts": datetime.now(timezone.utc).isoformat(), "symbol": symbol,
            "side": side, "qty": quantity, "result": result.get("status", "UNKNOWN"),
            "price": float(result.get("fills", [{}])[0].get("price", 0)) if result.get("fills") else 0
        }) + "\n")
    return result

def momentum_signal(klines):
    prices = [k["c"] for k in klines]
    ema20 = ema(prices, 20)
    current = prices[-1]
    avg_vol = sum(k["v"] for k in klines[-20:]) / 20
    vol_spike = klines[-1]["v"] > avg_vol * 1.5
    if current > ema20 and vol_spike: return "BUY"
    if current < ema20: return "SELL"
    return "HOLD"

def mean_reversion_signal(klines):
    prices = [k["c"] for k in klines]
    r = rsi(prices)
    lower, mid, upper = bollinger(prices)
    current = prices[-1]
    if r < 30 and current <= lower * 1.02: return "BUY"
    if r > 70 or current >= upper * 0.98: return "SELL"
    return "HOLD"

def run():
    log.info(f"Strategy: {STRATEGY} | Pairs: {PAIRS} | LLM: {USE_LLM}")
    balance = get_balance("USDT")
    log.info(f"USDT balance: ${balance:.2f}")

    if balance < 10:
        log.error("Insufficient USDT balance")
        return

    for symbol in PAIRS:
        try:
            klines = get_klines(symbol, "1h", 50)
            if not klines: continue

            if STRATEGY == "momentum":
                signal = momentum_signal(klines)
            elif STRATEGY == "mean_reversion":
                signal = mean_reversion_signal(klines)
            elif STRATEGY == "dca":
                signal = "BUY"  # DCA always buys
            else:
                signal = "HOLD"

            current_price = klines[-1]["c"]
            log.info(f"{symbol}: price=${current_price:.2f} signal={signal}")

            if signal == "BUY":
                if USE_LLM:
                    sentiment = llm_sentiment(symbol, klines)
                    log.info(f"  LLM sentiment: {sentiment:.2f}")
                    if sentiment < 0.35:
                        log.info(f"  LLM VETO — bearish sentiment")
                        continue

                if STRATEGY == "dca":
                    trade_usdt = DCA_AMOUNT
                else:
                    trade_usdt = balance * (TRADE_SIZE_PCT / 100)

                qty = trade_usdt / current_price
                if trade_usdt >= 10:  # Binance minimum
                    place_order(symbol, "BUY", qty)
                else:
                    log.info(f"  Skip: trade size ${trade_usdt:.2f} below $10 minimum")

            elif signal == "SELL":
                # Check if we hold this asset
                base_asset = symbol.replace("USDT", "")
                held = get_balance(base_asset)
                if held * current_price > 10:
                    place_order(symbol, "SELL", held)
                else:
                    log.info(f"  No {base_asset} position to sell")

        except Exception as e:
            log.error(f"{symbol} error: {e}")

    log.info("=== Cycle complete ===")

if __name__ == "__main__":
    run()


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