Repository: ccxt/ccxt
Pull request: huobi - fix balance
fix #16543

Problem statement:
Huobi fetchBalance of linear error 4002
### Operating System

Windows 10

### Programming Languages

JS

### CCXT Version

2.2.62

### Description

I am trying to fetch Huobi futures account balance and catch error

`
ExchangeError: Huobi {"status":"error","err_code":4002,"err_msg":"The merged cross and isolated margin account for USDT-M futures is unavailable. Please complete the query with linear-swap-api/v3/unified_account_info","ts":1674149112391}
`

Would someone be able to help me?


### Code

```
const balance = await huobiExchange.fetchTotalBalance({
		"defaultType" : "swap",
		"defaultSubType": "linear"
});

```

Hints from issue discussion:
Hello @Mnemon1k, can you please share a verbose (request+response) complete log? 
This?

```
/Users/alex/dev/bot/node_modules/ccxt/js/huobi.js:5742
                throw new ExchangeError (feedback);
                      ^
ExchangeError: huobi {"status":"error","err_code":4002,"err_msg":"The merged cross and isolated margin account for USDT-M futures is unavailable.Please complete the query with linear-swap-api/v3/unified_account_info","ts":1674398486181}
    at huobi.handleErrors (/Users/alex/dev/bot/node_modules/ccxt/js/huobi.js:5742:23)
    at /Users/alex/dev/bot/node_modules/ccxt/js/base/Exchange.js:603:51
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async timeout (/Users/alex/dev/bot/node_modules/ccxt/js/base/functions/time.js:181:20)
    at async huobi.fetch2 (/Users/alex/dev/bot/node_modules/ccxt/js/base/Exchange.js:2023:16)
    at async huobi.request (/Users/alex/dev/bot/node_modules/ccxt/js/base/Exchange.js:2027:16)
    at async huobi.fetchBalance (/Users/alex/dev/bot/node_modules/ccxt/js/huobi.js:2979:26)
    at async huobi.fetchPartialBalance (/Users/alex/dev/bot/node_modules/ccxt/js/base/Exchange.js:2233:25)
    at async huobi.fetchTotalBalance (/Users/alex/dev/bot/node_modules/ccxt/js/base/Exchange.js:2246:16)
    at async file:///Users/alex/dev/bot/src/CollapseBot.ts:88:22 {
  constructor: [class ExchangeError extends BaseError]

```
@carlosmiei this doesn't seem to be related to newly added endpoints. `unified_account_info` is nowhere mentioned or documented in API docs. 
I think it will need more research. I will need a day  or so.
@Mnemon1k please enable verbose mode and paste your verbose output:

- https://github.com/ccxt/ccxt/wiki/faq#what-is-required-to-get-help
- https://github.com/ccxt/ccxt/wiki/Manual#troubleshooting

