Repository: ccxt/ccxt
Pull request: bybit: update parsePosition
fix: ccxt/ccxt#21941

```BASH
$ n bybit fetchPositions '["BTC/USD:BTC"]' --test

     symbol |     timestamp |                 datetime | initialMargin | maintenanceMargin | notional | leverage | contracts | contractSize | markPrice | collateral | marginMode | stopLossPrice | takeProfitPrice
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BTC/USD:BTC | 1711524714893 | 2024-03-27T07:31:54.893Z |             0 |                 0 |        0 |       10 |         0 |            1 |  69918.69 |          0 |   isolated |             0 |               0

$ n bybit fetchPositions '["ETH/USD:ETH"]' --test

     symbol |     timestamp |                 datetime | initialMargin | initialMarginPercentage | maintenanceMargin | maintenanceMarginPercentage |    entryPrice |   notional | leverage | unrealizedPnl | contracts | contractSize | marginRatio | markPrice | collateral | marginMode |  side | stopLossPrice | takeProfitPrice
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ETH/USD:ETH | 1711524987468 | 2024-03-27T07:36:27.468Z |     0.0000028 |    0.010006432706740048 |              5e-8 |        0.000178686298334643 | 3573.72596669 | 0.00027982 |       10 |        2.3e-7 |         1 |            1 |      0.0017 |   3570.69 | 0.00002813 |      cross | short |             0 |               0
```

> Need to test classic account in bybit.

Problem statement:
Bybit - marginMode key not defined in position response.
### Operating System

_No response_

### Programming Languages

Python

### CCXT Version

4.2.82

### Description

Hello,

I updated CCXT a couple of days ago and found out the marginMode key is not set anymore in the response from fetch_positions calls in BYBIT. It used to be, and the info block still contains the information from the exchange inside the 'tradeMode' key.

```
[{'collateral': 7.0507895,
  'contractSize': 1.0,
  'contracts': 0.001,
  'datetime': '2024-03-26T23:37:24.732Z',
  'entryPrice': 70160.6,
  'id': None,
  'info': {'adlRankIndicator': '2',      
           'autoAddMargin': '0',
           'avgPrice': '70160.6',
           'bustPrice': '63144.60',
           'createdTime': '1690514722705',
           'cumRealisedPnl': '-38.56271122',
           'curRealisedPnl': '-0.03858833',
           'isReduceOnly': False,
           'leverage': '10',
           'leverageSysUpdatedTime': '',
           'liqPrice': '63495.40',
           'markPrice': '70152.80',
           'mmrSysUpdatedTime': '',
           'positionBalance': '7.0507895',
           'positionIM': '0.701606',
           'positionIdx': '0',
           'positionMM': '0.315723',
           'positionStatus': 'Normal',
           'positionValue': '70.1606',
           'riskId': '1',
           'riskLimitValue': '2000000',
           'seq': '9079756564',
           'sessionAvgPrice': '',
           'side': 'Buy',
           'size': '0.001',
           'stopLoss': '0.00',
           'symbol': 'BTCUSDT',
           'takeProfit': '0.00',
           'tpslMode': 'Full',
           'tradeMode': '1',
           'trailingStop': '0.00',
           'unrealisedPnl': '-0.0078',
           'updatedTime': '1711496244732'},
  'initialMargin': 7.01606,
  'initialMarginPercentage': 0.1,
  'lastPrice': None,
  'lastUpdateTimestamp': None,
  'leverage': 10.0,
  'liquidationPrice': 63495.4,
  'maintenanceMargin': 0.3508,
  'maintenanceMarginPercentage': 0.0049999572409586,
  'marginMode': None,
  'marginRatio': 0.0497,
  'markPrice': 70152.8,
  'notional': 70.1606,
  'percentage': None,
  'side': 'long',
  'stopLossPrice': 0.0,
  'symbol': 'BTC/USDT:USDT',
  'takeProfitPrice': 0.0,
  'timestamp': 1711496244732,
  'unrealizedPnl': -0.0078}]
```

Just letting you know.
Thanks.

Hints from issue discussion:
@germangar the value was set to undefined because of this issue ccxt/ccxt#21079.  I try the isolated / cross position, the tradeMode is 0. Which order did you create? Could you provide example?
I did nothing special. This is the vervose output from my order:

