Repository: ccxt/ccxt
Pull request: kucoinfutures stopPrice order bug fix
Previously trigger orders were being triggered immediately, because the parameter `stop` was set to the opposite value that it should have been set to

fixes: #16442

----------------------------------

## Testing

```
% kucoinfutures createOrder BTC/USDT:USDT market buy 1 undefined '{"stopPrice": "22951"}'
Debugger attached.
2023-01-24T17:02:47.724Z
Node.js: v18.4.0
CCXT v2.6.70
kucoinfutures.createOrder (BTC/USDT:USDT, market, buy, 1, , [object Object])
2023-01-24T17:02:50.173Z iteration 0 passed in 554 ms

{
  id: '63d00f3a84120200014adfa1',
  clientOrderId: undefined,
  timestamp: undefined,
  datetime: undefined,
  lastTradeTimestamp: undefined,
  symbol: undefined,
  type: undefined,
  side: undefined,
  price: undefined,
  amount: undefined,
  cost: undefined,
  average: undefined,
  filled: undefined,
  remaining: undefined,
  status: undefined,
  fee: undefined,
  trades: undefined,
  timeInForce: undefined,
  postOnly: undefined,
  stopPrice: undefined,
  triggerPrice: undefined,
  info: { code: '200000', data: { orderId: '63d00f3a84120200014adfa1' } }
}
2023-01-24T17:02:50.173Z iteration 1 passed in 554 ms

Waiting for the debugger to disconnect...
sam@Sams-Mac-mini ccxt % kucoinfutures fetchOrder 63d00f3a84120200014adfa1
Debugger attached.
2023-01-24T17:03:10.207Z
Node.js: v18.4.0
CCXT v2.6.70
kucoinfutures.fetchOrder (63d00f3a84120200014adfa1)
2023-01-24T17:03:12.606Z iteration 0 passed in 789 ms

{
  id: '63d00f3a84120200014adfa1',
  clientOrderId: '4eff52e1-be4c-4dbf-8218-a77574d9bda0',
  symbol: 'BTC/USDT:USDT',
  type: 'market',
  timeInForce: 'GTC',
  postOnly: false,
  side: 'buy',
  amount: 1,
  price: undefined,
  stopPrice: 22951,
  triggerPrice: 22951,
  cost: 0,
  filled: 0,
  remaining: 1,
  timestamp: 1674579770000,
  datetime: '2023-01-24T17:02:50.000Z',
  fee: { currency: undefined, cost: undefined },
  status: 'open',
  info: {
    id: '63d00f3a84120200014adfa1',
    symbol: 'XBTUSDTM',
    type: 'market',
    side: 'buy',
    price: null,
    size: 1,
    value: '0',
    dealValue: '0',
    dealSize: 0,
    stp: '',
    stop: 'up',
    stopPriceType: 'MP',
    stopTriggered: false,
    stopPrice: '22951',
    timeInForce: 'GTC',
    postOnly: false,
    hidden: false,
    iceberg: false,
    leverage: '1',
    forceHold: false,
    closeOrder: false,
    visibleSize: null,
    clientOid: '4eff52e1-be4c-4dbf-8218-a77574d9bda0',
    remark: null,
    tags: 'ccxtfutures',
    isActive: true,
    cancelExist: false,
    createdAt: 1674579770000,
    updatedAt: 1674579770000,
    endAt: null,
    orderTime: null,
    settleCurrency: 'USDT',
    status: 'open',
    filledValue: '0',
    filledSize: 0,
    reduceOnly: false
  },
  lastTradeTimestamp: undefined,
  average: undefined,
  trades: [],
  fees: [ { currency: undefined, cost: undefined } ],
  reduceOnly: undefined
}
2023-01-24T17:03:12.606Z iteration 1 passed in 789 ms
```

Problem statement:
Kucoin Futures - 'stopPrice'/'triggerPrice' argument not working
### Operating System

Windows 10

### Programming Languages

python

### CCXT Version

2.5.89

### Description

Currently I am unable to place a stop market order, as the code below when either using "stopPrice" or "triggerPrice" it immediately triggers the order on Kucoin. 

Please let me know in case I am overlooking something or whether I need to add other arguments to params. As currently below code will just execute a market order. 

### Code

```
import ccxt

public = ""
private = ""
password = ""

client = ccxt.kucoinfutures(
    {'apiKey': public, 'secret': private, 'password': password})

ticker = "BTC/USDT:USDT"
side = "buy"
quantity = 1
price = 18000

order = client.create_order(symbol=ticker, type="market",
                            side=side, amount=quantity,
                            price=None, params={'stopPrice': price})
print(order)

```

When printing the order it also gives the message "Cannot close a position of size 0. " in case the argument "closeOrder": True is provided in params.

Hints from issue discussion:

Commit message: kucoinfutures stopPrice order bug fix, fixes: #16442

