Repository: ccxt/ccxt
Pull request: Bybit: update stopLoss and takeProfit
Updated createContractV3Order and createUnifiedAccountOrder, stopLoss and takeProfit orders to follow the current recommended schema:
#13822
fixes: #16856

### stop type 1:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.03 1800 '{"stopPrice":1900}'

{
  info: { orderId: 'a46bd231-c63e-4d43-9977-b41ae1725f6b', orderLinkId: '' },
  id: 'a46bd231-c63e-4d43-9977-b41ae1725f6b',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

### stopLoss type 2:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.01 1300 '{"stopLossPrice":1400}'

{
  info: { orderId: 'a05d6bd5-56cc-4745-8a22-7a1658dc955d', orderLinkId: '' },
  id: 'a05d6bd5-56cc-4745-8a22-7a1658dc955d',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

### stopLoss and takeProfit type 3:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.01 1300 '{"stopLoss":1200,"takeProfit":1800}'

{
  info: { orderId: 'd3fc1b8f-11fb-45ba-913f-e61d8113f87f', orderLinkId: '' },
  id: 'd3fc1b8f-11fb-45ba-913f-e61d8113f87f',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

Problem statement:
TakeProfit and StopLoss are not working
### ؜

OS = Win 10

LANGUAGE = PYTHON

CCXT version = i didn't really understand how to get it 

My question is, what's the proper way to set stop loss and take profit?

exchange = bybit

also why all the attributes of the dictionary order are none?

Thx in advance

```python
def executeOrder(side, takeProfitPrice, stopLossPrice, quantity):
    print("order executer activated")
    params = {
        'stopPrice': stopLossPrice,
        'stopLimitPrice': stopLossPrice,
        'stopPriceType': 'limit',
        'takeProfitPrice': takeProfitPrice,
        'takeProfitPriceType': 'limit'
    }
    print(params)
    order = exchange.create_order(
        "ETH/USDT:USDT", 'market', side, quantity, params)
    print(order)
```

and it returns
(I formatted it a little bit to make it more readable)
```
order executer activated
{'stopPrice': 1551.456682573667, 'stopLimitPrice': 1551.456682573667, 'stopPriceType': 'limit', 'takeProfitPrice': 1551.4566825
{'info': {'orderId': '04893131-a394-4340-94ea-bbde30c30536', 'orderLinkId': ''}, 
'id': '04893131-a394-4340-94ea-bbde30c30536', 'clientOrderId': None, 'timestamp': None, 'datetime': None, 
'lastTradeTimestamp': None, 'symbol': None, 'type': None, 'timeInForce': None, 'postOnly': None, 'side': None, 'price': None, 
'stopPrice': None, 'triggerPrice': None, 'amount': None, 'cost': None, 'average': None, 'filled': None, 'remaining': None,
 'status': None, 'fee': None, 'trades': [], 'fees': [], 'reduceOnly': None}
```

Hints from issue discussion:
Look at https://github.com/ccxt/ccxt/issues/13822
hi, i have a same problem,  
TakeProfit and StopLoss are not working in bybit when create order 
> hi, i have a same problem, TakeProfit and StopLoss are not working in bybit when create order

@Dan-krm It looks like you added bybit takeProfit and stopLoss (https://github.com/ccxt/ccxt/pull/13859) functionality, can you take a look at this?

Thanks
> > hi, i have a same problem, TakeProfit and StopLoss are not working in bybit when create order
> 
> @Dan-krm It looks like you added bybit takeProfit and stopLoss (#13859) functionality, can you take a look at this?
> 
> Thanks

I think it was changed a few times since then, I'll take a look though

Commit message: feat(Bybit): update createContractV3Order and createUnifiedAccountOrder stopLoss and takeProfit orders with current unified approach

Updated createContractV3Order and createUnifiedAccountOrder, stopLoss and takeProfit orders to follow the current recommended schema:

### stop type 1:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.03 1800 '{"stopPrice":1900}'

{
  info: { orderId: 'a46bd231-c63e-4d43-9977-b41ae1725f6b', orderLinkId: '' },
  id: 'a46bd231-c63e-4d43-9977-b41ae1725f6b',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

### stopLoss type 2:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.01 1300 '{"stopLossPrice":1400}'

{
  info: { orderId: 'a05d6bd5-56cc-4745-8a22-7a1658dc955d', orderLinkId: '' },
  id: 'a05d6bd5-56cc-4745-8a22-7a1658dc955d',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

### stopLoss type 3:
```
node examples/js/cli bybit createOrder ETH/USDT:USDT limit buy 0.01 1300 '{"stopLoss":'{"price":1200}',"takeProfit":'{"price":1800}'}'

{
  info: { orderId: 'd3fc1b8f-11fb-45ba-913f-e61d8113f87f', orderLinkId: '' },
  id: 'd3fc1b8f-11fb-45ba-913f-e61d8113f87f',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  side: undefined,
  price: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: [],
  fees: [],
  reduceOnly: undefined
}
```

Patch to review:
diff --git a/js/bybit.js b/js/bybit.js
index dfad5071cc4c8..6f99831f5babb 100644
--- a/js/bybit.js
+++ b/js/bybit.js
@@ -3358,6 +3358,10 @@ module.exports = class bybit extends Exchange {
          * @method
          * @name bybit#createOrder
          * @description create a trade order
+         * @see https://bybit-exchange.github.io/docs/v5/order/create-order
+         * @see https://bybit-exchange.github.io/docs/spot/trade/place-order
+         * @see https://bybit-exchange.github.io/docs/derivatives/unified/place-order
+         * @see https://bybit-exchange.github.io/docs/derivatives/contract/place-order
          * @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'
@@ -3444,28 +3448,40 @@ module.exports = class bybit extends Exchange {
         } else if (timeInForce === 'ioc') {
             request['timeInForce'] = 'IOC';
         }
-        const triggerPrice = this.safeValue2 (params, 'stopPrice', 'triggerPrice');
-        const stopLossPrice = this.safeValue (params, 'stopLossPrice');
-        const isStopLossOrder = stopLossPrice !== undefined;
-        const takeProfitPrice = this.safeValue (params, 'takeProfitPrice');
-        const isTakeProfitOrder = takeProfitPrice !== undefined;
-        if (isStopLossOrder) {
-            request['stopLoss'] = this.priceToPrecision (symbol, stopLossPrice);
-        }
-        if (isTakeProfitOrder) {
-            request['takeProfit'] = this.priceToPrecision (symbol, takeProfitPrice);
-        }
-        if (triggerPrice !== undefined) {
-            // logical xor
+        const triggerPrice = this.safeNumber2 (params, 'triggerPrice', 'stopPrice');
+        const stopLossTriggerPrice = this.safeNumber (params, 'stopLossPrice', triggerPrice);
+        const takeProfitTriggerPrice = this.safeNumber (params, 'takeProfitPrice');
+        const isStopLoss = this.safeValue (params, 'stopLoss');
+        const isTakeProfit = this.safeValue (params, 'takeProfit');
+        if (triggerPrice) {
             const isBuy = side === 'buy';
-            const ascending = stopLossPrice ? !isBuy : isBuy;
+            const ascending = stopLossTriggerPrice ? !isBuy : isBuy;
             request['triggerDirection'] = ascending ? 2 : 1;
-            request['triggerBy'] = 'LastPrice';
             request['triggerPrice'] = this.priceToPrecision (symbol, triggerPrice);
+        } else if (stopLossTriggerPrice || takeProfitTriggerPrice) {
+            if (stopLossTriggerPrice) {
+                request['triggerPrice'] = this.priceToPrecision (symbol, stopLossTriggerPrice);
+                request['triggerDirection'] = 2;
+            } else {
+                request['triggerPrice'] = this.priceToPrecision (symbol, takeProfitTriggerPrice);
+                request['triggerDirection'] = 1;
+            }
+            request['reduceOnly'] = true;
+        } else if (isStopLoss || isTakeProfit) {
+            if (isStopLoss) {
+                const stopLossPrice = this.safeNumber (isStopLoss, 'price');
+                params = this.omit (params, 'stopLoss');
+                request['stopLoss'] = this.priceToPrecision (symbol, stopLossPrice);
+            }
+            if (isTakeProfit) {
+                const takeProfitPrice = this.safeNumber (isTakeProfit, 'price');
+                params = this.omit (params, 'takeProfit');
+                request['takeProfit'] = this.priceToPrecision (symbol, takeProfitPrice);
+            }
         }
         if (market['spot']) {
             // only works for spot market
-            if (triggerPrice !== undefined || isStopLossOrder || isTakeProfitOrder) {
+            if (triggerPrice !== undefined || stopLossTriggerPrice !== undefined || takeProfitTriggerPrice !== undefined || isStopLoss || isTakeProfit) {
                 request['orderFilter'] = 'tpslOrder';
             }
         }
@@ -3476,7 +3492,7 @@ module.exports = class bybit extends Exchange {
             // mandatory field for options
             request['orderLinkId'] = this.uuid16 ();
         }
-        params = this.omit (params, [ 'stopPrice', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId' ]);
+        params = this.omit (params, [ 'stopPrice', 'timeInForce', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId' ]);
         const response = await this.privatePostV5OrderCreate (this.extend (request, params));
         //
         //     {
@@ -3718,20 +3734,36 @@ module.exports = class bybit extends Exchange {
         } else if (timeInForce === 'ioc') {
             request['timeInForce'] = 'ImmediateOrCancel';
         }
-        const triggerPrice = this.safeValue2 (params, 'stopPrice', 'triggerPrice');
-        const stopLossPrice = this.safeValue (params, 'stopLossPrice', triggerPrice);
-        const isStopLossOrder = stopLossPrice !== undefined;
-        const takeProfitPrice = this.safeValue (params, 'takeProfitPrice');
-        const isTakeProfitOrder = takeProfitPrice !== undefined;
-        if (isStopLossOrder || isTakeProfitOrder) {
-            const triggerAt = isStopLossOrder ? stopLossPrice : takeProfitPrice;
-            const preciseTriggerPrice = this.priceToPrecision (symbol, triggerAt);
+        const triggerPrice = this.safeNumber2 (params, 'triggerPrice', 'stopPrice');
+        const stopLossTriggerPrice = this.safeNumber (params, 'stopLossPrice', triggerPrice);
+        const takeProfitTriggerPrice = this.safeNumber (params, 'takeProfitPrice');
+        const isStopLoss = this.safeValue (params, 'stopLoss');
+        const isTakeProfit = this.safeValue (params, 'takeProfit');
+        if (triggerPrice) {
             const isBuy = side === 'buy';
-            // logical xor
-            const ascending = stopLossPrice ? !isBuy : isBuy;
+            const ascending = stopLossTriggerPrice ? !isBuy : isBuy;
             request['triggerDirection'] = ascending ? 2 : 1;
-            request['triggerBy'] = 'LastPrice';
-            request['triggerPrice'] = this.priceToPrecision (symbol, preciseTriggerPrice);
+            request['triggerPrice'] = this.priceToPrecision (symbol, triggerPrice);
+        } else if (stopLossTriggerPrice || takeProfitTriggerPrice) {
+            if (stopLossTriggerPrice) {
+                request['triggerPrice'] = this.priceToPrecision (symbol, stopLossTriggerPrice);
+                request['triggerDirection'] = 2;
+            } else {
+                request['triggerPrice'] = this.priceToPrecision (symbol, takeProfitTriggerPrice);
+                request['triggerDirection'] = 1;
+            }
+            request['reduceOnly'] = true;
+        } else if (isStopLoss || isTakeProfit) {
+            if (isStopLoss) {
+                const stopLossPrice = this.safeNumber (isStopLoss, 'price');
+                params = this.omit (params, 'stopLoss');
+                request['stopLoss'] = this.priceToPrecision (symbol, stopLossPrice);
+            }
+            if (isTakeProfit) {
+                const takeProfitPrice = this.safeNumber (isTakeProfit, 'price');
+                params = this.omit (params, 'takeProfit');
+                request['takeProfit'] = this.priceToPrecision (symbol, takeProfitPrice);
+            }
         }
         const clientOrderId = this.safeString (params, 'clientOrderId');
         if (clientOrderId !== undefined) {
@@ -3740,7 +3772,7 @@ module.exports = class bybit extends Exchange {
             // mandatory field for options
             request['orderLinkId'] = this.uuid16 ();
         }
-        params = this.omit (params, [ 'stopPrice', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId' ]);
+        params = this.omit (params, [ 'stopPrice', 'timeInForce', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId' ]);
         const response = await this.privatePostContractV3PrivateOrderCreate (this.extend (request, params));
         //
         //     {


Code review comments:
diff_hunk: @@ -3718,20 +3734,36 @@ module.exports = class bybit extends Exchange {
         } else if (timeInForce === 'ioc') {
             request['timeInForce'] = 'ImmediateOrCancel';
         }
-        const triggerPrice = this.safeValue2 (params, 'stopPrice', 'triggerPrice');
-        const stopLossPrice = this.safeValue (params, 'stopLossPrice', triggerPrice);
-        const isStopLossOrder = stopLossPrice !== undefined;
-        const takeProfitPrice = this.safeValue (params, 'takeProfitPrice');
-        const isTakeProfitOrder = takeProfitPrice !== undefined;
-        if (isStopLossOrder || isTakeProfitOrder) {
-            const triggerAt = isStopLossOrder ? stopLossPrice : takeProfitPrice;
-            const preciseTriggerPrice = this.priceToPrecision (symbol, triggerAt);
+        const triggerPrice = this.safeNumber2 (params, 'triggerPrice', 'stopPrice');
+        const stopLossTriggerPrice = this.safeNumber (params, 'stopLossPrice', triggerPrice);
+        const takeProfitTriggerPrice = this.safeNumber (params, 'takeProfitPrice');
+        const isStopLoss = this.safeValue (params, 'stopLoss');
+        const isTakeProfit = this.safeValue (params, 'takeProfit');
path: js/bybit.js
text: @user2:
@user1-krm in this case, I don't think we should do this because `takeProfit` is also the exchange-specific parameter. Besides that, you can't set anything other than the price, so there is no point in encapsulating it inside an object IMO.

In other words, instead of 
`'{"stopLoss":'{"price":1200}',"takeProfit":'{"price":1800}'}'` 

I think we should stick with
`'{"stopLoss":1200,"takeProfit":1800}'` 
















@author:
I agree I'll push some changes

@user2:
@user1-krm thanks