luohoa97's picture
Deploy BitNet-Transformer Trainer
d5b7ee9 verified
"""Binance adapter stub β€” crypto trading via Binance API.
This is a stub implementation. To enable:
1. Install ccxt: `uv add ccxt`
2. Add your Binance API keys to config
3. Implement the TODO sections below
"""
from __future__ import annotations
import logging
from datetime import datetime, timedelta, timezone
import pandas as pd
from trading_cli.execution.adapters.base import (
AccountInfo,
MarketClock,
OrderResult,
Position,
TradingAdapter,
)
from trading_cli.execution.adapters.registry import register_adapter
logger = logging.getLogger(__name__)
@register_adapter
class BinanceAdapter(TradingAdapter):
"""Binance adapter for cryptocurrency trading.
Requires: ccxt library (`uv add ccxt`)
Config keys:
binance_api_key: Your Binance API key
binance_api_secret: Your Binance API secret
binance_sandbox: Use sandbox/testnet (default: False)
"""
def __init__(self, config: dict) -> None:
self._config = config
self._api_key = config.get("binance_api_key", "")
self._api_secret = config.get("binance_api_secret", "")
self._sandbox = config.get("binance_sandbox", False)
self._demo = not (self._api_key and self._api_secret)
if self._demo:
logger.info("BinanceAdapter: no API keys found, stub mode only")
return
try:
import ccxt
self._exchange = ccxt.binance({
"apiKey": self._api_key,
"secret": self._api_secret,
"enableRateLimit": True,
})
if self._sandbox:
self._exchange.set_sandbox_mode(True)
logger.info("BinanceAdapter connected (sandbox=%s)", self._sandbox)
except ImportError:
logger.warning("ccxt not installed. Run: uv add ccxt")
self._demo = True
self._exchange = None
except Exception as exc:
logger.error("Failed to connect to Binance: %s", exc)
self._demo = True
self._exchange = None
@property
def adapter_id(self) -> str:
return "binance"
@property
def supports_paper_trading(self) -> bool:
return self._sandbox # Binance testnet
@property
def is_demo_mode(self) -> bool:
return self._demo
# ── Account & Positions ───────────────────────────────────────────────────
def get_account(self) -> AccountInfo:
if self._demo or not self._exchange:
return AccountInfo(
equity=100000.0,
cash=100000.0,
buying_power=100000.0,
portfolio_value=100000.0,
)
# TODO: Implement real account fetch using self._exchange.fetch_balance()
balance = self._exchange.fetch_balance()
# Extract USDT balance as cash equivalent
cash = float(balance.get("USDT", {}).get("free", 0))
return AccountInfo(
equity=cash, # Simplified
cash=cash,
buying_power=cash,
portfolio_value=cash,
)
def get_positions(self) -> list[Position]:
if self._demo or not self._exchange:
return []
# TODO: Implement real position fetch
# For crypto, positions are balances with non-zero amounts
positions = []
balance = self._exchange.fetch_balance()
for currency, amount_info in balance.items():
if isinstance(amount_info, dict) and amount_info.get("total", 0) > 0:
if currency in ("free", "used", "total", "info"):
continue
total = amount_info.get("total", 0)
positions.append(
Position(
symbol=f"{currency}/USDT",
qty=total,
avg_entry_price=0.0, # TODO: Track entry prices
current_price=0.0, # TODO: Fetch current price
unrealized_pl=0.0,
unrealized_plpc=0.0,
market_value=0.0,
side="long",
)
)
return positions
# ── Orders ───────────────────────────────────────────────────────────────
def submit_market_order(self, symbol: str, qty: int, side: str) -> OrderResult:
if self._demo or not self._exchange:
return OrderResult(
order_id=f"BINANCE-DEMO-{datetime.now().timestamp()}",
symbol=symbol,
action=side,
qty=qty,
status="filled",
filled_price=0.0,
)
# TODO: Implement real order submission
try:
# Convert to ccxt format: 'BTC/USDT'
order = self._exchange.create_market_order(symbol, side.lower(), qty)
return OrderResult(
order_id=order.get("id", "unknown"),
symbol=symbol,
action=side,
qty=qty,
status=order.get("status", "filled"),
filled_price=float(order.get("average") or order.get("price") or 0),
)
except Exception as exc:
logger.error("Binance order failed for %s %s %d: %s", side, symbol, qty, exc)
raise
def close_position(self, symbol: str) -> OrderResult | None:
if self._demo or not self._exchange:
return None
# TODO: Implement position close
# Need to look up current position qty and sell all
return None
# ── Market Data ───────────────────────────────────────────────────────────
def fetch_ohlcv(self, symbol: str, days: int = 90) -> pd.DataFrame:
if self._demo or not self._exchange:
return pd.DataFrame()
try:
# Binance uses 'BTC/USDT' format
ohlcv = self._exchange.fetch_ohlcv(symbol, timeframe="1d", limit=days)
df = pd.DataFrame(
ohlcv,
columns=["timestamp", "Open", "High", "Low", "Close", "Volume"],
)
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df.set_index("timestamp", inplace=True)
return df
except Exception as exc:
logger.warning("Binance OHLCV fetch failed for %s: %s", symbol, exc)
return pd.DataFrame()
def get_latest_quote(self, symbol: str) -> float | None:
if self._demo or not self._exchange:
return None
try:
ticker = self._exchange.fetch_ticker(symbol)
return float(ticker.get("last") or 0)
except Exception as exc:
logger.warning("Binance quote failed for %s: %s", symbol, exc)
return None
# ── Market Info ───────────────────────────────────────────────────────────
def get_market_clock(self) -> MarketClock:
# Crypto markets are 24/7
return MarketClock(
is_open=True,
next_open="24/7",
next_close="24/7",
)
# ── News ──────────────────────────────────────────────────────────────────
def fetch_news(self, symbol: str, max_articles: int = 50,
days_ago: int = 0) -> list[tuple[str, float]]:
# Binance doesn't provide news via API
return []