MCP Expert Server
- src
- services
import { Spot } from '@binance/connector';
import { BinanceClientError, ApiKeyError, OrderValidationError, InsufficientMarginError, InvalidPositionModeError } from '../types/errors.js';
import { getApiKeys } from './keystore.js';
import {
FuturesOrder,
FuturesPosition,
FuturesAccountInformation,
LeverageSettings,
FundingRate,
PositionSide,
MarginType,
WorkingType
} from '../types/futures.js';
// Define futures client type with raw request methods
interface FuturesClient extends Spot {
request(method: string, path: string, params?: any): Promise<{ data: any }>;
signRequest(method: string, path: string, params?: any): Promise<{ data: any }>;
}
let futuresClient: FuturesClient | null = null;
const FUTURES_BASE_URL = 'https://fapi.binance.com';
export async function initializeFuturesClient(): Promise<boolean> {
try {
const keys = await getApiKeys();
if (!keys) {
throw new ApiKeyError('Futures API keys not found');
}
const { apiKey, apiSecret } = keys;
// Initialize client for USDⓈ-M Futures
// Initialize client for futures trading
futuresClient = new Spot(apiKey, apiSecret) as FuturesClient;
return true;
} catch (error) {
if (error instanceof ApiKeyError) {
throw error;
}
throw new BinanceClientError(`Failed to initialize futures client: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export async function createFuturesOrder(order: FuturesOrder): Promise<any> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
const params: Record<string, any> = {
symbol: order.symbol,
side: order.side,
type: order.type,
quantity: order.quantity,
};
if (order.positionSide) params.positionSide = order.positionSide;
if (order.price) params.price = order.price;
if (order.stopPrice) params.stopPrice = order.stopPrice;
if (order.reduceOnly !== undefined) params.reduceOnly = order.reduceOnly;
if (order.workingType) params.workingType = order.workingType;
if (order.timeInForce) params.timeInForce = order.timeInForce;
if (order.activationPrice) params.activationPrice = order.activationPrice;
if (order.callbackRate) params.callbackRate = order.callbackRate;
if (order.closePosition !== undefined) params.closePosition = order.closePosition;
if (order.priceProtect !== undefined) params.priceProtect = order.priceProtect;
// Validate required parameters based on order type
if (['LIMIT', 'STOP', 'TAKE_PROFIT'].includes(order.type) && !order.price) {
throw new OrderValidationError(`Price is required for ${order.type} orders`);
}
if (['STOP', 'STOP_MARKET', 'TAKE_PROFIT', 'TAKE_PROFIT_MARKET'].includes(order.type) && !order.stopPrice) {
throw new OrderValidationError(`Stop price is required for ${order.type} orders`);
}
if (order.type === 'TRAILING_STOP_MARKET' && !order.callbackRate) {
throw new OrderValidationError('Callback rate is required for TRAILING_STOP_MARKET orders');
}
const response = await futuresClient.signRequest('POST', `${FUTURES_BASE_URL}/fapi/v1/order`, params);
return response.data;
} catch (error) {
if (error instanceof OrderValidationError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (errorMessage.includes('insufficient margin')) {
throw new InsufficientMarginError(`Insufficient margin to create futures order: ${errorMessage}`);
}
if (errorMessage.includes('invalid position mode')) {
throw new InvalidPositionModeError(`Invalid position mode for futures order: ${errorMessage}`);
}
throw new BinanceClientError(`Failed to create futures order: ${errorMessage}`);
}
}
export async function getFuturesAccountInformation(): Promise<FuturesAccountInformation> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
const response = await futuresClient.signRequest('GET', `${FUTURES_BASE_URL}/fapi/v1/account`);
return response.data;
} catch (error) {
throw new BinanceClientError(`Failed to get futures account information: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export async function getFuturesPositions(): Promise<FuturesPosition[]> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
const response = await futuresClient.signRequest('GET', `${FUTURES_BASE_URL}/fapi/v1/positionRisk`);
return response.data;
} catch (error) {
throw new BinanceClientError(`Failed to get futures positions: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export async function setFuturesLeverage(settings: LeverageSettings): Promise<boolean> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
await futuresClient.signRequest('POST', `${FUTURES_BASE_URL}/fapi/v1/leverage`, {
symbol: settings.symbol,
leverage: settings.leverage
});
return true;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (errorMessage.includes('insufficient margin')) {
throw new InsufficientMarginError(`Insufficient margin to change leverage: ${errorMessage}`);
}
if (errorMessage.includes('invalid position mode')) {
throw new InvalidPositionModeError(`Invalid position mode when changing leverage: ${errorMessage}`);
}
throw new BinanceClientError(`Failed to set futures leverage: ${errorMessage}`);
}
}
export async function getFundingRate(symbol: string): Promise<FundingRate> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
const response = await futuresClient.request('GET', `${FUTURES_BASE_URL}/fapi/v1/fundingRate`, { symbol });
return response.data[0]; // Return the latest funding rate
} catch (error) {
throw new BinanceClientError(`Failed to get funding rate: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export async function cancelFuturesOrder(symbol: string, orderId: number): Promise<void> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
await futuresClient.signRequest('DELETE', `${FUTURES_BASE_URL}/fapi/v1/order`, {
symbol,
orderId
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (errorMessage.includes('invalid position mode')) {
throw new InvalidPositionModeError(`Invalid position mode when canceling futures order: ${errorMessage}`);
}
throw new BinanceClientError(`Failed to cancel futures order: ${errorMessage}`);
}
}
export async function getFuturesOpenOrders(symbol?: string): Promise<FuturesOrder[]> {
if (!futuresClient) {
throw new BinanceClientError('Futures client not initialized');
}
try {
const params = symbol ? { symbol } : {};
const response = await futuresClient.signRequest('GET', `${FUTURES_BASE_URL}/fapi/v1/openOrders`, params);
return response.data;
} catch (error) {
throw new BinanceClientError(`Failed to get open futures orders: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}