jaggedsoft / node-binance-api

Node Binance API is an asynchronous node.js library for the Binance API designed to be easy to use.
MIT License
1.58k stars 767 forks source link

Margin Spot OCO error: 'type' was not sent, was empty/null, or malformed #641

Open teshtek opened 3 years ago

teshtek commented 3 years ago

I have always an error in Margin Trade:

This the code:

clientBinance.mgBuy(pairs, quantity, takeProfit, { type: 'OCO', stopLimitPrice: swingHIGH, stopPrice: swingHIGH }, (e, res) => {....});

and

clientBinance.mgSell(pairs, quantity, takeProfit, { type: 'OCO', stopLimitPrice: swingLOW, stopPrice: swingLOW }, (e, res) => {...});

This the error:

{"code":-1102,"msg":"Mandatory parameter 'type' was not sent, was empty/null, or malformed."}

Tom687 commented 3 years ago

Got the same problem

C4rno commented 1 year ago

I have this problem too

Tom687 commented 1 year ago

I have this problem too

Hi, if I remember correctly, OCO is not an accepted type in the margin trading Binance API (as well as the Futures API, but that could have changed since). What I did to solve this is simply use a function that calculates my TP and SL, then place them using the following types for TP : TAKE_PROFIT or TAKE_PROFIT_MARKET and for SL : STOP or STOP_MARKET.

You then need a way to know when your TP or SL is hit, so you can cancel the other one. Personally, I made my bot use TradingView alerts (using TW webhooks). That way, I can create or use existing TW strategies and when the conditions are met, it sends a POST request to my API with the parameters I configured in the alerts. What I did to know which order to cancel (TP or SL) is by making alerts when one is hit. So when the TP or SL is hit, the alert comes to my API with parameters telling which order was hit and with a bit of code, cancel the other one (if TP is hit, cancel the SL order and vice versa).

If you want, here is my function that calculates the TP and SL needed for the trade (this is Node.js). It uses ATR values and the wanted R:R ratio (that come from the parameters I put in my TW webhook alerts) :

const calculateTakeProfitAndStopLoss = async (
  symbol, 
  paidPricePerUnit,
  side,
  ATRValue, 
  stopLossATR,
  rewardRiskRatio
) => {
  try {
    const tickSize = minimums[symbol].tickSize;

    let takeProfitPrice, stopPrice, stopLimitPrice;

    // Calculate SL value using the ATR value provided and TP value using R:R ratio provided
    const stopLossValue = Number(ATRValue) * Number(stopLossATR);
    const takeProfitValue = Number(rewardRiskRatio) * Number(stopLossValue);

    // Calculate TP, stop and stop limit prices taking into account if it is a LONG or SHORT position
    if (side === 'BUY') { // LONG position
      stopPrice = Number(paidPricePerUnit) - Number(stopLossValue);
      stopLimitPrice = Number(stopLimitPrice);
      takeProfitPrice = Number(takeProfitValue) + Number(paidPricePerUnit);
    }
    else if (side === 'SELL') { // SHORT position
      stopPrice = Number(stopLossValue) + Number(paidPricePerUnit);
      stopLimitPrice = Number(stopLimitPrice);
      takeProfitPrice = Number(paidPricePerUnit) - Number(takeProfitValue);
    }
    else {
      throw new Error('Trade type is neither LONG or SHORT');
    }

    // Use roundTicks() (with tickSize) to round TP / SL values to correct decimal
    takeProfitPrice = await binanceClient.roundTicks(takeProfitPrice, tickSize);
    stopPrice = await binanceClient.roundTicks(stopPrice, tickSize);
    stopLimitPrice = await binanceClient.roundTicks(stopLimitPrice, tickSize);

    // Return string values otherwise it seems to not work (error, not making the orders)
    return {
      takeProfitPrice: takeProfitPrice.toString(),
      stopLimitPrice: stopLimitPrice.toString(),
      stopPrice: stopPrice.toString(),
    };
  }
  catch (err) {
    console.error('Err calculateTPandSL :', err);
  }
};

I used market orders and if you do, I advice you to use a delay() function to let like 1 second pass before placing the TP and SL orders, to make sure the base order is completed before the TP and SL are set (otherwise it doesn't work properly). If you use limit orders, you would need to check for the status of the order (for it to be completed) before placing the TP and SL, otherwise it won't work.

Here is how you can use the above function, I placed it in the function that actually does "all the work" (adjusting trading type, getting asset price, calculating buy quantity, leverage if any, placing initial order then TP and SL ones) :

// Place long or short order
const baseOrder = await marketOrder(
  symbol, 
  buyQuantity, 
  side
);

// origQty is used to retrieve the exact quantity of the traded asset from the completed base order
const origQty = baseOrder.origQty;

// Calculate TP and SL values
const { 
  takeProfitPrice, 
  stopLimitPrice,
  stopPrice 
} = await calculateTakeProfitAndStopLoss(
  symbol, 
  quotePrice, 
  side,
  ATRValue, 
  stopLossATR, 
  rewardRiskRatio = 1.5
);

// Use of a 1 second delay to make sure the market order is completed before setting the TP and SL ones
await delay(1000);

// reverseSide is used to know if TP and SL orders should be a buy or sell order
let reverseSide = side === 'BUY' ? 'SELL' : 'BUY';

// Place TP and SL orders  
const takeProfitOrder = await futuresOrder(
  reverseSide,
  symbol, 
  origQty, 
  false, 
  { type: 'TAKE_PROFIT_MARKET', stopPrice: takeProfitPrice }
);

const stopLossOrder = await futuresOrder(
  reverseSide, 
  symbol, 
  origQty, 
  false, 
  { type: 'STOP_MARKET', stopPrice: stopPrice }
);

The marketOrder() function is just a wrapper that uses either binanceClient.futuresMarketSell() or binanceClient.futuresMarketBuy(), depending on the side of the order (long or short).

Same for the futuresOrder() function, which is a wrapper that uses binanceClient.futuresOrder().

Note that this line const origQty = baseOrder.origQty; is important and used to retrieve the exact quantity of the traded asset (that comes from the original completed order), as it can vary from the one you calculated. If the quantity you put in your TP / SL orders is not the exact same as the base completed order, there is two possibilities :

1) The quantity you calculated is a bit more than the actual completed order quantity (even by just one decimal) : when trying to place the TP and SL orders, they will fail and not be set.

2) The quantity you calculated is a bit less than the actual completed order quantity : your TP and SL will be set but will be shy of the difference between your calculated quantity and the actual order quantity. This means that when your TP or SL will be hit, you will still have that little asset quantity difference continuing the trade (not good at all). If this happens and you don't see it, that little quantity trade could be continuing for days, weeks or even months and accumulate lots of fees.

This is why you must use this line of code to make sure that, when setting your TP and SL, the quantities are the exact same as the original base order quantity.

Also note that this is for Binance Futures trading and not margin trading, which I found after a lot of trial and error that the Futures trading API is more complete and easy to use than the margin trading API (but that could have changed since, I made my bot around 2 years ago).

C4rno commented 1 year ago

Thanks for you answer, but i solvet it with the wonderful modular programming using node js, just find another package that has the OCO and margin part implemented, create an instance of said package and that's it, my code is left as it was and I only use that instance for OCO

You can checkk this:

await client.marginOrderOco({ symbol: 'BTCBUSD' type: 'LIMIT', side: 'SELL' quantity: 0.1, price: 18000, stopPrice: 16000, stopLimitPrice: 15000 }

Tom687 commented 1 year ago

Glad to hear you found out a solution by yourself ! Could you please share the package name ? Thank you very much