โ† ะะฐะทะฐะด
'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, };