ccxt: Bybit V5 - buggy stoploss behavior

Operating System

linux

Programming Languages

Python

CCXT Version

4.0.101

Description

Creating a stop-order on bybit futures now requires a slightly different paremter (we used to use stopPrice - which is now triggering a “params error: TriggerDirection invalid” error.

Now adopting this to stopLossPrice is not a problem - but if i create an order with stopLossPrice, i’d expect to get this same price back. Unfortunately, that’s not the case, and stopLossPrice fails to parse.

import ccxt
exchange = ccxt.bybit({
    'apiKey': '<yourApiKey>', 
    'secret': '<yoursecret>', 
    'options': {'defaultType': 'swap'} 
    })
pair = 'ETH/USDT:USDT'
params = {
    'stopPrice': 1401,
}

# o = exchange.create_order(pair, 'limit', 'sell', amount=0.01, price=1401, params=params)
# Exception (see below detail block) for output

# Moving on
params = {
    'stopLossPrice': 1401,
}

o = exchange.create_order(pair, 'limit', 'sell', amount=0.01, price=1401, params=params)
o

exchange.fetch_order(o['id'], pair)

Output

Exception output ...


fetch Request: bybit POST https://api.bybit.com/v5/order/create RequestHeaders: {'Content-Type': 'application/json', 'X-BAPI-API-KEY': '', 'X-BAPI-TIMESTAMP': '1695399145735', 'X-BAPI-RECV-WINDOW': '5000', 'X-BAPI-SIGN': '', 'Referer': 'CCXT', 'User-Agent': 'python-requests/2.31.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'} RequestBody: {"symbol":"ETHUSDT","side":"Sell","orderType":"Limit","category":"linear","qty":"0.01","price":"1401","triggerPrice":"1401"}

fetch Response: bybit POST https://api.bybit.com/v5/order/create 200 ResponseHeaders: {'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '116', 'Connection': 'keep-alive', 'Date': 'Fri, 22 Sep 2023 16:12:26 GMT', 'Ret_code': '10001', 'Cache-Control': 'no-store', 'Traceid': 'e76f7b0639c9a53d8714e1f1415b7c3c', 'Timenow': '1695399146142', 'Server': 'Openresty', 'X-Cache': 'Miss from cloudfront', 'Via': '1.1 387adc951beb5181d840dfb5d1f09488.cloudfront.net (CloudFront)', 'X-Amz-Cf-Pop': 'FRA56-P4', 'X-Amz-Cf-Id': '=='} ResponseBody: {"retCode":10001,"retMsg":"params error: TriggerDirection invalid","result":{},"retExtInfo":{},"time":1695399146142}


---------------------------------------------------------------------------
BadRequest                                Traceback (most recent call last)
[/home/xmatt/devel/cryptos/stuff/ccxt_bybit.ipynb](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/ccxt_bybit.ipynb) Cell 12 line 1
----> [1](vscode-notebook-cell:/home/xmatt/devel/cryptos/stuff/ccxt_bybit.ipynb#X14sZmlsZQ%3D%3D?line=0) o = ct.create_order(pair, 'limit', 'sell', amount=0.01, price=1401, params=params)
      [2](vscode-notebook-cell:/home/xmatt/devel/cryptos/stuff/ccxt_bybit.ipynb#X14sZmlsZQ%3D%3D?line=1) o

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/bybit.py:3579](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/bybit.py:3579), in bybit.create_order(self, symbol, type, side, amount, price, params)
   3577     request['orderLinkId'] = self.uuid16()
   3578 params = self.omit(params, ['stopPrice', 'timeInForce', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId', 'triggerPrice', 'stopLoss', 'takeProfit'])
-> 3579 response = self.privatePostV5OrderCreate(self.extend(request, params))
   3580 #
   3581 #     {
   3582 #         "retCode": 0,
   (...)
   3590 #     }
   3591 #
   3592 order = self.safe_value(response, 'result', {})

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/types.py:26](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/types.py:26), in Entry.__init__.<locals>.unbound_method(_self, params)
     25 def unbound_method(_self, params={}):
---> 26     return _self.request(self.path, self.api, self.method, params, config=self.config)

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3051](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3051), in Exchange.request(self, path, api, method, params, headers, body, config)
   3050 def request(self, path, api: Any = 'public', method='GET', params={}, headers: Optional[Any] = None, body: Optional[Any] = None, config={}):
-> 3051     return self.fetch2(path, api, method, params, headers, body, config)

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3048](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3048), in Exchange.fetch2(self, path, api, method, params, headers, body, config)
   3046 self.lastRestRequestTimestamp = self.milliseconds()
   3047 request = self.sign(path, api, method, params, headers, body)
-> 3048 return self.fetch(request['url'], request['method'], request['headers'], request['body'])

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:654](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:654), in Exchange.fetch(self, url, method, headers, body)
    651     else:
    652         raise ExchangeError(details) from e
--> 654 self.handle_errors(http_status_code, http_status_text, url, method, headers, http_response, json_response, request_headers, request_body)
    655 if json_response is not None:
    656     return json_response

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/bybit.py:6917](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/bybit.py:6917), in bybit.handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody)
   6915         feedback = self.id + ' ' + body
   6916     self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
-> 6917     self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
   6918     raise ExchangeError(feedback)  # unknown message
   6919 return None