```JavaScript
await huobiExchange.loadMarkets()

huobiExchange.verbose = true  // <<<<<<<<<<<<<<<<< after loading the markets

const balance = await huobiExchange.fetchTotalBalance({
		"defaultType" : "swap",
		"defaultSubType": "linear"
});
```
Is it right info?
```

fetch Request:
 huobi POST https://api.hbdm.com/linear-swap-api/v1/swap_cross_account_info?AccessKeyId=1qdmpe4rty-24061045-b8a8ae56-5e8dd&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2023-01-23T10%3A28%3A58&Signature=2qUdxbxfqovHR0aWQ4Yhyln3yUumm63DFg%2FMnhtFMLQ%3D 
RequestHeaders:
 {
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
  'Content-Type': 'application/json'
} 
RequestBody:
 {} 

handleRestResponse:
 huobi POST https://api.hbdm.com/linear-swap-api/v1/swap_cross_account_info?AccessKeyId=1qdmpe4rty-24061045-b8a8ae56-5e8dd&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2023-01-23T10%3A28%3A58&Signature=2qUdxbxfqovHR0aWQ4Yhyln3yUumm63DFg%2FMnhtFMLQ%3D 200 OK 
ResponseHeaders:
 {
  'Cache-Control': 'no-cache, no-store',
  'Cf-Cache-Status': 'DYNAMIC',
  'Cf-Ray': '78dfd87c79e724a4-KBP',
  Connection: 'keep-alive',
  'Content-Encoding': 'gzip',
  'Content-Type': 'text/plain;charset=UTF-8',
  Date: 'Mon, 23 Jan 2023 10:28:59 GMT',
  Expires: 'Thu, 01 Jan 1970 00:00:00 GMT',
  Pragma: 'no-cache',
  'Ratelimit-Interval': '3000',
  'Ratelimit-Limit': '72',
  'Ratelimit-Remaining': '71',
  'Ratelimit-Reset': '1674469742115',
  Reqid: 'bab92485dacc2eb33a2f82c373b6cda8',
  'Request-Id': '5397f51d946bd11b6531fe5734ca2644',
  Server: 'cloudflare',
  'Set-Cookie': '__cf_bm=cCgOOgiitB78RlBMPze0uy19FF.ZbV68LGtkwQo1vjY-1674469739-0-AVtvDrvSkjojMCkK+34YWWuQQ2+fvNMJ83OiTB8x2cTcv0UCMhZK2GchwE5E9zhqEHzbnAiExWtCdOsIhFQK/fs=; path=/; expires=Mon, 23-Jan-23 10:58:59 GMT; domain=.hbdm.com; HttpOnly; Secure; SameSite=None',
  'Transfer-Encoding': 'chunked',
  'X-Request-Id': 'bab92485dacc2eb33a2f82c373b6cda8'
} 
ResponseBody:
 {"status":"error","err_code":4002,"err_msg":"The merged cross and isolated margin account for USDT-M futures is unavailable.Please complete the query with linear-swap-api/v3/unified_account_info","ts":1674469739115} 

```
@Mnemon1k yes, thank you! We will try to resolve it asap and will revert back to you here!
@Mnemon1k can you confirm whether the issue has gone away? for me, it works at this moment. I suspect there has been some temporary bug in huobi api.
Hmm, no i still have error
@Mnemon1k did you try to update ccxt (npm update)?
Hello @Mnemon1k you did upgrade to the unified margin account right?
Right now, Huobi does not seem to fully support it so we didn't add it yet, but we will follow this matter closely
![image](https://user-images.githubusercontent.com/43336371/214876496-c70112e0-0899-48ab-a84f-3fec3f138241.png)

Commit message: move lines

Patch to review:
diff --git a/js/huobi.js b/js/huobi.js
index 23aa527ee5ade..2966bb7cb0ebc 100644
--- a/js/huobi.js
+++ b/js/huobi.js
@@ -2929,7 +2929,6 @@ module.exports = class huobi extends Exchange {
         const options = this.safeValue (this.options, 'fetchBalance', {});
         const request = {};
         let method = undefined;
-        const margin = (type === 'margin');
         const spot = (type === 'spot');
         const future = (type === 'future');
         const swap = (type === 'swap');
@@ -2943,23 +2942,20 @@ module.exports = class huobi extends Exchange {
         params = this.omit (params, [ 'defaultSubType', 'subType' ]);
         const isolated = (marginMode === 'isolated');
         const cross = (marginMode === 'cross');
-        if (spot) {
-            if (isolated) {
-                method = 'spotPrivateGetV1MarginAccountsBalance';
-            } else if (cross) {
-                method = 'spotPrivateGetV1CrossMarginAccountsBalance';
+        const margin = (type === 'margin') || cross || isolated;
+        if (spot || margin) {
+            if (margin) {
+                if (isolated) {
+                    method = 'spotPrivateGetV1MarginAccountsBalance';
+                } else {
+                    method = 'spotPrivateGetV1CrossMarginAccountsBalance';
+                }
             } else {
                 await this.loadAccounts ();
                 const accountId = await this.fetchAccountIdByType (type, params);
                 request['account-id'] = accountId;
                 method = 'spotPrivateGetV1AccountAccountsAccountIdBalance';
             }
-        } else if (margin) {
-            if (isolated) {
-                method = 'spotPrivateGetV1MarginAccountsBalance';
-            } else {
-                method = 'spotPrivateGetV1CrossMarginAccountsBalance';
-            }
         } else if (linear) {
             if (isolated) {
                 method = 'contractPrivatePostLinearSwapApiV1SwapAccountInfo';
@@ -3131,7 +3127,7 @@ module.exports = class huobi extends Exchange {
         //
         // TODO add balance parsing for linear swap
         //
-        const result = { 'info': response };
+        let result = { 'info': response };
         const data = this.safeValue (response, 'data');
         if (spot || margin) {
             if (isolated) {
@@ -3156,17 +3152,11 @@ module.exports = class huobi extends Exchange {
                     const code = this.safeCurrencyCode (currencyId);
                     result[code] = this.parseMarginBalanceHelper (balance, code, result);
                 }
+                result = this.safeBalance (result);
             }
         } else if (linear) {
             const first = this.safeValue (data, 0, {});
-            if (cross) {
-                const account = this.account ();
-                account['free'] = this.safeString (first, 'margin_balance', 'margin_available');
-                account['used'] = this.safeString (first, 'margin_frozen');
-                const currencyId = this.safeString2 (first, 'margin_asset', 'symbol');
-                const code = this.safeCurrencyCode (currencyId);
-                result[code] = account;
-            } else if (isolated) {
+            if (isolated) {
                 for (let i = 0; i < data.length; i++) {
                     const balance = data[i];
                     const marketId = this.safeString2 (balance, 'contract_code', 'margin_account');
@@ -3187,7 +3177,14 @@ module.exports = class huobi extends Exchange {
                         result[symbol] = this.safeBalance (accountsByCode);
                     }
                 }
-                return result;
+            } else {
+                const account = this.account ();
+                account['free'] = this.safeString (first, 'margin_balance', 'margin_available');
+                account['used'] = this.safeString (first, 'margin_frozen');
+                const currencyId = this.safeString2 (first, 'margin_asset', 'symbol');
+                const code = this.safeCurrencyCode (currencyId);
+                result[code] = account;
+                result = this.safeBalance (result);
             }
         } else if (inverse) {
             for (let i = 0; i < data.length; i++) {
@@ -3199,9 +3196,9 @@ module.exports = class huobi extends Exchange {
                 account['used'] = this.safeString (balance, 'margin_frozen');
                 result[code] = account;
             }
+            result = this.safeBalance (result);
         }
-        const isolatedMargin = isolated && (spot || margin);
-        return isolatedMargin ? result : this.safeBalance (result);
+        return result;
     }
 
     async fetchOrder (id, symbol = undefined, params = {}) {


Code review comments:
diff_hunk: @@ -2943,23 +2942,20 @@ module.exports = class huobi extends Exchange {
         params = this.omit (params, [ 'defaultSubType', 'subType' ]);
         const isolated = (marginMode === 'isolated');
         const cross = (marginMode === 'cross');
-        if (spot) {
-            if (isolated) {
-                method = 'spotPrivateGetV1MarginAccountsBalance';
-            } else if (cross) {
-                method = 'spotPrivateGetV1CrossMarginAccountsBalance';
+        const margin = (type === 'margin') || cross || isolated;
path: js/huobi.js
text: @user1:
@author something is off here 
```
 p huobi balance
Python v3.10.9
CCXT v2.7.33
{}
huobi requires to release all resources with an explicit call to the .close() coroutine. If you are using the exchange instance with async coroutines, add `await exchange.close()` to your code into a place when you're done with the exchange and don't need the exchange instance anymore (at the end of your async coroutine).
```


@author:
ah, forgot to place spot. please recheck now.
all I wanted to mean is that, if someone uses `fetchBalance ({marginMode: 'isolated/cross'})` I mean that user is requesting spot margin balance (isolated/cross), without the need to place `margin:true` in params. is not that a correct expectation?

@user1:
>  fetchBalance ({marginMode: 'isolated/cross'}) I mean that user is requesting spot margin balance 
@author yes I think that is correct unless we have the ambiguity between isolated/cross on the swap wallet as well