ccxt / ccxt

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading API with support for more than 100 bitcoin/altcoin exchanges
https://docs.ccxt.com
MIT License
32.93k stars 7.51k forks source link

Bybit stop loss order can get invalid trigger #19000

Closed hodlerhacks closed 1 year ago

hodlerhacks commented 1 year ago

Operating System

Windows

Programming Languages

JavaScript

CCXT Version

4.0.75

Description

When placing a stop loss order on Bybit with a Unified Account, like this:

await exchange.createOrder('XRP/USDT', 'limit', 'sell', 20, 0.51, { stopLossPrice: 0.52 });

This works fine if the actual price is above the stopLossPrice. Then the trigger price on Bybit shows <= 0.52 (when checking the order details in the Bybit web application).

However, if the actual price is below the stopLossPrice when creating the order, then the trigger price on Bybit shows >= 0.52. This obviously is problematic, as the limit sell order will not be triggerd (until the price climbes above 0.52).

What I would expect is one of two options:

  1. The order is not created at all, and an OrderImmediatelyFillable exception is thrown. This is the Binance behavior
  2. The order is created and the limit sell order is triggered immediately. This is the KuCoin behavior

The thing is, in my code I do check if the current price is not already below the stopLossPrice, but if the price is dropping fast enough, that check will not help, and an order is created that doesn't work as intended, i.e. the coins will not be sold. This now is quite a serious loop hole in my application, and I have experienced this problem in practice.

Is there something I can do to avoid this problem, or this a bug in CCXT that you guys can fix?

Code

  
hodlerhacks commented 1 year ago

Note: using exchange.verbose = true, I get the following information:

RequestBody: {"symbol":"XRPUSDT","side":"Sell","orderType":"Limit","category":"spot","qty":"15.44","price":"0.51","triggerDirection":2,"triggerPrice":"0.52","reduceOnly":true,"orderFilter":"tpslOrder"}

The triggerDirection seems to be correct.

However, in the onMessage data:

2023-08-25T15:43:12.466Z onMessage { topic: 'order', id: 'SyncStopOrder_1495203880737181952', creationTime: 1692978192366, data: [ { category: 'spot', symbol: 'XRPUSDT', orderId: '1495203880737181952', orderLinkId: '1692978192350902', blockTradeId: '', side: 'Sell', positionIdx: 0, orderStatus: 'Untriggered', cancelType: 'UNKNOWN', rejectReason: 'EC_NoError', timeInForce: 'GTC', isLeverage: '0', price: '0.5100', qty: '15.44', avgPrice: '0', leavesQty: '15.44', leavesValue: '0.000000', cumExecQty: '0.00', cumExecValue: '0.000000', cumExecFee: '', orderType: 'Limit', stopOrderType: 'tpslOrder', orderIv: '', triggerPrice: '0.5200', takeProfit: '', stopLoss: '', triggerBy: '', tpTriggerBy: '', slTriggerBy: '', triggerDirection: 0, placeType: '', lastPriceOnCreated: '0.5172', closeOnTrigger: false, reduceOnly: false, smpGroup: 0, smpType: 'None', smpOrderId: '', createdTime: '1692978192356', updatedTime: '1692978192356', feeCurrency: '' } ] }

I see that triggerDirection is set to zero.

Perhaps this helps with identifying the problem.

Dan-krm commented 1 year ago

@hodlerhacks this looks like a bug on the exchange side since the triggerDirection is correct in the RequestBody but is changed in the response. We could ask Bybit if they could make some changes to handle this situation similar to how Binance or Kucoin handle it like you mentioned. I think if CCXT attempted to handle this it would have the same results as the check you've implemented in your code comparing the current price and stopLossPrice.

I'm not familiar with your application so I'm not sure why you're creating spot conditional orders with the trigger-price, execution-price and market-price so close to each other, but I'm thinking maybe just using a limit order would be ideal in this situation to avoid this issue and to have your code behave similar to the Kucoin behavior, or creating orders that have some more space between the current price and stopLossPrice

hodlerhacks commented 1 year ago

Hi @Dan-krm, thanks for your quick response! I wasn't sure the onMessage info was the Bybit response, but then this seems to be a bug on their side indeed. Would be great if you can ask Bybit to resolve this.

And you're right, generally, my application tries to avoid placing a stop loss order so close to the actual price, but depending on the situation, and especially when the price is suddenly dropping fast, then this problem does arise, and I can't handle it in any other way. As long as I either get an exception, or a limit sell order is placed, i.e. the stop loss triggers immediately (preferred), then I can manage.

Could you please share the link to the issue once you've reported it to Bybit, so I can track it as well, as the launch of my application depends on it. Or do you have direct communication with them for issues?

Thanks so much!

hodlerhacks commented 1 year ago

Hi @Dan-krm, what would be the best way forward? I reported the problem on the 'Bybit API Discussion' Telegram channel (twice), but the channel is flooded with messages so things easily get lost, and I haven't received any response so far. So if you have direct contact with Bybit, that would certainly help.

Dan-krm commented 1 year ago

Hi @hodlerhacks, CCXT has a communication channel with access to some members of the Bybit team, I've brought this issue to their attention and they said they'll look into it. I'll let you know of any updates I receive

Dan-krm commented 1 year ago

@hodlerhacks They are saying the triggerDirection currently can't be set for spot orders and is only applicable for Futures conditional orders, for spot orders it looks like the direction is being automatically set causing this issue. Could you try setting "orderFilter":"StopOrder" in your params and check the website to see if the issue still persists?

hodlerhacks commented 1 year ago

Hi @Dan-krm, thanks a lot for following up with Bybit!

I tried what you've suggested. Instead of a TP/SL order, it now appears in the orders list as a conditional limit order. However, the triggerDirection is still incorrect, unfortunately.

Do you think Bybit would consider enabling this use case, or should I not count on it getting fixed/supported?

Anything else I could try/do without changes on the Bybit API?

Dan-krm commented 1 year ago

@hodlerhacks We determined that this issue could be resolved if triggerDirection was supported for spot markets similar to how it's supported for futures, because for futures an error would be thrown in the situation you've described.

Our contact said they will discuss this with their product team next week and submit a request to support triggerDirection for spot markets, but they can't promise they will do it.

I wouldn't count on it getting fixed, but you can watch the changelog especially in the coming weeks to see if they end up adding support: https://bybit-exchange.github.io/docs/changelog/v5

I'm going to close this issue now, but I'll comment and tag you in this issue if I receive any other updates

hodlerhacks commented 1 year ago

Great, thanks again, appreciate your efforts to get this resolved! Fingers crossed that they will fix it.

FYI, and in case other people run into the same problem, I found a workaround. After creating the stop loss order, when receiving the order event with watchOrders(), I do the following:

      if (exchange == 'bybit' && order.info.stopOrderType == 'tpslOrder') {
          if (Number(order.info.lastPriceOnCreated) <= order.stopPrice) {
              // cancel stop loss order
              // create limit sell order
          }
      }

Still, of course it would be far better if Bybit would fix the problem, to avoid wasting time creating, canceling and creating another order. But this way at least the application doesn't get stuck assuming a stop loss order has been placed.