```
fetch Request: bybit POST https://api-testnet.bybit.com/v5/order/create RequestHeaders: {'Content-Type': 'application/json', 'X-BAPI-API-KEY': 'rEGVMj9eZYZWMnqd21', 'X-BAPI-TIMESTAMP': '1711517785329', 'X-BAPI-RECV-WINDOW': '5000', 'X-BAPI-SIGN': '2cfce54ce430777cc7c59cc883f613be6a4a228377943da83540f6c3321e3f46', 'Referer': 'CCXT', 'User-Agent': 'python-requests/2.31.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'} RequestBody: {"symbol":"BTCUSDT","side":"Buy","orderType":"Market","category":"linear","qty":"0.001"}
```

And here's the position response resulting after the order was filled:

```
fetch Request: bybit GET https://api-testnet.bybit.com/v5/position/list?settleCoin=USDT&category=linear&settle=USDT RequestHeaders: {'Content-Type': 'application/json', 'X-BAPI-API-KEY': 'rEGVMj9eZYZWMnqd21', 'X-BAPI-TIMESTAMP': '1711517793561', 'X-BAPI-RECV-WINDOW': '5000', 'X-BAPI-SIGN': 'bfa9850aa80c0f78e8336e58e8372132c1582cdb197a97decfa9d274ba31e674', 'User-Agent': 'python-requests/2.31.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'} RequestBody: None

fetch Response: bybit GET https://api-testnet.bybit.com/v5/position/list?settleCoin=USDT&category=linear&settle=USDT 200 ResponseHeaders: {'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '852', 'Connection': 'keep-alive', 'Date': 'Wed, 27 Mar 2024 05:36:33 GMT', 'X-Bapi-Limit': '10', 'X-Bapi-Limit-Status': '9', 'X-Bapi-Limit-Reset-Timestamp': '1711517793747', 'Ret_code': '0', 'Traceid': '209ef2f0b0f6294139b2251d0d15a0ce', 'Timenow': '1711517793751', 'Server': 'Openresty', 'x-cld-src': 'Loc-A', 'X-Cache': 'Miss from cloudfront', 'Via': '1.1 0fb7843fe80b8fea4dc0d4946b492a40.cloudfront.net (CloudFront)', 'X-Amz-Cf-Pop': 'MAD56-P1', 'X-Amz-Cf-Id': 'g-hB7S6SKdFDwixCZtIm9Ku3F31sFBZZviSh5cKV035-g8dq2PYQag=='} ResponseBody: {"retCode":0,"retMsg":"OK","result":{"list":[{"positionIdx":0,"riskId":1,"riskLimitValue":"2000000","symbol":"BTCUSDT","side":"Buy","size":"0.001","avgPrice":"70493.3","positionValue":"70.4933","tradeMode":1,"positionStatus":"Normal","autoAddMargin":0,"adlRankIndicator":2,"leverage":"10","positionBalance":"7.08422419","markPrice":"70503.98","liqPrice":"63796.50","bustPrice":"63444.00","positionMM":"0.31722","positionIM":"0.704933","tpslMode":"Full","takeProfit":"0.00","stopLoss":"0.00","trailingStop":"0.00","unrealisedPnl":"0.01068","cumRealisedPnl":"-38.68764469","curRealisedPnl":"-0.03877132","seq":9080328670,"isReduceOnly":false,"mmrSysUpdatedTime":"","leverageSysUpdatedTime":"","sessionAvgPrice":"","createdTime":"1690514722705","updatedTime":"1711517785858"}],"nextPageCursor":"","category":"linear"},"retExtInfo":{},"time":1711517793751}   
```

