ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
472 lines
12 KiB
TypeScript
472 lines
12 KiB
TypeScript
/**
|
|
* Binance Client Service
|
|
*
|
|
* CCXT wrapper for Binance operations.
|
|
* Provides a unified interface for both Spot and Futures trading.
|
|
*
|
|
* @version 1.0.0
|
|
* @author Trading Platform Trading Platform
|
|
*/
|
|
|
|
import ccxt, { Ticker, OrderBook, OHLCV, Balance, Order, Trade } from 'ccxt';
|
|
import { createBinanceSpotClient, createBinanceFuturesClient, binanceConfig } from '../config';
|
|
import { logger } from '../utils/logger';
|
|
|
|
// ==========================================
|
|
// Types
|
|
// ==========================================
|
|
|
|
export interface BinanceTicker {
|
|
symbol: string;
|
|
price: number;
|
|
bid: number;
|
|
ask: number;
|
|
high24h: number;
|
|
low24h: number;
|
|
volume24h: number;
|
|
change24h: number;
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface BinanceOrderBook {
|
|
symbol: string;
|
|
bids: [number, number][];
|
|
asks: [number, number][];
|
|
spread: number;
|
|
spreadPercentage: number;
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface BinanceKline {
|
|
timestamp: number;
|
|
open: number;
|
|
high: number;
|
|
low: number;
|
|
close: number;
|
|
volume: number;
|
|
}
|
|
|
|
export interface BinanceAccountBalance {
|
|
asset: string;
|
|
free: number;
|
|
locked: number;
|
|
total: number;
|
|
}
|
|
|
|
export interface BinanceAccount {
|
|
accountType: string;
|
|
balances: BinanceAccountBalance[];
|
|
canTrade: boolean;
|
|
canWithdraw: boolean;
|
|
updateTime: number;
|
|
}
|
|
|
|
export interface BinanceOrder {
|
|
id: string;
|
|
symbol: string;
|
|
side: string;
|
|
type: string;
|
|
price: number | null;
|
|
amount: number;
|
|
filled: number;
|
|
remaining: number;
|
|
status: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
export interface CreateOrderParams {
|
|
symbol: string;
|
|
side: 'buy' | 'sell';
|
|
type: 'market' | 'limit' | 'stop_loss' | 'take_profit';
|
|
amount: number;
|
|
price?: number;
|
|
stopPrice?: number;
|
|
}
|
|
|
|
export interface OrderResult {
|
|
success: boolean;
|
|
order?: BinanceOrder;
|
|
error?: string;
|
|
}
|
|
|
|
// ==========================================
|
|
// Binance Client Class
|
|
// ==========================================
|
|
|
|
export class BinanceClient {
|
|
private spotClient: ccxt.binance;
|
|
private futuresClient: ccxt.binance;
|
|
private marketsLoaded: boolean = false;
|
|
|
|
constructor() {
|
|
this.spotClient = createBinanceSpotClient();
|
|
this.futuresClient = createBinanceFuturesClient();
|
|
}
|
|
|
|
/**
|
|
* Check if client is properly configured
|
|
*/
|
|
isConfigured(): boolean {
|
|
return binanceConfig.apiKey !== '' && binanceConfig.apiSecret !== '';
|
|
}
|
|
|
|
/**
|
|
* Test connectivity to Binance
|
|
*/
|
|
async isConnected(): Promise<boolean> {
|
|
try {
|
|
await this.spotClient.fetchTime();
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load markets if not already loaded
|
|
*/
|
|
private async ensureMarketsLoaded(): Promise<void> {
|
|
if (!this.marketsLoaded) {
|
|
await this.spotClient.loadMarkets();
|
|
this.marketsLoaded = true;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// Market Data Methods
|
|
// ==========================================
|
|
|
|
/**
|
|
* Get ticker for a symbol
|
|
*/
|
|
async getTicker(symbol: string): Promise<BinanceTicker> {
|
|
try {
|
|
await this.ensureMarketsLoaded();
|
|
const ticker: Ticker = await this.spotClient.fetchTicker(symbol);
|
|
|
|
return {
|
|
symbol: ticker.symbol,
|
|
price: ticker.last ?? 0,
|
|
bid: ticker.bid ?? 0,
|
|
ask: ticker.ask ?? 0,
|
|
high24h: ticker.high ?? 0,
|
|
low24h: ticker.low ?? 0,
|
|
volume24h: ticker.baseVolume ?? 0,
|
|
change24h: ticker.percentage ?? 0,
|
|
timestamp: ticker.timestamp ?? Date.now(),
|
|
};
|
|
} catch (error) {
|
|
logger.error(`Failed to get ticker for ${symbol}`, { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get order book for a symbol
|
|
*/
|
|
async getOrderBook(symbol: string, limit: number = 20): Promise<BinanceOrderBook> {
|
|
try {
|
|
await this.ensureMarketsLoaded();
|
|
const orderbook: OrderBook = await this.spotClient.fetchOrderBook(symbol, limit);
|
|
|
|
const topBid = orderbook.bids[0]?.[0] ?? 0;
|
|
const topAsk = orderbook.asks[0]?.[0] ?? 0;
|
|
const spread = topAsk - topBid;
|
|
const spreadPercentage = topBid > 0 ? (spread / topBid) * 100 : 0;
|
|
|
|
return {
|
|
symbol,
|
|
bids: orderbook.bids.slice(0, limit) as [number, number][],
|
|
asks: orderbook.asks.slice(0, limit) as [number, number][],
|
|
spread,
|
|
spreadPercentage,
|
|
timestamp: orderbook.timestamp ?? Date.now(),
|
|
};
|
|
} catch (error) {
|
|
logger.error(`Failed to get order book for ${symbol}`, { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get OHLCV (klines/candles) for a symbol
|
|
*/
|
|
async getKlines(
|
|
symbol: string,
|
|
interval: string = '5m',
|
|
limit: number = 100
|
|
): Promise<BinanceKline[]> {
|
|
try {
|
|
await this.ensureMarketsLoaded();
|
|
const ohlcv: OHLCV[] = await this.spotClient.fetchOHLCV(symbol, interval, undefined, limit);
|
|
|
|
return ohlcv.map((candle) => ({
|
|
timestamp: candle[0] as number,
|
|
open: candle[1] as number,
|
|
high: candle[2] as number,
|
|
low: candle[3] as number,
|
|
close: candle[4] as number,
|
|
volume: candle[5] as number,
|
|
}));
|
|
} catch (error) {
|
|
logger.error(`Failed to get klines for ${symbol}`, { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// Account Methods
|
|
// ==========================================
|
|
|
|
/**
|
|
* Get account balance
|
|
*/
|
|
async getAccount(): Promise<BinanceAccount> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
const balance: Balance = await this.spotClient.fetchBalance();
|
|
|
|
// Filter non-zero balances
|
|
const balances: BinanceAccountBalance[] = Object.entries(balance.total)
|
|
.filter(([_, amount]) => (amount as number) > 0)
|
|
.map(([asset, total]) => ({
|
|
asset,
|
|
free: (balance.free[asset] as number) ?? 0,
|
|
locked: (balance.used[asset] as number) ?? 0,
|
|
total: total as number,
|
|
}));
|
|
|
|
return {
|
|
accountType: 'SPOT',
|
|
balances,
|
|
canTrade: true,
|
|
canWithdraw: true,
|
|
updateTime: Date.now(),
|
|
};
|
|
} catch (error) {
|
|
logger.error('Failed to get account info', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get open orders
|
|
*/
|
|
async getOpenOrders(symbol?: string): Promise<BinanceOrder[]> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
const orders: Order[] = await this.spotClient.fetchOpenOrders(symbol);
|
|
|
|
return orders.map((order) => ({
|
|
id: order.id,
|
|
symbol: order.symbol,
|
|
side: order.side,
|
|
type: order.type,
|
|
price: order.price,
|
|
amount: order.amount,
|
|
filled: order.filled,
|
|
remaining: order.remaining,
|
|
status: order.status,
|
|
createdAt: order.timestamp ?? Date.now(),
|
|
}));
|
|
} catch (error) {
|
|
logger.error('Failed to get open orders', { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get trade history
|
|
*/
|
|
async getTradeHistory(symbol: string, limit: number = 50): Promise<Trade[]> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
return await this.spotClient.fetchMyTrades(symbol, undefined, limit);
|
|
} catch (error) {
|
|
logger.error(`Failed to get trade history for ${symbol}`, { error });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// Order Methods
|
|
// ==========================================
|
|
|
|
/**
|
|
* Create a new order
|
|
*/
|
|
async createOrder(params: CreateOrderParams): Promise<OrderResult> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
await this.ensureMarketsLoaded();
|
|
|
|
let order: Order;
|
|
|
|
switch (params.type) {
|
|
case 'market':
|
|
order = await this.spotClient.createMarketOrder(
|
|
params.symbol,
|
|
params.side,
|
|
params.amount
|
|
);
|
|
break;
|
|
|
|
case 'limit':
|
|
if (!params.price) {
|
|
return { success: false, error: 'Price is required for limit orders' };
|
|
}
|
|
order = await this.spotClient.createLimitOrder(
|
|
params.symbol,
|
|
params.side,
|
|
params.amount,
|
|
params.price
|
|
);
|
|
break;
|
|
|
|
case 'stop_loss':
|
|
if (!params.stopPrice) {
|
|
return { success: false, error: 'Stop price is required for stop loss orders' };
|
|
}
|
|
order = await this.spotClient.createOrder(
|
|
params.symbol,
|
|
'stop_loss',
|
|
params.side,
|
|
params.amount,
|
|
undefined,
|
|
{ stopPrice: params.stopPrice }
|
|
);
|
|
break;
|
|
|
|
case 'take_profit':
|
|
if (!params.stopPrice) {
|
|
return { success: false, error: 'Stop price is required for take profit orders' };
|
|
}
|
|
order = await this.spotClient.createOrder(
|
|
params.symbol,
|
|
'take_profit',
|
|
params.side,
|
|
params.amount,
|
|
undefined,
|
|
{ stopPrice: params.stopPrice }
|
|
);
|
|
break;
|
|
|
|
default:
|
|
return { success: false, error: `Unsupported order type: ${params.type}` };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
order: {
|
|
id: order.id,
|
|
symbol: order.symbol,
|
|
side: order.side,
|
|
type: order.type,
|
|
price: order.price ?? order.average ?? null,
|
|
amount: order.amount,
|
|
filled: order.filled,
|
|
remaining: order.remaining,
|
|
status: order.status,
|
|
createdAt: order.timestamp ?? Date.now(),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
logger.error('Failed to create order', { error, params });
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel an order
|
|
*/
|
|
async cancelOrder(orderId: string, symbol: string): Promise<OrderResult> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
const result = await this.spotClient.cancelOrder(orderId, symbol);
|
|
|
|
return {
|
|
success: true,
|
|
order: {
|
|
id: result.id,
|
|
symbol: result.symbol,
|
|
side: result.side,
|
|
type: result.type,
|
|
price: result.price,
|
|
amount: result.amount,
|
|
filled: result.filled,
|
|
remaining: result.remaining,
|
|
status: 'CANCELLED',
|
|
createdAt: result.timestamp ?? Date.now(),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
logger.error('Failed to cancel order', { error, orderId, symbol });
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel all orders for a symbol
|
|
*/
|
|
async cancelAllOrders(symbol: string): Promise<{ success: boolean; cancelledCount: number; error?: string }> {
|
|
try {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Binance API keys not configured');
|
|
}
|
|
|
|
const result = await this.spotClient.cancelAllOrders(symbol);
|
|
|
|
return {
|
|
success: true,
|
|
cancelledCount: Array.isArray(result) ? result.length : 0,
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
logger.error('Failed to cancel all orders', { error, symbol });
|
|
return { success: false, cancelledCount: 0, error: message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current price for a symbol (helper method)
|
|
*/
|
|
async getCurrentPrice(symbol: string): Promise<number> {
|
|
const ticker = await this.getTicker(symbol);
|
|
return ticker.price;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// Singleton Instance
|
|
// ==========================================
|
|
|
|
let clientInstance: BinanceClient | null = null;
|
|
|
|
export function getBinanceClient(): BinanceClient {
|
|
if (!clientInstance) {
|
|
clientInstance = new BinanceClient();
|
|
}
|
|
return clientInstance;
|
|
}
|
|
|
|
export function resetBinanceClient(): void {
|
|
clientInstance = null;
|
|
}
|