File [~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3414](https://file+.vscode-resource.vscode-cdn.net/home/xmatt/devel/cryptos/stuff/~/.pyenv/versions/3.11.4/envs/trade_3114/lib/python3.11/site-packages/ccxt/base/exchange.py:3414), in Exchange.throw_exactly_matched_exception(self, exact, string, message)
   3412 def throw_exactly_matched_exception(self, exact, string, message):
   3413     if string in exact:
-> 3414         raise exact[string](message)

BadRequest: bybit {"retCode":10001,"retMsg":"params error: TriggerDirection invalid","result":{},"retExtInfo":{},"time":1695399146142}

-- Create order
{'info': {'orderId': 'b8273344-3013-4522-8f76-cde643747434',
  'orderLinkId': ''},
 'id': 'b8273344-3013-4522-8f76-cde643747434',
 'clientOrderId': None,
 'timestamp': None,
 'datetime': None,
 'lastTradeTimestamp': None,
 'lastUpdateTimestamp': None,
 'symbol': 'ETH/USDT:USDT',
 'type': None,
 'timeInForce': None,
 'postOnly': None,
 'reduceOnly': None,
 'side': None,
 'price': None,
 'stopPrice': None,
 'triggerPrice': None,
 'takeProfitPrice': None,
 'stopLossPrice': None,
 'amount': None,
 'cost': None,
 'average': None,
 'filled': None,
 'remaining': None,
 'status': None,
 'fee': None,
 'trades': [],
 'fees': []}


--- subsequent fetch

{'info': {'orderId': 'b8273344-3013-4522-8f76-cde643747434',
  'orderLinkId': '',
  'blockTradeId': '',
  'symbol': 'ETHUSDT',
  'price': '1401.00',
  'qty': '0.01',
  'side': 'Sell',
  'isLeverage': '',
  'positionIdx': '0',
  'orderStatus': 'Untriggered',
  'cancelType': 'UNKNOWN',
  'rejectReason': 'EC_NoError',
  'avgPrice': '0',
  'leavesQty': '0.01',
  'leavesValue': '14.01',
  'cumExecQty': '0.00',
  'cumExecValue': '0',
  'cumExecFee': '0',
  'timeInForce': 'GTC',
  'orderType': 'Limit',
  'stopOrderType': 'Stop',
  'orderIv': '',
  'triggerPrice': '1401.00',
  'takeProfit': '0.00',
  'stopLoss': '0.00',
  'tpTriggerBy': 'UNKNOWN',
  'slTriggerBy': 'UNKNOWN',
  'triggerDirection': '2',
  'triggerBy': 'LastPrice',
  'lastPriceOnCreated': '0.00',
  'reduceOnly': True,
  'closeOnTrigger': False,
  'smpType': 'None',
  'smpGroup': '0',
  'smpOrderId': '',
  'tpslMode': '',
  'tpLimitPrice': '',
  'slLimitPrice': '',
  'placeType': '',
  'createdTime': '1695398854285',
  'updatedTime': '1695398854285',
  'nextPageCursor': 'page_token%3D12397685%26'},
 'id': 'b8273344-3013-4522-8f76-cde643747434',
 'clientOrderId': None,
 'timestamp': 1695398854285,
 'datetime': '2023-09-22T16:07:34.285Z',
 'lastTradeTimestamp': 1695398854285,
 'lastUpdateTimestamp': 1695398854285,
 'symbol': 'ETH/USDT:USDT',
 'type': 'limit',
 'timeInForce': 'GTC',
 'postOnly': False,
 'reduceOnly': True,
 'side': 'sell',
 'price': 1401.0,
 'stopPrice': 1401.0,
 'triggerPrice': 1401.0,
 'takeProfitPrice': None,
 'stopLossPrice': None, <-- i used this parameter to create the order - shouldn't it be filled?
 'amount': 0.01,
 'cost': 0.0,
 'average': None,
 'filled': 0.0,
 'remaining': 0.01,
 'status': 'open',
 'fee': {'cost': 0.0, 'currency': 'USDT'},
 'trades': [],
 'fees': [{'cost': 0.0, 'currency': 'USDT'}]}  

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Comments: 17 (12 by maintainers)

Most upvoted comments

@xmatthias I agree, that would be ideal (to fill in stopLossPrice and takeProfitPrice ), thanks for your suggestion, I will run some tests and let you know if that is doable.

@xmatthias the reasoning for deleting that param is because it’s not required so removing it gives the user more flexibility with creating orders, and stopLossPrice and takeProfitPrice use that param so its redundant to have stop orders behave the exact same as stopLossPrice and takeProfitPrice orders.

I’m not getting an error on my end so I don’t think it’s introducing a bug other than maybe parseOrder could be adjusted but the commit does have the fix() tag, I think the freqtrade user is confused because using the stopPrice or stopLossPrice params creates a conditional order not an actual stopLoss order attached to a position.

stopPrice or triggerPrice: https://github.com/ccxt/ccxt/wiki/Manual#trigger-orders stopLossPrice: https://github.com/ccxt/ccxt/wiki/Manual#stop-loss-orders stopLoss: https://github.com/ccxt/ccxt/wiki/Manual#stoploss-and-takeprofit-orders-attached-to-a-position