```
[{'collateral': 7.08422419,
  'contractSize': 1.0,
  'contracts': 0.001,
  'datetime': '2024-03-27T05:36:25.858Z',
  'entryPrice': 70493.3,
  'id': None,
  'info': {'adlRankIndicator': '2',
           'autoAddMargin': '0',
           'avgPrice': '70493.3',
           'bustPrice': '63444.00',
           'createdTime': '1690514722705',
           'cumRealisedPnl': '-38.68764469',
           'curRealisedPnl': '-0.03877132',
           'isReduceOnly': False,
           'leverage': '10',
           'leverageSysUpdatedTime': '',
           'liqPrice': '63796.50',
           'markPrice': '70503.98',
           'mmrSysUpdatedTime': '',
           'positionBalance': '7.08422419',
           'positionIM': '0.704933',
           'positionIdx': '0',
           'positionMM': '0.31722',
           'positionStatus': 'Normal',
           'positionValue': '70.4933',
           'riskId': '1',
           'riskLimitValue': '2000000',
           'seq': '9080328670',
           'sessionAvgPrice': '',
           'side': 'Buy',
           'size': '0.001',
           'stopLoss': '0.00',
           'symbol': 'BTCUSDT',
           'takeProfit': '0.00',
           'tpslMode': 'Full',
           'tradeMode': '1',
           'trailingStop': '0.00',
           'unrealisedPnl': '0.01068',
           'updatedTime': '1711517785858'},
  'initialMargin': 7.04933,
  'initialMarginPercentage': 0.1,
  'lastPrice': None,
  'lastUpdateTimestamp': None,
  'leverage': 10.0,
  'liquidationPrice': 63796.5,
  'maintenanceMargin': 0.3525,
  'maintenanceMarginPercentage': 0.00500047522246795,
  'marginMode': None,
  'marginRatio': 0.0497,
  'markPrice': 70503.98,
  'notional': 70.4933,
  'percentage': None,
  'side': 'long',
  'stopLossPrice': 0.0,
  'symbol': 'BTC/USDT:USDT',
  'takeProfitPrice': 0.0,
  'timestamp': 1711517785858,
  'unrealizedPnl': 0.01068}]
```
Margin mode was set before the order with this code. Nothing special either (other than adding the leverage as param, but that's not special)

```
            params = {}
            # coinex and bybit expect the leverage as part of the marginMode call
            if( self.exchange.id == 'coinex' or self.exchange.id == 'bybit' ):
                params['leverage'] = leverage
            elif( self.exchange.id == 'okx' ):
                params['lever'] = leverage

            try:
                response = self.exchange.set_margin_mode( self.MARGIN_MODE, symbol, params )
```
@germangar do you use unified account?

BTW, I think the tradeMode would work when it's classic or UTA (inverse), see: https://bybit-exchange.github.io/docs/v5/position. I'll update this asap.
> @germangar do you use unified account?
> 
> BTW, I think the tradeMode would work when it's classic or UTA (inverse), see: https://bybit-exchange.github.io/docs/v5/position. I'll update this asap.

That's rigth. I don't use unified account. I stuck to the classic one.

I don't truly have a problem the way it is. I changed it to check the tradeMode key from the info block and it's working fine for me, but I thought I should inform you about it.

Commit message: Merge branch 'master' of github.com:ccxt/ccxt into update-bybit-parse-position

Patch to review:
diff --git a/ts/src/bybit.ts b/ts/src/bybit.ts
index 7c2e2c0aef6ee..f7c453a2268d6 100644
--- a/ts/src/bybit.ts
+++ b/ts/src/bybit.ts
@@ -6315,6 +6315,12 @@ export default class bybit extends Exchange {
         if (timestamp === undefined) {
             timestamp = this.safeIntegerN (position, [ 'updatedTime', 'updatedAt' ]);
         }
+        const tradeMode = this.safeInteger (position, 'tradeMode', 0);
+        let marginMode = undefined;
+        if ((!this.options['enableUnifiedAccount']) || (this.options['enableUnifiedAccount'] && market['inverse'])) {
+            // tradeMode would work for classic and UTA(inverse)
+            marginMode = tradeMode ? 'isolated' : 'cross';
+        }
         let collateralString = this.safeString (position, 'positionBalance');
         const entryPrice = this.omitZero (this.safeString2 (position, 'entryPrice', 'avgPrice'));
         const liquidationPrice = this.omitZero (this.safeString (position, 'liqPrice'));
@@ -6376,7 +6382,7 @@ export default class bybit extends Exchange {
             'markPrice': this.safeNumber (position, 'markPrice'),
             'lastPrice': undefined,
             'collateral': this.parseNumber (collateralString),
-            'marginMode': undefined,
+            'marginMode': marginMode,
             'side': side,
             'percentage': undefined,
             'stopLossPrice': this.safeNumber2 (position, 'stop_loss', 'stopLoss'),


Code review comments:
diff_hunk: @@ -6315,6 +6315,12 @@ export default class bybit extends Exchange {
         if (timestamp === undefined) {
             timestamp = this.safeIntegerN (position, [ 'updatedTime', 'updatedAt' ]);
         }
+        const tradeMode = this.safeInteger (position, 'tradeMode', 0);
+        let marginMode = undefined;
+        if ((!this.options['enableUnifiedAccount']) || (this.options['enableUnifiedAccount'] && market['inverse'])) {
+            // tradeMode would work for classic and UTA(inverse)
+            marginMode = tradeMode ? 'isolated' : 'cross';
path: ts/src/bybit.ts
text: @user1:
let's try to be explicit on our comparisons plss