Patch to review:
diff --git a/js/kucoinfutures.js b/js/kucoinfutures.js
index cdb0bcbd60e0a..6f0ea65009609 100644
--- a/js/kucoinfutures.js
+++ b/js/kucoinfutures.js
@@ -1030,18 +1030,18 @@ module.exports = class kucoinfutures extends kucoin {
          * @param {float} amount the amount of currency to trade
          * @param {float} price *ignored in "market" orders* the price at which the order is to be fullfilled at in units of the quote currency
          * @param {object} params  Extra parameters specific to the exchange API endpoint
-         * @param {float} params.leverage Leverage size of the order
-         * @param {float} params.stopPrice The price a stop order is triggered at
-         * @param {float} params.triggerPrice price to trigger stop orders
+         * @param {float} params.triggerPrice The price a trigger order is triggered at
          * @param {float} params.stopLossPrice price to trigger stop-loss orders
          * @param {float} params.takeProfitPrice price to trigger take-profit orders
-         * @param {string} params.stop 'up' or 'down', the direction the stopPrice is triggered from, requires stopPrice
-         * @param {string} params.stopPriceType  TP, IP or MP, defaults to MP: Mark Price
          * @param {bool} params.reduceOnly A mark to reduce the position size only. Set to false by default. Need to set the position size when reduceOnly is true.
          * @param {string} params.timeInForce GTC, GTT, IOC, or FOK, default is GTC, limit orders only
          * @param {string} params.postOnly Post only flag, invalid when timeInForce is IOC or FOK
+         * ----------------- Exchange Specific Parameters -----------------
+         * @param {float} params.leverage Leverage size of the order
          * @param {string} params.clientOid client order id, defaults to uuid if not passed
          * @param {string} params.remark remark for the order, length cannot exceed 100 utf8 characters
+         * @param {string} params.stop 'up' or 'down', the direction the stopPrice is triggered from, requires stopPrice. down: Triggers when the price reaches or goes below the stopPrice. up: Triggers when the price reaches or goes above the stopPrice.
+         * @param {string} params.stopPriceType  TP, IP or MP, defaults to MP: Mark Price
          * @param {bool} params.closeOrder set to true to close position
          * @param {bool} params.forceHold A mark to forcely hold the funds for an order, even though it's an order to reduce the position size. This helps the order stay on the order book and not get canceled when the position size changes. Set to false by default.
          * @returns {object} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
@@ -1068,28 +1068,20 @@ module.exports = class kucoinfutures extends kucoin {
         const takeProfitPrice = this.safeValue (params, 'takeProfitPrice');
         const isStopLoss = stopLossPrice !== undefined;
         const isTakeProfit = takeProfitPrice !== undefined;
-        const stop = this.safeString (params, 'stop');
-        const stopPriceType = this.safeString (params, 'stopPriceType', 'MP');
         if (stopPrice) {
-            if (stop === undefined) {
-                request['stop'] = (side === 'buy') ? 'down' : 'up';
-            }
+            request['stop'] = (side === 'buy') ? 'up' : 'down';
             request['stopPrice'] = this.priceToPrecision (symbol, stopPrice);
-            request['stopPriceType'] = stopPriceType;
+            request['stopPriceType'] = 'MP';
         } else if (isStopLoss || isTakeProfit) {
             if (isStopLoss) {
-                if (stop === undefined) {
-                    request['stop'] = (side === 'buy') ? 'up' : 'down';
-                }
+                request['stop'] = (side === 'buy') ? 'down' : 'up';
                 request['stopPrice'] = this.priceToPrecision (symbol, stopLossPrice);
             } else {
-                if (stop === undefined) {
-                    request['stop'] = (side === 'buy') ? 'down' : 'up';
-                }
+                request['stop'] = (side === 'buy') ? 'up' : 'down';
                 request['stopPrice'] = this.priceToPrecision (symbol, takeProfitPrice);
             }
             request['reduceOnly'] = true;
-            request['stopPriceType'] = stopPriceType;
+            request['stopPriceType'] = 'MP';
         }
         const uppercaseType = type.toUpperCase ();
         const timeInForce = this.safeStringUpper (params, 'timeInForce');
@@ -1115,7 +1107,7 @@ module.exports = class kucoinfutures extends kucoin {
                 throw new ArgumentsRequired (this.id + ' createOrder() requires a visibleSize parameter for iceberg orders');
             }
         }
-        params = this.omit (params, [ 'timeInForce', 'stop', 'stopPrice', 'stopPriceType', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ]); // Time in force only valid for limit orders, exchange error when gtc for market orders
+        params = this.omit (params, [ 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ]); // Time in force only valid for limit orders, exchange error when gtc for market orders
         const response = await this.futuresPrivatePostOrders (this.extend (request, params));
         //
         //    {


Code review comments:
diff_hunk: @@ -1068,28 +1068,20 @@ module.exports = class kucoinfutures extends kucoin {
         const takeProfitPrice = this.safeValue (params, 'takeProfitPrice');
         const isStopLoss = stopLossPrice !== undefined;
         const isTakeProfit = takeProfitPrice !== undefined;
-        const stop = this.safeString (params, 'stop');
-        const stopPriceType = this.safeString (params, 'stopPriceType', 'MP');
         if (stopPrice) {
-            if (stop === undefined) {
-                request['stop'] = (side === 'buy') ? 'down' : 'up';
-            }
+            request['stop'] = (side === 'buy') ? 'up' : 'down';
             request['stopPrice'] = this.priceToPrecision (symbol, stopPrice);
-            request['stopPriceType'] = stopPriceType;
+            request['stopPriceType'] = 'MP';
         } else if (isStopLoss || isTakeProfit) {
             if (isStopLoss) {
-                if (stop === undefined) {
-                    request['stop'] = (side === 'buy') ? 'up' : 'down';
-                }
+                request['stop'] = (side === 'buy') ? 'down' : 'up';
path: js/kucoinfutures.js
text: @user1:
If I'm creating a stop-loss order for a long position wouldn't the `stopPrice` be triggered from the price going `down` to open a `sell` order at the `price`, and vice versa for takeProfit and short positions

@author:
> If I'm creating a stop-loss order for a long position wouldn't the `stopPrice` be triggered from the price going `down` to open a `sell` order at the `price`, and vice versa for takeProfit and short positions

for a long **position** your stop order will be a sell, because you're exiting

@user1:
> > If I'm creating a stop-loss order for a long position wouldn't the `stopPrice` be triggered from the price going `down` to open a `sell` order at the `price`, and vice versa for takeProfit and short positions
> 
> for a long **position** your stop order will be a sell, because you're exiting

Right so we should probably change the stopLoss and takeProfit stop requests back then

@author:
> > > If I'm creating a stop-loss order for a long position wouldn't the `stopPrice` be triggered from the price going `down` to open a `sell` order at the `price`, and vice versa for takeProfit and short positions
> > 
> > 
> > for a long **position** your stop order will be a sell, because you're exiting
> 
> Right so we should probably change the stopLoss and takeProfit stop requests back then

on those ones, the side refers to the direction of the original order, so the buy/sell side of the stopLoss is opposite of the side of the order

@user1:
@author 
I think the `stop` parameter being `up` or `down` is referring to the stop-direction not the direction of the current position. The kucoin api doesn't have true stop-losses or take-profits yet so these are order type 2 from the stopLoss schema #13822.

I tested your approach alongside the kucoin futures website and these are the responses I received from the orders:
### set side to sell:
`kucoinfutures createOrder BTC/USDT:USDT limit sell 1 13000 '{"stopLossPrice":"14000"}'`
`kucoinfutures createOrder BTC/USDT:USDT limit sell 1 14000 '{"stopLossPrice":"13000"}'`
result:
`Sell 1 BTC Perpetual/USDT contract(s) at 13000 . The order has been canceled. Caused by: Order price cannot be lower than 21465.611`

### set side to buy:
`kucoinfutures createOrder BTC/USDT:USDT limit buy 1 13000 '{"stopLossPrice":"14000"}'`
`kucoinfutures createOrder BTC/USDT:USDT limit buy 1 14000 '{"stopLossPrice":"13000"}'`
result:
`creates a buy order`

### current approach
`kucoinfutures createOrder BTC/USDT:USDT limit sell 1 13000 '{"stopLossPrice":"14000"}'`
result:
`creates an emulated stop-loss sell order with triggerPrice 14000 and order price 13000`

@author:
The `stopLossPrice` can't exist without another order though, so when your doing 

`kucoinfutures createOrder BTC/USDT:USDT limit sell 1 13000 '{"stopLossPrice":"14000"}'`

I thought that you would be creating a limit order to sell 1 BTC/USDT contract 13000, and then if this limit order is hit, the position will have a stop loss price of 14000

@user2:
> I thought that you would be creating a limit order to sell 1 BTC/USDT contract 13000, and then if this limit order is hit, the position will have a stop loss price of 14000


@author currently we're unifying stop order type 2, where the order as whole is the sl/tk order and can be used to protect previously opened positions.
 I think you're describing stop orders type 3, where you open a position and immediately attach a takeprofit, stopLoss or both to it 

@user1:
> The `stopLossPrice` can't exist without another order though, so when your doing
> 
> `kucoinfutures createOrder BTC/USDT:USDT limit sell 1 13000 '{"stopLossPrice":"14000"}'`
> 
> I thought that you would be creating a limit order to sell 1 BTC/USDT contract 13000, and then if this limit order is hit, the position will have a stop loss price of 14000

I thought that would be the best way to handle stop-losses initially as well but it's actually kind of nice doing it this way and sort of emulating stop-losses using stop orders because a lot of exchange API's don't support order type 3 yet