Skip to content

Commit

Permalink
Merge pull request ccxt#17936 from Dan-krm/binanceEditOrderFutures
Browse files Browse the repository at this point in the history
Binance: editOrder, add swap and future support
  • Loading branch information
kroitor authored May 19, 2023
2 parents f968ab6 + e5cd442 commit a128e70
Showing 1 changed file with 240 additions and 96 deletions.
336 changes: 240 additions & 96 deletions ts/src/binance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3621,10 +3621,10 @@ export default class binance extends Exchange {
return this.parseTrades (response, market, since, limit);
}

async editOrder (id: string, symbol, type, side, amount, price = undefined, params = {}) {
async editSpotOrder (id: string, symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name binance#editOrder
* @name binance#editSpotOrder
* @description edit a trade order
* @see https://binance-docs.github.io/apidocs/spot/en/#cancel-an-existing-order-and-send-a-new-order-trade
* @param {string} id cancel order id
Expand All @@ -3639,118 +3639,136 @@ export default class binance extends Exchange {
await this.loadMarkets ();
const market = this.market (symbol);
if (!market['spot']) {
throw new NotSupported (this.id + ' editOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted');
throw new NotSupported (this.id + ' editSpotOrder() does not support ' + market['type'] + ' orders');
}
const request = {
'symbol': market['id'],
'side': side.toUpperCase (),
'cancelReplaceMode': 'STOP_ON_FAILURE',
// STOP_ON_FAILURE - If the cancel request fails, the new order placement will not be attempted.
// ALLOW_FAILURE - new order placement will be attempted even if cancel request fails.
};
const cancelId = this.safeString2 (params, 'cancelNewClientOrderId', 'cancelOrigClientOrderId');
if (cancelId === undefined) {
request['cancelOrderId'] = id; // user can provide either cancelOrderId, cancelOrigClientOrderId or cancelOrigClientOrderId
}
const clientOrderId = this.safeString2 (params, 'newClientOrderId', 'clientOrderId');
const initialUppercaseType = type.toUpperCase ();
let uppercaseType = initialUppercaseType;
const postOnly = this.isPostOnly (initialUppercaseType === 'MARKET', initialUppercaseType === 'LIMIT_MAKER', params);
if (postOnly) {
uppercaseType = 'LIMIT_MAKER';
}
request['type'] = uppercaseType;
const stopPrice = this.safeNumber (params, 'stopPrice');
if (stopPrice !== undefined) {
if (uppercaseType === 'MARKET') {
uppercaseType = 'STOP_LOSS';
} else if (uppercaseType === 'LIMIT') {
uppercaseType = 'STOP_LOSS_LIMIT';
const clientOrderId = this.safeStringN (params, [ 'newClientOrderId', 'clientOrderId', 'origClientOrderId' ]);
let response = undefined;
if (market['spot']) {
const initialUppercaseType = type.toUpperCase ();
let uppercaseType = initialUppercaseType;
const postOnly = this.isPostOnly (initialUppercaseType === 'MARKET', initialUppercaseType === 'LIMIT_MAKER', params);
if (postOnly) {
uppercaseType = 'LIMIT_MAKER';
}
}
const validOrderTypes = this.safeValue (market['info'], 'orderTypes');
if (!this.inArray (uppercaseType, validOrderTypes)) {
if (initialUppercaseType !== uppercaseType) {
throw new InvalidOrder (this.id + ' stopPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders');
} else {
throw new InvalidOrder (this.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market');
request['type'] = uppercaseType;
const stopPrice = this.safeNumber (params, 'stopPrice');
if (stopPrice !== undefined) {
if (uppercaseType === 'MARKET') {
uppercaseType = 'STOP_LOSS';
} else if (uppercaseType === 'LIMIT') {
uppercaseType = 'STOP_LOSS_LIMIT';
}
}
}
if (clientOrderId === undefined) {
const broker = this.safeValue (this.options, 'broker');
if (broker !== undefined) {
const brokerId = this.safeString (broker, 'spot');
if (brokerId !== undefined) {
request['newClientOrderId'] = brokerId + this.uuid22 ();
const validOrderTypes = this.safeValue (market['info'], 'orderTypes');
if (!this.inArray (uppercaseType, validOrderTypes)) {
if (initialUppercaseType !== uppercaseType) {
throw new InvalidOrder (this.id + ' stopPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders');
} else {
throw new InvalidOrder (this.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market');
}
}
} else {
request['newClientOrderId'] = clientOrderId;
}
request['newOrderRespType'] = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
let timeInForceIsRequired = false;
let priceIsRequired = false;
let stopPriceIsRequired = false;
let quantityIsRequired = false;
if (uppercaseType === 'MARKET') {
const quoteOrderQty = this.safeValue (this.options, 'quoteOrderQty', true);
if (quoteOrderQty) {
const quoteOrderQtyNew = this.safeValue2 (params, 'quoteOrderQty', 'cost');
const precision = market['precision']['price'];
if (quoteOrderQtyNew !== undefined) {
request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQtyNew, TRUNCATE, precision, this.precisionMode);
} else if (price !== undefined) {
const amountString = this.numberToString (amount);
const priceString = this.numberToString (price);
const quoteOrderQuantity = Precise.stringMul (amountString, priceString);
request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQuantity, TRUNCATE, precision, this.precisionMode);
if (clientOrderId === undefined) {
const broker = this.safeValue (this.options, 'broker');
if (broker !== undefined) {
const brokerId = this.safeString (broker, 'spot');
if (brokerId !== undefined) {
request['newClientOrderId'] = brokerId + this.uuid22 ();
}
}
} else {
request['newClientOrderId'] = clientOrderId;
}
request['newOrderRespType'] = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
let timeInForceIsRequired = false;
let priceIsRequired = false;
let stopPriceIsRequired = false;
let quantityIsRequired = false;
if (uppercaseType === 'MARKET') {
const quoteOrderQty = this.safeValue (this.options, 'quoteOrderQty', true);
if (quoteOrderQty) {
const quoteOrderQtyNew = this.safeValue2 (params, 'quoteOrderQty', 'cost');
const precision = market['precision']['price'];
if (quoteOrderQtyNew !== undefined) {
request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQtyNew, TRUNCATE, precision, this.precisionMode);
} else if (price !== undefined) {
const amountString = this.numberToString (amount);
const priceString = this.numberToString (price);
const quoteOrderQuantity = Precise.stringMul (amountString, priceString);
request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQuantity, TRUNCATE, precision, this.precisionMode);
} else {
quantityIsRequired = true;
}
} else {
quantityIsRequired = true;
}
} else {
} else if (uppercaseType === 'LIMIT') {
priceIsRequired = true;
timeInForceIsRequired = true;
quantityIsRequired = true;
} else if ((uppercaseType === 'STOP_LOSS') || (uppercaseType === 'TAKE_PROFIT')) {
stopPriceIsRequired = true;
quantityIsRequired = true;
} else if ((uppercaseType === 'STOP_LOSS_LIMIT') || (uppercaseType === 'TAKE_PROFIT_LIMIT')) {
quantityIsRequired = true;
stopPriceIsRequired = true;
priceIsRequired = true;
timeInForceIsRequired = true;
} else if (uppercaseType === 'LIMIT_MAKER') {
priceIsRequired = true;
quantityIsRequired = true;
}
} else if (uppercaseType === 'LIMIT') {
priceIsRequired = true;
timeInForceIsRequired = true;
quantityIsRequired = true;
} else if ((uppercaseType === 'STOP_LOSS') || (uppercaseType === 'TAKE_PROFIT')) {
stopPriceIsRequired = true;
quantityIsRequired = true;
} else if ((uppercaseType === 'STOP_LOSS_LIMIT') || (uppercaseType === 'TAKE_PROFIT_LIMIT')) {
quantityIsRequired = true;
stopPriceIsRequired = true;
priceIsRequired = true;
timeInForceIsRequired = true;
} else if (uppercaseType === 'LIMIT_MAKER') {
priceIsRequired = true;
quantityIsRequired = true;
}
if (quantityIsRequired) {
if (quantityIsRequired) {
request['quantity'] = this.amountToPrecision (symbol, amount);
}
if (priceIsRequired) {
if (price === undefined) {
throw new InvalidOrder (this.id + ' editOrder() requires a price argument for a ' + type + ' order');
}
request['price'] = this.priceToPrecision (symbol, price);
}
if (timeInForceIsRequired) {
request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel
}
if (stopPriceIsRequired) {
if (stopPrice === undefined) {
throw new InvalidOrder (this.id + ' editOrder() requires a stopPrice extra param for a ' + type + ' order');
} else {
request['stopPrice'] = this.priceToPrecision (symbol, stopPrice);
}
}
request['cancelReplaceMode'] = 'STOP_ON_FAILURE'; // If the cancel request fails, the new order placement will not be attempted.
const cancelId = this.safeString2 (params, 'cancelNewClientOrderId', 'cancelOrigClientOrderId');
if (cancelId === undefined) {
request['cancelOrderId'] = id; // user can provide either cancelOrderId, cancelOrigClientOrderId or cancelOrigClientOrderId
}
// remove timeInForce from params because PO is only used by this.isPostOnly and it's not a valid value for Binance
if (this.safeString (params, 'timeInForce') === 'PO') {
params = this.omit (params, [ 'timeInForce' ]);
}
params = this.omit (params, [ 'quoteOrderQty', 'cost', 'stopPrice', 'newClientOrderId', 'clientOrderId', 'postOnly' ]);
response = await this.privatePostOrderCancelReplace (this.extend (request, params));
} else {
request['orderId'] = id;
request['quantity'] = this.amountToPrecision (symbol, amount);
}
if (priceIsRequired) {
if (price === undefined) {
throw new InvalidOrder (this.id + ' editOrder() requires a price argument for a ' + type + ' order');
if (price !== undefined) {
request['price'] = this.priceToPrecision (symbol, price);
}
request['price'] = this.priceToPrecision (symbol, price);
}
if (timeInForceIsRequired) {
request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel
}
if (stopPriceIsRequired) {
if (stopPrice === undefined) {
throw new InvalidOrder (this.id + ' editOrder() requires a stopPrice extra param for a ' + type + ' order');
} else {
request['stopPrice'] = this.priceToPrecision (symbol, stopPrice);
if (clientOrderId !== undefined) {
request['origClientOrderId'] = clientOrderId;
}
params = this.omit (params, [ 'clientOrderId', 'newClientOrderId' ]);
if (market['linear']) {
response = await this.fapiPrivatePutOrder (this.extend (request, params));
} else if (market['inverse']) {
response = await this.dapiPrivatePutOrder (this.extend (request, params));
}
}
// remove timeInForce from params because PO is only used by this.isPostOnly and it's not a valid value for Binance
if (this.safeString (params, 'timeInForce') === 'PO') {
params = this.omit (params, [ 'timeInForce' ]);
}
const requestParams = this.omit (params, [ 'quoteOrderQty', 'cost', 'stopPrice', 'newClientOrderId', 'clientOrderId', 'postOnly' ]);
const response = await this.privatePostOrderCancelReplace (this.extend (request, requestParams));
//
// spot
//
// {
// "cancelResult": "SUCCESS",
Expand Down Expand Up @@ -3792,6 +3810,106 @@ export default class binance extends Exchange {
return this.parseOrder (data, market);
}

async editContractOrder (id: string, symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name binance#editOrder
* @description edit a trade order
* @see https://binance-docs.github.io/apidocs/futures/en/#modify-order-trade
* @see https://binance-docs.github.io/apidocs/delivery/en/#modify-order-trade
* @param {string} id cancel order id
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of currency you want to trade in units of base currency
* @param {float|undefined} price the price at which the order is to be fullfilled, in units of the base currency, ignored in market orders
* @param {object} params extra parameters specific to the binance api endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
await this.loadMarkets ();
const market = this.market (symbol);
if (!market['contract']) {
throw new NotSupported (this.id + ' editContractOrder() does not support ' + market['type'] + ' orders');
}
const request = {
'symbol': market['id'],
'side': side.toUpperCase (),
};
const clientOrderId = this.safeStringN (params, [ 'newClientOrderId', 'clientOrderId', 'origClientOrderId' ]);
request['orderId'] = id;
request['quantity'] = this.amountToPrecision (symbol, amount);
if (price !== undefined) {
request['price'] = this.priceToPrecision (symbol, price);
}
if (clientOrderId !== undefined) {
request['origClientOrderId'] = clientOrderId;
}
params = this.omit (params, [ 'clientOrderId', 'newClientOrderId' ]);
let response = undefined;
if (market['linear']) {
response = await this.fapiPrivatePutOrder (this.extend (request, params));
} else if (market['inverse']) {
response = await this.dapiPrivatePutOrder (this.extend (request, params));
}
//
// swap and future
//
// {
// "orderId": 151007482392,
// "symbol": "BTCUSDT",
// "status": "NEW",
// "clientOrderId": "web_pCCGp9AIHjziKLlpGpXI",
// "price": "25000",
// "avgPrice": "0.00000",
// "origQty": "0.001",
// "executedQty": "0",
// "cumQty": "0",
// "cumQuote": "0",
// "timeInForce": "GTC",
// "type": "LIMIT",
// "reduceOnly": false,
// "closePosition": false,
// "side": "BUY",
// "positionSide": "BOTH",
// "stopPrice": "0",
// "workingType": "CONTRACT_PRICE",
// "priceProtect": false,
// "origType": "LIMIT",
// "updateTime": 1684300587845
// }
//
return this.parseOrder (response, market);
}

async editOrder (id: string, symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name binance#editOrder
* @description edit a trade order
* @see https://binance-docs.github.io/apidocs/spot/en/#cancel-an-existing-order-and-send-a-new-order-trade
* @see https://binance-docs.github.io/apidocs/futures/en/#modify-order-trade
* @see https://binance-docs.github.io/apidocs/delivery/en/#modify-order-trade
* @param {string} id cancel order id
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of currency you want to trade in units of base currency
* @param {float|undefined} price the price at which the order is to be fullfilled, in units of the base currency, ignored in market orders
* @param {object} params extra parameters specific to the binance api endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
await this.loadMarkets ();
const market = this.market (symbol);
if (market['option']) {
throw new NotSupported (this.id + ' editOrder() does not support ' + market['type'] + ' orders');
}
if (market['spot']) {
return await this.editSpotOrder (id, symbol, type, side, amount, price, params);
} else {
return await this.editContractOrder (id, symbol, type, side, amount, price, params);
}
}

parseOrderStatus (status) {
const statuses = {
'NEW': 'open',
Expand Down Expand Up @@ -3850,6 +3968,32 @@ export default class binance extends Exchange {
// "fills": []
// }
//
// swap and future: editOrder
//
// {
// "orderId": 151007482392,
// "symbol": "BTCUSDT",
// "status": "NEW",
// "clientOrderId": "web_pCCGp9AIHjziKLlpGpXI",
// "price": "25000",
// "avgPrice": "0.00000",
// "origQty": "0.001",
// "executedQty": "0",
// "cumQty": "0",
// "cumQuote": "0",
// "timeInForce": "GTC",
// "type": "LIMIT",
// "reduceOnly": false,
// "closePosition": false,
// "side": "BUY",
// "positionSide": "BOTH",
// "stopPrice": "0",
// "workingType": "CONTRACT_PRICE",
// "priceProtect": false,
// "origType": "LIMIT",
// "updateTime": 1684300587845
// }
//
// futures
//
// {
Expand Down

0 comments on commit a128e70

Please sign in to comment.