From 981f5cd1ac37c4026d44627189c5df58eb587098 Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:01:46 +0100 Subject: [PATCH 1/2] feat(client): add ocoOrder and example --- examples/oco-order.ts | 101 ++++++++++++++++++++++++++++++++++++++++ src/node-binance-api.ts | 77 +++++++++++++++++++++--------- src/types.ts | 1 + 3 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 examples/oco-order.ts diff --git a/examples/oco-order.ts b/examples/oco-order.ts new file mode 100644 index 00000000..db356c5a --- /dev/null +++ b/examples/oco-order.ts @@ -0,0 +1,101 @@ +import Binance from "../src/node-binance-api.js" + + +async function main() { + + const binance = new Binance({ + APIKEY: '', + APISECRET: '', + verbose: true + }); + + const symbol = 'LTCUSDT'; + const side = 'SELL'; + const quantity = 0.1; + + const params = { + // below order + belowType: 'STOP_LOSS_LIMIT', + belowPrice: 51, + belowStopPrice: 50, + belowTimeInForce: 'GTC', + + + // above order + aboveType: 'TAKE_PROFIT_LIMIT', + aboveStopPrice: 160, + abovePrice: 120, + aboveTimeInForce: 'GTC', + } + + const oco1 = await binance.ocoOrder(side, symbol, quantity, params); + console.log('oco1', oco1); + +} + +main() + + +// RESPONSE + +// oco1 { +// orderListId: 218029, +// contingencyType: 'OCO', +// listStatusType: 'EXEC_STARTED', +// listOrderStatus: 'EXECUTING', +// listClientOrderId: 'x-B3AUXNYV9e56b68a11d646f4b8da07', +// transactionTime: 1758726001738, +// symbol: 'LTCUSDT', +// orders: [ +// { +// symbol: 'LTCUSDT', +// orderId: 21409867, +// clientOrderId: 'MVM96szzkULu3dD7eN8xrZ' +// }, +// { +// symbol: 'LTCUSDT', +// orderId: 21409868, +// clientOrderId: 'yTaqP6Txvp6mwF7Oo7RWnb' +// } +// ], +// orderReports: [ +// { +// symbol: 'LTCUSDT', +// orderId: 21409867, +// orderListId: 218029, +// clientOrderId: 'MVM96szzkULu3dD7eN8xrZ', +// transactTime: 1758726001738, +// price: '51.00000000', +// origQty: '0.10000000', +// executedQty: '0.00000000', +// origQuoteOrderQty: '0.00000000', +// cummulativeQuoteQty: '0.00000000', +// status: 'NEW', +// timeInForce: 'GTC', +// type: 'STOP_LOSS_LIMIT', +// side: 'SELL', +// stopPrice: '50.00000000', +// workingTime: -1, +// selfTradePreventionMode: 'EXPIRE_MAKER' +// }, +// { +// symbol: 'LTCUSDT', +// orderId: 21409868, +// orderListId: 218029, +// clientOrderId: 'yTaqP6Txvp6mwF7Oo7RWnb', +// transactTime: 1758726001738, +// price: '120.00000000', +// origQty: '0.10000000', +// executedQty: '0.00000000', +// origQuoteOrderQty: '0.00000000', +// cummulativeQuoteQty: '0.00000000', +// status: 'NEW', +// timeInForce: 'GTC', +// type: 'TAKE_PROFIT_LIMIT', +// side: 'SELL', +// stopPrice: '160.00000000', +// workingTime: -1, +// selfTradePreventionMode: 'EXPIRE_MAKER' +// } +// ] +// } \ No newline at end of file diff --git a/src/node-binance-api.ts b/src/node-binance-api.ts index 1558e6ec..385b03f2 100644 --- a/src/node-binance-api.ts +++ b/src/node-binance-api.ts @@ -785,7 +785,7 @@ export default class Binance { * @see https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade * @see https://developers.binance.com/docs/binance-spot-api-docs/rest-api/public-api-endpoints#test-new-order-trade * @see https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-list---oco-trade - * @param {OrderType} type - LIMIT, MARKET, STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT, LIMIT_MAKER + * @param {OrderType} type - LIMIT, MARKET, STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT, LIMIT_MAKER, OCO * @param {OrderSide} side - BUY or SELL * @param {string} symbol - The symbol to buy or sell * @param {string} quantity - The quantity to buy or sell @@ -797,7 +797,8 @@ export default class Binance { * @return {undefined} */ async order(type: OrderType, side: OrderSide, symbol: string, quantity: number, price?: number, params: Dict = {}): Promise { - let endpoint = params.type === 'OCO' ? 'v3/orderList/oco' : 'v3/order'; + const isOCO = type === 'OCO' || params.type === 'OCO'; + let endpoint = isOCO ? 'v3/orderList/oco' : 'v3/order'; if (params.test) { delete params.test; endpoint += '/test'; @@ -805,44 +806,41 @@ export default class Binance { const request = { symbol: symbol, side: side, - type: type + // type: type } as Dict; + if (!isOCO) request.type = type; if (params.quoteOrderQty && params.quoteOrderQty > 0) request.quoteOrderQty = params.quoteOrderQty; else request.quantity = quantity; - if (request.type.includes('LIMIT')) { + if (!isOCO && request.type.includes('LIMIT')) { request.price = price; if (request.type !== 'LIMIT_MAKER') { request.timeInForce = 'GTC'; } } - if (request.type == 'MARKET' && typeof params.quoteOrderQty !== 'undefined') { + if (!isOCO && request.type == 'MARKET' && typeof params.quoteOrderQty !== 'undefined') { request.quoteOrderQty = params.quoteOrderQty; delete request.quantity; } - if (request.type === 'OCO') { - request.price = price; - request.stopLimitPrice = params.stopLimitPrice; - request.stopLimitTimeInForce = 'GTC'; - delete request.type; - // if (typeof params.listClientOrderId !== 'undefined') opt.listClientOrderId = params.listClientOrderId; - // if (typeof params.limitClientOrderId !== 'undefined') opt.limitClientOrderId = params.limitClientOrderId; - // if (typeof params.stopClientOrderId !== 'undefined') opt.stopClientOrderId = params.stopClientOrderId; - } // if (typeof params.timeInForce !== 'undefined') opt.timeInForce = params.timeInForce; // if (typeof params.newOrderRespType !== 'undefined') opt.newOrderRespType = params.newOrderRespType; - if (!params.newClientOrderId) { - request.newClientOrderId = this.SPOT_PREFIX + this.uuid22(); + if (!params.newClientOrderId && !params.listClientOrderId) { + const id = this.SPOT_PREFIX + this.uuid22(); + if (!isOCO) { + request.newClientOrderId = id; + } else { + request.listClientOrderId = id; + } } - const allowedTypesForStopAndTrailing = ['STOP_LOSS', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT']; + const allowedTypesForStopAndTrailing = ['STOP_LOSS', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT', 'TAKE_PROFIT_LIMIT', 'OCO']; if (params.trailingDelta) { request.trailingDelta = params.trailingDelta; - if (!allowedTypesForStopAndTrailing.includes(request.type)) { - throw Error('trailingDelta: Must set "type" to one of the following: STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT'); + if (!isOCO && !allowedTypesForStopAndTrailing.includes(request.type)) { + throw Error('trailingDelta: Must set "type" to one of the following: STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT, OCO'); } } @@ -856,8 +854,8 @@ export default class Binance { // if (typeof params.icebergQty !== 'undefined') request.icebergQty = params.icebergQty; if (params.stopPrice) { request.stopPrice = params.stopPrice; - if (!allowedTypesForStopAndTrailing.includes(request.type)) { - throw Error('stopPrice: Must set "type" to one of the following: STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT'); + if (!isOCO && !allowedTypesForStopAndTrailing.includes(request.type)) { + throw Error('stopPrice: Must set "type" to one of the following: STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT, OCO'); } } const response = await this.privateSpotRequest(endpoint, this.extend(request, params), 'POST'); @@ -875,6 +873,43 @@ export default class Binance { return response; } + /** + * Create a OCO spot order + * @see https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-list---oco-trade + * @param {OrderSide} side - BUY or SELL + * @param {string} symbol - The symbol to buy or sell + * @param {string} quantity - The quantity to buy or sell + * @param {string} price - The price per unit to transact each unit at + * @param {object} params - additional order settings + * @param {string} params.aboveType - The type of the above order + * @param {string} params.belowType - The type of the below order + * @param {string} params.abovePrice - The price of the above order + * @param {string} params.aboveStopPrice - The stop price of the above order + * @param {string} params.aboveTrailingDelta - The trailing delta of the above order + * @param {string} params.aboveTimeInForce - The time in force of the above order + * @param {string} params.belowPrice - The price of the below order + * @param {string} params.belowStopPrice - The stop price of the below order + * @param {string} params.belowTrailingDelta - The trailing delta of the below order + * @param {string} params.belowTimeInForce - The time in force of the below order + * @return {undefined} + */ + async ocoOrder(side: OrderSide, symbol: string, quantity: number, params: Dict = {}): Promise { + const request = { + symbol: symbol, + side: side, + quantity: quantity, + } as Dict; + + if (!params.listClientOrderId) { + const id = this.SPOT_PREFIX + this.uuid22(); + request.listClientOrderId = id; + } + + const endpoint = 'v3/orderList/oco'; + const response = await this.privateSpotRequest(endpoint, this.extend(request, params), 'POST'); + return response; + } + /** * Creates a buy order * @param {string} symbol - the symbol to buy diff --git a/src/types.ts b/src/types.ts index 77bd5e3f..478da125 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,6 +30,7 @@ export type OrderType = | 'TAKE_PROFIT_MARKET' | 'LIMIT_MAKER' | 'TRAILING_STOP_MARKET' + | 'OCO' export type OrderSide = 'BUY' | 'SELL' From 769492a44db9e69cdb6bb9ffb12c6273548944aa Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:07:16 +0100 Subject: [PATCH 2/2] type return type --- examples/oco-order.ts | 4 +++- src/node-binance-api.ts | 6 +++--- src/types.ts | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/examples/oco-order.ts b/examples/oco-order.ts index db356c5a..14f48640 100644 --- a/examples/oco-order.ts +++ b/examples/oco-order.ts @@ -29,7 +29,9 @@ async function main() { } const oco1 = await binance.ocoOrder(side, symbol, quantity, params); - console.log('oco1', oco1); + console.log('oco1', oco1.orderListId); + console.log(oco1.orderReports[0].orderId) + console.log(oco1.orderReports[1].orderId) } diff --git a/src/node-binance-api.ts b/src/node-binance-api.ts index 385b03f2..59deef0c 100644 --- a/src/node-binance-api.ts +++ b/src/node-binance-api.ts @@ -16,7 +16,7 @@ import nodeFetch from 'node-fetch'; import zip from 'lodash.zipobject'; import stringHash from 'string-hash'; // eslint-disable-next-line -import { Interval, PositionRisk, Order, FuturesOrder, PositionSide, WorkingType, OrderType, OrderStatus, TimeInForce, Callback, IConstructorArgs, OrderSide, FundingRate, CancelOrder, AggregatedTrade, Trade, MyTrade, WithdrawHistoryResponse, DepositHistoryResponse, DepositAddress, WithdrawResponse, Candle, FuturesCancelAllOpenOrder, OrderBook, Ticker, FuturesUserTrade, Account, FuturesAccountInfo, FuturesBalance, QueryOrder, HttpMethod, BookTicker, DailyStats, PremiumIndex, OpenInterest, IWebsocketsMethods, SymbolConfig } from './types.js'; +import { Interval, PositionRisk, Order, FuturesOrder, PositionSide, WorkingType, OrderType, OrderStatus, TimeInForce, Callback, IConstructorArgs, OrderSide, FundingRate, CancelOrder, AggregatedTrade, Trade, MyTrade, WithdrawHistoryResponse, DepositHistoryResponse, DepositAddress, WithdrawResponse, Candle, FuturesCancelAllOpenOrder, OrderBook, Ticker, FuturesUserTrade, Account, FuturesAccountInfo, FuturesBalance, QueryOrder, HttpMethod, BookTicker, DailyStats, PremiumIndex, OpenInterest, IWebsocketsMethods, SymbolConfig, OCOOrder } from './types.js'; // export { Interval, PositionRisk, Order, FuturesOrder, PositionSide, WorkingType, OrderType, OrderStatus, TimeInForce, Callback, IConstructorArgs, OrderSide, FundingRate, CancelOrder, AggregatedTrade, Trade, MyTrade, WithdrawHistoryResponse, DepositHistoryResponse, DepositAddress, WithdrawResponse, Candle, FuturesCancelAllOpenOrder, OrderBook, Ticker, FuturesUserTrade, FuturesAccountInfo, FuturesBalance, QueryOrder } from './types'; export interface Dictionary { @@ -874,7 +874,7 @@ export default class Binance { } /** - * Create a OCO spot order + * Create an OCO spot order * @see https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-list---oco-trade * @param {OrderSide} side - BUY or SELL * @param {string} symbol - The symbol to buy or sell @@ -893,7 +893,7 @@ export default class Binance { * @param {string} params.belowTimeInForce - The time in force of the below order * @return {undefined} */ - async ocoOrder(side: OrderSide, symbol: string, quantity: number, params: Dict = {}): Promise { + async ocoOrder(side: OrderSide, symbol: string, quantity: number, params: Dict = {}): Promise { const request = { symbol: symbol, side: side, diff --git a/src/types.ts b/src/types.ts index 478da125..d858db30 100644 --- a/src/types.ts +++ b/src/types.ts @@ -602,4 +602,42 @@ export interface SymbolConfig { isAutoAddMargin: boolean leverage: number maxNotionalValue: string -} \ No newline at end of file +} + +/** + * The response structure for an OCO order, based on Binance API documentation. + * See: https://binance-docs.github.io/apidocs/spot/en/#new-oco-trade + */ +export interface OCOOrder { + orderListId: number; + contingencyType: string; + listStatusType: string; + listOrderStatus: string; + listClientOrderId: string; + transactionTime: number; + symbol: string; + orders: Array<{ + symbol: string; + orderId: number; + clientOrderId: string; + }>; + orderReports: Array<{ + symbol: string; + orderId: number; + orderListId: number; + clientOrderId: string; + transactTime: number; + price: string; + origQty: string; + executedQty: string; + cummulativeQuoteQty: string; + status: string; + timeInForce: string; + type: string; + side: string; + stopPrice?: string; + icebergQty?: string; + workingTime?: number; + selfTradePreventionMode?: string; + }>; + } \ No newline at end of file