โ ะะฐะทะฐะด'use strict';
const crypto = require('crypto');
const axios = require('axios');
const config = require('../config');
const logger = require('../utils/logger');
const BASE = config.binance.eapiBase;
const API_KEY = config.binance.apiKey;
const API_SECRET = config.binance.apiSecret;
// โโโ Signature helper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function sign(params) {
const qs = new URLSearchParams(params).toString();
const signature = crypto.createHmac('sha256', API_SECRET).update(qs).digest('hex');
return `${qs}&signature=${signature}`;
}
function headers() {
return { 'X-MBX-APIKEY': API_KEY };
}
function ensureEnabled() {
if (!config.trading.enabled) {
throw new Error('Trading is disabled. Set TRADING_ENABLED=true in .env');
}
if (!API_KEY || !API_SECRET) {
throw new Error('Binance API credentials not configured');
}
}
// โโโ Account Info โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function getAccount() {
ensureEnabled();
const params = { timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.get(`${BASE}/eapi/v1/marginAccount?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Open Positions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function getPositions() {
ensureEnabled();
const params = { timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.get(`${BASE}/eapi/v1/position?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Place Order โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// symbol: "BTC-260424-95000-C"
// side: "BUY" | "SELL"
// type: "LIMIT" | "MARKET"
// quantity: number (contracts)
// price: number (premium, required for LIMIT)
async function placeOrder({ symbol, side, quantity, price, reduceOnly = false, timeInForce }) {
ensureEnabled();
// Binance eAPI only supports LIMIT orders for options
if (!price) throw new Error('Price required โ Binance options only support LIMIT orders');
// Validate qty: min 0.01, step 0.01
const qty = Math.round(parseFloat(quantity) * 100) / 100; // round to 2 decimals
if (qty < 0.01) throw new Error('Min quantity is 0.01');
const params = {
symbol,
side: side.toUpperCase(),
type: 'LIMIT',
quantity: String(qty),
price: String(price),
timeInForce: timeInForce || 'GTC',
timestamp: Date.now(),
recvWindow: 5000,
};
if (reduceOnly) params.reduceOnly = 'true';
logger.info(`[TRADE] Placing ${side} LIMIT ${qty} ร ${symbol} @ $${price}`);
const resp = await axios.post(`${BASE}/eapi/v1/order?${sign(params)}`, null, { headers: headers() });
logger.info(`[TRADE] Order placed: orderId=${resp.data.orderId} status=${resp.data.status}`);
return resp.data;
}
// โโโ Cancel Order โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function cancelOrder(symbol, orderId) {
ensureEnabled();
const params = { symbol, orderId: String(orderId), timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.delete(`${BASE}/eapi/v1/order?${sign(params)}`, { headers: headers() });
logger.info(`[TRADE] Order cancelled: ${orderId}`);
return resp.data;
}
// โโโ Cancel All Orders for Symbol โโโโโโโโโโโโโโโโโโโโโโโโ
async function cancelAllOrders(symbol) {
ensureEnabled();
const params = { symbol, timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.delete(`${BASE}/eapi/v1/allOpenOrders?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Open Orders โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function getOpenOrders(symbol) {
ensureEnabled();
const params = { timestamp: Date.now(), recvWindow: 5000 };
if (symbol) params.symbol = symbol;
const resp = await axios.get(`${BASE}/eapi/v1/openOrders?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Trade History โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function getTradeHistory(symbol, limit = 20) {
ensureEnabled();
const params = { symbol, limit, timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.get(`${BASE}/eapi/v1/userTrades?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Exercise History โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Returns expired/exercised options. strikeResult:
// EXTRINSIC_VALUE_EXPIRED = expired worthless (OTM)
// REALISTIC_VALUE_STRICKEN = auto-exercised (ITM at expiry)
async function getExerciseHistory({ symbol, startTime, endTime, limit = 100 } = {}) {
ensureEnabled();
const params = { timestamp: Date.now(), recvWindow: 5000, limit };
if (symbol) params.symbol = symbol;
if (startTime) params.startTime = startTime;
if (endTime) params.endTime = endTime;
const resp = await axios.get(`${BASE}/eapi/v1/exerciseHistory?${sign(params)}`, { headers: headers() });
return resp.data;
}
// โโโ Order Query โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function queryOrder(symbol, orderId) {
ensureEnabled();
const params = { symbol, orderId: String(orderId), timestamp: Date.now(), recvWindow: 5000 };
const resp = await axios.get(`${BASE}/eapi/v1/order?${sign(params)}`, { headers: headers() });
return resp.data;
}
module.exports = {
getAccount,
getPositions,
placeOrder,
cancelOrder,
cancelAllOrders,
getOpenOrders,
getTradeHistory,
getExerciseHistory,
queryOrder,
};