Question

Bitget CCXT partially close position

I have a strange issue trading on Bitget with CCXT.

I'm developing a python bot that automates some signals from a telegram group from my laptop (win 11, using VS), and the ting is that I have the code working as expected, Entry order executes, Take Profits works as expected as well... but then when I deploy the code (or just run it in VS) on the intel NUC that runs my docker containers, it keeps complaining about Unilateral side, and/or about side mismatch. **I have 'hard' entered long, shot, buy, sell, but the same happens.

If I test the code on a 3rd machine it works as expected (I copied the code directly off the NUC where I get the errors).

Below is the entry order:

Trying to execute order: (29, 5, 'LDOUSDT', 'short', 1.9725, 76.0, 15.0, 'Open', 'entry')
Order response: {'info': {'clientOid': '1191025251500830722', 'orderId': '1191025251484053505'}, 'id': '1191025251484053505', 'clientOrderId': '1191025251500830722', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'LDO/USDT:USDT', 'type': None, 'side': None, 'price': None, 'amount': None, 'cost': None, 'average': None, 'filled': None, 'remaining': None, 'timeInForce': None, 'postOnly': None, 'reduceOnly': None, 'stopPrice': None, 'triggerPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None, 'status': None, 'fee': None, 'trades': [], 'fees': []}

The entry works as expected on all 3 machines. I have the code then to try and take profit (to ensure that the function actually works), as follows:

Order: (31, 5, 'LDOUSDT', 'buy', 1.9338, 5.7, 15.0, 'Open', 'takeprofit')
{'code': '00000', 'msg': 'success', 'requestTime': '1719688487060', 'data': {'posMode': 'hedge_mode'}}
Take-profit order response: {'info': {'clientOid': '1191025332270542852', 'orderId': '1191025332270542849'}, 'id': '1191025332270542849', 'clientOrderId': '1191025332270542852', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'LDO/USDT:USDT', 'type': None, 'side': None, 'price': None, 'amount': None, 'cost': None, 'average': None, 'filled': None, 'remaining': None, 'timeInForce': None, 'postOnly': None, 'reduceOnly': None, 'stopPrice': None, 'triggerPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None, 'status': None, 'fee': None, 'trades': [], 'fees': []}

The above was executed on the laptop where I wrote the code... then trying to execute the exact same thing on the Intel NUC:

Trying to execute order: (29, 5, 'LDOUSDT', 'short', 1.9725, 76.0, 15.0, 'Open', 'entry')
Order response: {'info': {'clientOid': '1191029181727256581', 'orderId': '1191029181710479368'}, 'id': '1191029181710479368', 'clientOrderId': '1191029181727256581', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'LDO/USDT:USDT', 'type': None, 'side': None, 'price': None, 'amount': None, 'cost': None, 'average': None, 'filled': None, 'remaining': None, 'timeInForce': None, 'postOnly': None, 'reduceOnly': None, 'stopPrice': None, 'triggerPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None, 'status': None, 'fee': None, 'trades': [], 'fees': []}

So Entry worked as expected, below I double check to ensure we are in hedge mode:

Order: (31, 5, 'LDOUSDT', 'buy', 1.9338, 5.7, 15.0, 'Open', 'takeprofit')
{'code': '00000', 'msg': 'success', 'requestTime': '1719689424364', 'data': {'posMode': 'hedge_mode'}}

and then finally:

An error occurred: bitget {"code":"40774","msg":"The order type for unilateral position must also be the unilateral position type.","requestTime":1719689424722,"data":null}

Below is the same code that I tested on all 3 machines, Works on both my laptop and the Desktop, but not the NUC:

async def execute_order(userId, order):
    """Executes a futures order for a user based on the signal received, with specified leverage and margin mode."""
    user = await get_user_data(userId)
    print(f"User: {user}")
    print(f"Trying to execute order: {order}")
    if user:
        apiKey = decrypt(user[2])
        secret = decrypt(user[3])
        password = decrypt(user[4])
        bitget = ccxt.bitget({
            'apiKey': apiKey,
            'secret': secret,
            'password': password
        })
        

        try:
            symbol = order[2]
            size = order[5]
            price = order[4]
            margin_coin = 'USDT'  # Default margin coin
            margin_mode = 'cross'
            side = 'buy' if order[3] == 'long' else 'sell'
            hold_side = 'long' if order[3] == 'long' else 'short'
            pos_side = hold_side  # Ensuring posSide matches the holdSide
            
            bitget.options['defaultType'] = 'swap'
            # Set position mode
            bitget.set_position_mode('False', order[2])
            # Set leverage
            bitget.set_leverage(15, symbol, {
                'marginMode': margin_mode, 
                'marginCoin': margin_coin,
                'holdSide': hold_side
            })

            parms = {
                'holdSide': hold_side,
                'marginMode': margin_mode,
                'marginCoin': margin_coin,
                'posSide': pos_side,
                'tradeSide': 'open',
                'productType': 'USDT-FUTURES'
            }

            #print(f"Setting Leverage: {leverage_response}")

            # Place the futures order
            response = bitget.create_order(symbol, 'market', side, size, price, parms)
            
            print(f"Order response: {response}")
            #await telegram_notifier(f"Order executed for user {userId}: {response}")
        except Exception as e:
            error_message = f"An unexpected error occurred: {e}"
            print(error_message)
            #await telegram_notifier(f"Error executing futures order for user {userId}: {error_message}")
    return None



async def take_profit(userId, tp_order):
    """Sets a take-profit order for the given futures position."""
    user = await get_user_data(userId)
    if user:
        apiKey = decrypt(user[2])
        secret = decrypt(user[3])
        password = decrypt(user[4])
        bitget = ccxt.bitget({
            'apiKey': apiKey,
            'secret': secret,
            'password': password
        })
        
        bitget.options['defaultType'] = 'swap'
        # Set position mode
        hedge = bitget.set_position_mode('False', tp_order[2])
        print(hedge)

        try:
            symbol = tp_order[2]
            size = tp_order[5]
            price = tp_order[4]
            side = tp_order[3]
            hold_side = 'long' if side == 'sell' else 'short'  # Take profit on the opposite side
            
            # Order parameters
            type = 'market'  # or 'limit'
            amount = size  # Amount you want to close, adjust as necessary
            price = None  # Relevant for limit orders
            
            # Additional parameters specific to Bitget
            params = {
                'tdMode': 'cross',  # or 'cross'
                'reduceOnly': True,  # This ensures that the order can only reduce a position  

            }

            # Create the order
            order = bitget.create_order(symbol, type, side, amount, price, params)
            print(f"Take-profit order response: {order}")
            
            if tp_order[8] == 'takeprofit-msl':
                await update_stoploss_order_price(tp_order[1])
                


            return None

        except ccxt.BaseError as e:
            error_message = f"An error occurred: {e}"
            print(error_message)
            #await telegram_notifier(f"Error setting take-profit order for user {userId}: {error_message}")
    return None

The most common suggestion I found is to use One Way Mode, but I really don't want to, especially knowing that it works on 2 computers but not the NUC. Source: Close position Bitget Futures using ccxt and https://tickerly.net/errors/

The other thing I have tried is just hard coding the holdSide, tradeSide, etc. as suggested by bitget's API documentation, but then it just complains about side missmatch, regardless of value, unless I change Long/Short into Buy/Sell, where it just states value is invalid.

async def take_profit(userId, tp_order):
    """Sets a take-profit order for the given futures position."""
    user = await get_user_data(userId)
    if user:
        apiKey = decrypt(user[2])
        secret = decrypt(user[3])
        password = decrypt(user[4])
        bitget = ccxt.bitget({
            'apiKey': apiKey,
            'secret': secret,
            'password': password
        })
        
        bitget.options['defaultType'] = 'swap'
        # Set position mode
        hedge = bitget.set_position_mode('False', tp_order[2])
        print(hedge)

        try:
            symbol = tp_order[2]
            size = tp_order[5]
            price = tp_order[4]
            side = 'long'#tp_order[3]
            hold_side = 'long' if side == 'sell' else 'short'  # Take profit on the opposite side
            
            # Order parameters
            type = 'market'  # or 'limit'
            amount = size  # Amount you want to close, adjust as necessary
            price = None  # Relevant for limit orders
            
            # Additional parameters specific to Bitget
            params = {
                'tdMode': 'cross',  # or 'cross'
                'reduceOnly': True,  # This ensures that the order can only reduce a position  

            }

            # Create the order
            order = bitget.create_order(symbol, type, side, amount, price, params)
            print(f"Take-profit order response: {order}")
            
            if tp_order[8] == 'takeprofit-msl':
                await update_stoploss_order_price(tp_order[1])
                


            return None

        except ccxt.BaseError as e:
            error_message = f"An error occurred: {e}"
            print(error_message)
            #await telegram_notifier(f"Error setting take-profit order for user {userId}: {error_message}")
    return None

returns the following:

Trying to execute order: (29, 5, 'LDOUSDT', 'short', 1.9725, 76.0, 15.0, 'Open', 'entry')
Order response: {'info': {'clientOid': '1191031910600159232', 'orderId': '1191031910583382017'}, 'id': '1191031910583382017', 'clientOrderId': '1191031910600159232', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'lastUpdateTimestamp': None, 'symbol': 'LDO/USDT:USDT', 'type': None, 'side': None, 'price': None, 'amount': None, 'cost': None, 'average': None, 'filled': None, 'remaining': None, 'timeInForce': None, 'postOnly': None, 'reduceOnly': None, 'stopPrice': None, 'triggerPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None, 'status': None, 'fee': None, 'trades': [], 'fees': []}
Order: (31, 5, 'LDOUSDT', 'buy', 1.9338, 5.7, 15.0, 'Open', 'takeprofit')
{'code': '00000', 'msg': 'success', 'requestTime': '1719690075905', 'data': {'posMode': 'hedge_mode'}}
An error occurred: bitget {"code":"400172","msg":"side mismatch","requestTime":1719690076235,"data":null}

The above should be a mismatch, as both are short. I have tried entering Long and all other possible values, but the same issue.

As mentioned before I checked Bitgets API documentation and tried the additional parameters as they suggest but still nothing. API docs here: https://docs.ccxt.com/#/exchanges/bitget?id=createorder

I guess another exception was this:

enter image description here

But when I tried using it, it stated that there is no open position.

How I can further troubleshoot this?

 2  62  2
1 Jan 1970

Solution

 0

This appear to be a bug in the CCXT library when trading with unilateral positions, the tradeSide parameter is required reference here: https://github.com/ccxt/ccxt/issues/19140#issuecomment-1882527221

as this is my take profit code, I have tradeSide static on 'close'

I got my code working as follows:

async def take_profit(userId, tp_order):
    """Sets a take-profit order for the given futures position."""
    user = await get_user_data(userId)
    if user:
        apiKey = decrypt(user[2])
        secret = decrypt(user[3])
        password = decrypt(user[4])
        bitget = ccxt.bitget({
            'apiKey': apiKey,
            'secret': secret,
            'password': password
        })
        
        bitget.options['defaultType'] = 'swap'
        # Set position mode
        bitget.set_position_mode('False', tp_order[2])

        try:
            symbol = tp_order[2]
            size = tp_order[5]
            price = tp_order[4]
            side = tp_order[3]
            
            # Order parameters
            type = 'market'  # or 'limit'
            amount = size  # Amount you want to close, adjust as necessary
            price = None  # Relevant for limit orders
            
            # Additional parameters specific to Bitget
            params = {
                'tdMode': 'cross',  # or 'cross'
                'reduceOnly': True,  # This ensures that the order can only reduce a position
                'tradeSide': 'close',
                'productType': 'USDT-FUTURES'
            }

            # Create the order
            order = bitget.create_order(symbol, type, side, amount, price, params)
            print(f"Take-profit order response: {order}")
            
            if tp_order[8] == 'takeprofit-msl':
                await update_stoploss_order_price(tp_order[1])
               
            return None

        except ccxt.BaseError as e:
            error_message = f"An error occurred: {e}"
            print(error_message)
            #await telegram_notifier(f"Error setting take-profit order for user {userId}: {error_message}")
    return None

Please note that side should have a value of 'buy' for longs and 'sell' for shorts, I am not 100% sure why just passing long or short doesnt work and results in a "Side mismatch error"

2024-07-20
Smokey Van Den Berg