UNPKG

ccxt-xigua

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,938 lines 84.4 kB
'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { BadSymbol, ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, BadRequest } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
const Precise = require ('./base/Precise');

//  ---------------------------------------------------------------------------

module.exports = class currencycom extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'currencycom',
            'name': 'Currency.com',
            'countries': [ 'BY' ], // Belarus
            'rateLimit': 100,
            'certified': true,
            'pro': true,
            'version': 'v2',
            // new metainfo interface
            'has': {
                'CORS': undefined,
                'spot': true,
                'margin': true,
                'swap': true,
                'future': false,
                'option': false,
                'addMargin': undefined,
                'cancelAllOrders': undefined,
                'cancelOrder': true,
                'cancelOrders': undefined,
                'createDepositAddress': undefined,
                'createLimitOrder': true,
                'createMarketOrder': true,
                'createOrder': true,
                'createStopLimitOrder': true,
                'createStopMarketOrder': true,
                'createStopOrder': true,
                'editOrder': 'emulated',
                'fetchAccounts': true,
                'fetchBalance': true,
                'fetchBidsAsks': undefined,
                'fetchBorrowRate': undefined,
                'fetchBorrowRateHistory': undefined,
                'fetchBorrowRates': undefined,
                'fetchBorrowRatesPerSymbol': undefined,
                'fetchCanceledOrders': undefined,
                'fetchClosedOrder': undefined,
                'fetchClosedOrders': undefined,
                'fetchCurrencies': true,
                'fetchDeposit': undefined,
                'fetchDepositAddress': true,
                'fetchDepositAddresses': false,
                'fetchDepositAddressesByNetwork': false,
                'fetchDeposits': true,
                'fetchFundingHistory': false,
                'fetchFundingRate': false,
                'fetchFundingRateHistory': false,
                'fetchFundingRates': false,
                'fetchIndexOHLCV': false,
                'fetchL2OrderBook': true,
                'fetchLedger': true,
                'fetchLedgerEntry': false,
                'fetchLeverage': true,
                'fetchLeverageTiers': false,
                'fetchMarginMode': false,
                'fetchMarkets': true,
                'fetchMarkOHLCV': false,
                'fetchMyTrades': true,
                'fetchOHLCV': true,
                'fetchOpenOrder': undefined,
                'fetchOpenOrders': true,
                'fetchOrder': undefined,
                'fetchOrderBook': true,
                'fetchOrderBooks': undefined,
                'fetchOrders': undefined,
                'fetchOrderTrades': undefined,
                'fetchPosition': undefined,
                'fetchPositionMode': false,
                'fetchPositions': true,
                'fetchPositionsRisk': undefined,
                'fetchPremiumIndexOHLCV': false,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTime': true,
                'fetchTrades': true,
                'fetchTradingFee': false,
                'fetchTradingFees': true,
                'fetchTradingLimits': undefined,
                'fetchTransactionFee': undefined,
                'fetchTransactionFees': undefined,
                'fetchTransactions': true,
                'fetchTransfers': undefined,
                'fetchWithdrawal': undefined,
                'fetchWithdrawals': true,
                'reduceMargin': undefined,
                'setLeverage': undefined,
                'setMarginMode': undefined,
                'setPositionMode': undefined,
                'signIn': undefined,
                'transfer': undefined,
                'withdraw': undefined,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '10m': '10m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '1d': '1d',
                '1w': '1w',
            },
            'hostname': 'backend.currency.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg',
                'api': {
                    'public': 'https://api-adapter.{hostname}/api',
                    'private': 'https://api-adapter.{hostname}/api',
                    'marketcap': 'https://marketcap.{hostname}/api',
                },
                'test': {
                    'public': 'https://demo-api-adapter.{hostname}/api',
                    'private': 'https://demo-api-adapter.{hostname}/api',
                },
                'www': 'https://www.currency.com',
                'referral': 'https://currency.com/trading/signup?c=362jaimv&pid=referral',
                'doc': [
                    'https://currency.com/api',
                ],
                'fees': 'https://currency.com/fees-charges',
            },
            // rate-limits are described at: https://currency.com/api-get-started
            'api': {
                'public': {
                    'get': {
                        'v1/time': 1,
                        'v1/exchangeInfo': 1,
                        'v1/depth': 1,
                        'v1/aggTrades': 1,
                        'v1/klines': 1,
                        'v1/ticker/24hr': 1,
                        'v2/time': 1,
                        'v2/exchangeInfo': 1,
                        'v2/depth': 1,
                        'v2/aggTrades': 1,
                        'v2/klines': 1,
                        'v2/ticker/24hr': 1,
                    },
                },
                'marketcap': {
                    'get': {
                        'v1/assets': 1,
                        'v1/candles': 1,
                        'v1/orderbook': 1,
                        'v1/summary': 1,
                        'v1/ticker': 1,
                        'v1/token/assets': 1,
                        'v1/token/orderbook': 1,
                        'v1/token/summary': 1,
                        'v1/token/ticker': 1,
                        'v1/token/trades': 1,
                        'v1/token_crypto/OHLC': 1,
                        'v1/token_crypto/assets': 1,
                        'v1/token_crypto/orderbook': 1,
                        'v1/token_crypto/summary': 1,
                        'v1/token_crypto/ticker': 1,
                        'v1/token_crypto/trades': 1,
                        'v1/trades': 1,
                    },
                },
                'private': {
                    'get': {
                        'v1/account': 1,
                        'v1/currencies': 1,
                        'v1/deposits': 1,
                        'v1/depositAddress': 1,
                        'v1/ledger': 1,
                        'v1/leverageSettings': 1,
                        'v1/myTrades': 1,
                        'v1/openOrders': 1,
                        'v1/tradingPositions': 1,
                        'v1/tradingPositionsHistory': 1,
                        'v1/transactions': 1,
                        'v1/withdrawals': 1,
                        'v2/account': 1,
                        'v2/currencies': 1,
                        'v2/deposits': 1,
                        'v2/depositAddress': 1,
                        'v2/ledger': 1,
                        'v2/leverageSettings': 1,
                        'v2/myTrades': 1,
                        'v2/openOrders': 1,
                        'v2/tradingPositions': 1,
                        'v2/tradingPositionsHistory': 1,
                        'v2/transactions': 1,
                        'v2/withdrawals': 1,
                    },
                    'post': {
                        'v1/order': 1,
                        'v1/updateTradingPosition': 1,
                        'v1/updateTradingOrder': 1,
                        'v1/closeTradingPosition': 1,
                        'v2/order': 1,
                        'v2/updateTradingPosition': 1,
                        'v2/updateTradingOrder': 1,
                        'v2/closeTradingPosition': 1,
                    },
                    'delete': {
                        'v1/order': 1,
                        'v2/order': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': false,
                    'percentage': true,
                    'taker': this.parseNumber ('0.002'),
                    'maker': this.parseNumber ('0.002'),
                },
            },
            'precisionMode': TICK_SIZE,
            // exchange-specific options
            'options': {
                'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill
                'warnOnFetchOpenOrdersWithoutSymbol': true,
                'recvWindow': 5 * 1000, // 5 sec, default
                'timeDifference': 0, // the difference between system clock and Binance clock
                'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation
                'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision
                'newOrderRespType': {
                    'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
                    'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT'
                    'stop': 'RESULT',
                },
                'leverage_markets_suffix': '_LEVERAGE',
                'collateralCurrencies': [ 'USD', 'EUR', 'USDT' ],
            },
            'exceptions': {
                'broad': {
                    'FIELD_VALIDATION_ERROR Cancel is available only for LIMIT order': InvalidOrder,
                    'API key does not exist': AuthenticationError,
                    'Order would trigger immediately.': InvalidOrder,
                    'Account has insufficient balance for requested action.': InsufficientFunds,
                    'Rest API trading is not enabled.': ExchangeNotAvailable,
                    'Combination of parameters invalid': BadRequest,
                    'Invalid limit price': BadRequest,
                    'Only leverage symbol allowed here:': BadSymbol, // when you fetchLeverage for non-leverage symbols, like 'BTC/USDT' instead of 'BTC/USDT_LEVERAGE': {"code":"-1128","msg":"Only leverage symbol allowed here: BTC/USDT"}
                    'market data service is not available': ExchangeNotAvailable, // {"code":"-1021","msg":"market data service is not available"}
                    'your time is ahead of server': InvalidNonce, // {"code":"-1021","msg":"your time is ahead of server"}
                },
                'exact': {
                    '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."}
                    '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
                    // '-1021': InvalidNonce, // {"code":"-1021","msg":"your time is ahead of server"} // see above in the broad section
                    '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."}
                    '-1030': InvalidOrder, // {"code":"-1030","msg":"You mentioned an invalid value for the price parameter."}
                    '-1100': InvalidOrder, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
                    '-1104': ExchangeError, // Not all sent parameters were read, read 8 parameters but was sent 9
                    '-1025': AuthenticationError, // {"code":-1025,"msg":"Invalid API-key, IP, or permissions for action"}
                    '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} | {"code":"-1128","msg":"Combination of parameters invalid"} | {"code":"-1128","msg":"Invalid limit price"}
                    '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc...
                    '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
                    '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist'
                    '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." }
                    '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action."
                },
            },
            'commonCurrencies': {
                'ACN': 'Accenture',
                'AMC': 'AMC Entertainment Holdings',
                'BNS': 'Bank of Nova Scotia',
                'CAR': 'Avis Budget Group Inc',
                'CLR': 'Continental Resources',
                'EDU': 'New Oriental Education & Technology Group Inc',
                'ETN': 'Eaton',
                'FOX': 'Fox Corporation',
                'GM': 'General Motors Co',
                'IQ': 'iQIYI',
                'OSK': 'Oshkosh',
                'PLAY': "Dave & Buster's Entertainment",
            },
        });
    }

    nonce () {
        return this.milliseconds () - this.options['timeDifference'];
    }

    async fetchTime (params = {}) {
        /**
         * @method
         * @name currencycom#fetchTime
         * @description fetches the current integer timestamp in milliseconds from the exchange server
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {int} the current integer timestamp in milliseconds from the exchange server
         */
        const response = await this.publicGetV2Time (params);
        //
        //     {
        //         "serverTime": 1590998366609
        //     }
        //
        return this.safeInteger (response, 'serverTime');
    }

    async fetchCurrencies (params = {}) {
        /**
         * @method
         * @name currencycom#fetchCurrencies
         * @description fetches all available currencies on an exchange
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} an associative dictionary of currencies
         */
        // requires authentication
        if (!this.checkRequiredCredentials (false)) {
            return undefined;
        }
        const response = await this.privateGetV2Currencies (params);
        //
        //     [
        //         {
        //             "name": "Euro",
        //             "displaySymbol": "EUR.cx",
        //             "precision": "2",
        //             "type": "FIAT",
        //             "minWithdrawal": "90.0",
        //             "maxWithdrawal": "1.0E+8",
        //             "commissionMin": "0.02", // some instruments do not have this property
        //             "commissionPercent": "1.5", // some instruments do not have this property
        //             "minDeposit": "90.0",
        //         },
        //         {
        //             name: "Bitcoin",
        //             displaySymbol: "BTC",
        //             precision: "8",
        //             type: "CRYPTO", // only a few major currencies have this value, others like USDT have a value of "TOKEN"
        //             minWithdrawal: "0.00020",
        //             commissionFixed: "0.00010",
        //             minDeposit: "0.00010",
        //         },
        //     ]
        //
        const result = {};
        for (let i = 0; i < response.length; i++) {
            const currency = response[i];
            const id = this.safeString (currency, 'displaySymbol');
            const code = this.safeCurrencyCode (id);
            const fee = this.safeNumber (currency, 'commissionFixed');
            result[code] = {
                'id': id,
                'code': code,
                'address': this.safeString (currency, 'baseAddress'),
                'type': this.safeStringLower (currency, 'type'),
                'name': this.safeString (currency, 'name'),
                'active': undefined,
                'deposit': undefined,
                'withdraw': undefined,
                'fee': fee,
                'precision': this.parseNumber (this.parsePrecision (this.safeString (currency, 'precision'))),
                'limits': {
                    'amount': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': this.safeNumber (currency, 'minWithdrawal'),
                        'max': this.safeNumber (currency, 'maxWithdrawal'),
                    },
                    'deposit': {
                        'min': this.safeNumber (currency, 'minDeposit'),
                        'max': undefined,
                    },
                },
                'info': currency,
            };
        }
        return result;
    }

    async fetchMarkets (params = {}) {
        /**
         * @method
         * @name currencycom#fetchMarkets
         * @description retrieves data on all markets for currencycom
         * @param {dict} params extra parameters specific to the exchange api endpoint
         * @returns {[dict]} an array of objects representing market data
         */
        const response = await this.publicGetV2ExchangeInfo (params);
        //
        //     {
        //         timezone: "UTC",
        //         serverTime: "1645186287261",
        //         rateLimits: [
        //             { rateLimitType: "REQUEST_WEIGHT", interval: "MINUTE", intervalNum: "1", limit: "1200" },
        //             { rateLimitType: "ORDERS", interval: "SECOND", intervalNum: "1", limit: "10" },
        //             { rateLimitType: "ORDERS", interval: "DAY", intervalNum: "1", limit: "864000" },
        //         ],
        //         exchangeFilters: [],
        //         symbols: [
        //             {
        //                 symbol: "BTC/USDT", // BTC/USDT, BTC/USDT_LEVERAGE
        //                 name: "Bitcoin / Tether",
        //                 status: "TRADING", // TRADING, BREAK, HALT
        //                 baseAsset: "BTC",
        //                 baseAssetPrecision: "4",
        //                 quoteAsset: "USDT",
        //                 quoteAssetId: "USDT", // USDT, USDT_LEVERAGE
        //                 quotePrecision: "4",
        //                 orderTypes: [ "LIMIT", "MARKET" ], // LIMIT, MARKET, STOP
        //                 filters: [
        //                     { filterType: "LOT_SIZE", minQty: "0.0001", maxQty: "100", stepSize: "0.0001", },
        //                     { filterType: "MIN_NOTIONAL", minNotional: "5", },
        //                 ],
        //                 marketModes: [ "REGULAR" ], // CLOSE_ONLY, LONG_ONLY, REGULAR
        //                 marketType: "SPOT", // SPOT, LEVERAGE
        //                 longRate: -0.0684932, // LEVERAGE only
        //                 shortRate: -0.0684932, // LEVERAGE only
        //                 swapChargeInterval: 1440, // LEVERAGE only
        //                 country: "",
        //                 sector: "",
        //                 industry: "",
        //                 tradingHours: "UTC; Mon - 22:00, 22:05 -; Tue - 22:00, 22:05 -; Wed - 22:00, 22:05 -; Thu - 22:00, 22:05 -; Fri - 22:00, 23:01 -; Sat - 22:00, 22:05 -; Sun - 21:00, 22:05 -",
        //                 tickSize: "0.01",
        //                 tickValue: "403.4405", // not available in BTC/USDT_LEVERAGE, but available in BTC/USD_LEVERAGE
        //                 exchangeFee: "0.2", // SPOT only
        //                 tradingFee: 0.075, // LEVERAGE only
        //                 makerFee: -0.025, // LEVERAGE only
        //                 takerFee: 0.06, // LEVERAGE only
        //                 maxSLGap: 50, // LEVERAGE only
        //                 minSLGap: 1, // LEVERAGE only
        //                 maxTPGap: 50, // LEVERAGE only
        //                 minTPGap: 0.5, // LEVERAGE only
        //                 assetType: "CRYPTOCURRENCY",
        //             },
        //         ]
        //     }
        //
        if (this.options['adjustForTimeDifference']) {
            await this.loadTimeDifference ();
        }
        const markets = this.safeValue (response, 'symbols', []);
        const result = [];
        for (let i = 0; i < markets.length; i++) {
            const market = markets[i];
            const id = this.safeString (market, 'symbol');
            const baseId = this.safeString (market, 'baseAsset');
            const quoteId = this.safeString (market, 'quoteAsset');
            const base = this.safeCurrencyCode (baseId);
            const quote = this.safeCurrencyCode (quoteId);
            let symbol = base + '/' + quote;
            const type = this.safeString (market, 'marketType');
            const spot = (type === 'SPOT');
            const futures = false;
            const swap = (type === 'LEVERAGE');
            const margin = swap; // as we decided to set
            if (swap) {
                symbol = symbol.replace (this.options['leverage_markets_suffix'], '');
                symbol += ':' + quote;
            }
            const active = this.safeString (market, 'status') === 'TRADING';
            // to set taker & maker fees, we use one from the below data - pairs either have 'exchangeFee' or 'tradingFee', if none of them (rare cases), then they should have 'takerFee & makerFee'
            const exchangeFee = this.safeString2 (market, 'exchangeFee', 'tradingFee');
            let makerFee = this.safeString (market, 'makerFee', exchangeFee);
            let takerFee = this.safeString (market, 'takerFee', exchangeFee);
            makerFee = Precise.stringDiv (makerFee, '100');
            takerFee = Precise.stringDiv (takerFee, '100');
            const filters = this.safeValue (market, 'filters', []);
            const filtersByType = this.indexBy (filters, 'filterType');
            let limitPriceMin = undefined;
            let limitPriceMax = undefined;
            let precisionPrice = this.safeNumber (market, 'tickSize');
            if ('PRICE_FILTER' in filtersByType) {
                const filter = this.safeValue (filtersByType, 'PRICE_FILTER', {});
                precisionPrice = this.safeNumber (filter, 'tickSize');
                // PRICE_FILTER reports zero values for maxPrice
                // since they updated filter types in November 2018
                // https://github.com/ccxt/ccxt/issues/4286
                // therefore limits['price']['max'] doesn't have any meaningful value except undefined
                limitPriceMin = this.safeNumber (filter, 'minPrice');
                const maxPrice = this.safeNumber (filter, 'maxPrice');
                if ((maxPrice !== undefined) && (maxPrice > 0)) {
                    limitPriceMax = maxPrice;
                }
            }
            let precisionAmount = this.parseNumber (this.parsePrecision (this.safeString (market, 'baseAssetPrecision')));
            let limitAmount = {
                'min': undefined,
                'max': undefined,
            };
            if ('LOT_SIZE' in filtersByType) {
                const filter = this.safeValue (filtersByType, 'LOT_SIZE', {});
                precisionAmount = this.safeNumber (filter, 'stepSize');
                limitAmount = {
                    'min': this.safeNumber (filter, 'minQty'),
                    'max': this.safeNumber (filter, 'maxQty'),
                };
            }
            let limitMarket = {
                'min': undefined,
                'max': undefined,
            };
            if ('MARKET_LOT_SIZE' in filtersByType) {
                const filter = this.safeValue (filtersByType, 'MARKET_LOT_SIZE', {});
                limitMarket = {
                    'min': this.safeNumber (filter, 'minQty'),
                    'max': this.safeNumber (filter, 'maxQty'),
                };
            }
            let costMin = undefined;
            if ('MIN_NOTIONAL' in filtersByType) {
                const filter = this.safeValue (filtersByType, 'MIN_NOTIONAL', {});
                costMin = this.safeNumber (filter, 'minNotional');
            }
            const isContract = swap || futures;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': undefined,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': undefined,
                'type': type,
                'spot': spot,
                'margin': margin,
                'swap': swap,
                'future': futures,
                'option': false,
                'active': active,
                'contract': isContract,
                'linear': isContract ? true : undefined,
                'inverse': undefined,
                'taker': this.parseNumber (takerFee),
                'maker': this.parseNumber (makerFee),
                'contractSize': undefined,
                'expiry': undefined,
                'expiryDatetime': undefined,
                'strike': undefined,
                'optionType': undefined,
                'precision': {
                    'amount': precisionAmount,
                    'price': precisionPrice,
                },
                'limits': {
                    'leverage': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'amount': limitAmount,
                    'market': limitMarket,
                    'price': {
                        'min': limitPriceMin,
                        'max': limitPriceMax,
                    },
                    'cost': {
                        'min': costMin,
                        'max': undefined,
                    },
                },
                'info': market,
            });
        }
        return result;
    }

    async fetchAccounts (params = {}) {
        /**
         * @method
         * @name currencycom#fetchAccounts
         * @description fetch all the accounts associated with a profile
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a dictionary of [account structures]{@link https://docs.ccxt.com/en/latest/manual.html#account-structure} indexed by the account type
         */
        const response = await this.privateGetV2Account (params);
        //
        //     {
        //         "makerCommission": "0.20",
        //         "takerCommission": "0.20",
        //         "buyerCommission": "0.20",
        //         "sellerCommission": "0.20",
        //         "canTrade": true,
        //         "canWithdraw": true,
        //         "canDeposit": true,
        //         "updateTime": "1645266330",
        //         "userId": "644722",
        //         "balances": [
        //             {
        //                 "accountId": "120702016179403605",
        //                 "collateralCurrency": false,
        //                 "asset": "CAKE",
        //                 "free": "3.1",
        //                 "locked": "0.0",
        //                 "default": false,
        //             },
        //             {
        //                 "accountId": "109698017713125316",
        //                 "collateralCurrency": true,
        //                 "asset": "USD",
        //                 "free": "17.58632",
        //                 "locked": "0.0",
        //                 "default": true,
        //             }
        //         ]
        //     }
        //
        const accounts = this.safeValue (response, 'balances', []);
        const result = [];
        for (let i = 0; i < accounts.length; i++) {
            const account = accounts[i];
            const accountId = this.safeInteger (account, 'accountId');
            const currencyId = this.safeString (account, 'asset');
            const currencyCode = this.safeCurrencyCode (currencyId);
            result.push ({
                'id': accountId,
                'type': undefined,
                'currency': currencyCode,
                'info': account,
            });
        }
        return result;
    }

    async fetchTradingFees (params = {}) {
        /**
         * @method
         * @name currencycom#fetchTradingFees
         * @description fetch the trading fees for multiple markets
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a dictionary of [fee structures]{@link https://docs.ccxt.com/en/latest/manual.html#fee-structure} indexed by market symbols
         */
        await this.loadMarkets ();
        const response = await this.privateGetV2Account (params);
        //
        //    {
        //        makerCommission: '0.20',
        //        takerCommission: '0.20',
        //        buyerCommission: '0.20',
        //        sellerCommission: '0.20',
        //        canTrade: true,
        //        canWithdraw: true,
        //        canDeposit: true,
        //        updateTime: '1645738976',
        //        userId: '-1924114235',
        //        balances: []
        //    }
        //
        const makerFee = this.safeNumber (response, 'makerCommission');
        const takerFee = this.safeNumber (response, 'takerCommission');
        const result = {};
        for (let i = 0; i < this.symbols.length; i++) {
            const symbol = this.symbols[i];
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': makerFee,
                'taker': takerFee,
                'percentage': true,
                'tierBased': false,
            };
        }
        return result;
    }

    parseBalance (response, type = undefined) {
        //
        //     {
        //         "makerCommission":0.20,
        //         "takerCommission":0.20,
        //         "buyerCommission":0.20,
        //         "sellerCommission":0.20,
        //         "canTrade":true,
        //         "canWithdraw":true,
        //         "canDeposit":true,
        //         "updateTime":1591056268,
        //         "balances":[
        //             {
        //                 "accountId":5470306579272368,
        //                 "collateralCurrency":true,
        //                 "asset":"ETH",
        //                 "free":0.0,
        //                 "locked":0.0,
        //                 "default":false,
        //             },
        //         ]
        //     }
        //
        const result = { 'info': response };
        const balances = this.safeValue (response, 'balances', []);
        for (let i = 0; i < balances.length; i++) {
            const balance = balances[i];
            const currencyId = this.safeString (balance, 'asset');
            const code = this.safeCurrencyCode (currencyId);
            const account = this.account ();
            account['free'] = this.safeString (balance, 'free');
            account['used'] = this.safeString (balance, 'locked');
            result[code] = account;
        }
        return this.safeBalance (result);
    }

    async fetchBalance (params = {}) {
        /**
         * @method
         * @name currencycom#fetchBalance
         * @description query for balance and get the amount of funds available for trading or funds locked in orders
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure}
         */
        await this.loadMarkets ();
        const response = await this.privateGetV2Account (params);
        //
        //     {
        //         "makerCommission": "0.20",
        //         "takerCommission": "0.20",
        //         "buyerCommission": "0.20",
        //         "sellerCommission": "0.20",
        //         "canTrade": true,
        //         "canWithdraw": true,
        //         "canDeposit": true,
        //         "updateTime": "1645266330",
        //         "userId": "644722",
        //         "balances": [
        //             {
        //                 "accountId": "120702016179403605",
        //                 "collateralCurrency": false,
        //                 "asset": "CAKE",
        //                 "free": "1.784",
        //                 "locked": "0.0",
        //                 "default": false,
        //             },
        //             {
        //                 "accountId": "109698017413175316",
        //                 "collateralCurrency": true,
        //                 "asset": "USD",
        //                 "free": "7.58632",
        //                 "locked": "0.0",
        //                 "default": true,
        //             }
        //         ]
        //     }
        //
        return this.parseBalance (response);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchOrderBook
         * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         * @param {str} symbol unified symbol of the market to fetch the order book for
         * @param {int|undefined} limit the maximum amount of order book entries to return
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        if (limit !== undefined) {
            request['limit'] = limit; // default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000, 5000
        }
        const response = await this.publicGetV2Depth (this.extend (request, params));
        //
        //     {
        //         "lastUpdateId":1590999849037,
        //         "asks":[
        //             [0.02495,60.0345],
        //             [0.02496,34.1],
        //             ...
        //         ],
        //         "bids":[
        //             [0.02487,72.4144854],
        //             [0.02486,24.043],
        //             ...
        //         ]
        //     }
        //
        const orderbook = this.parseOrderBook (response, symbol);
        orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId');
        return orderbook;
    }

    parseTicker (ticker, market = undefined) {
        //
        // fetchTicker
        //
        //     {
        //         "symbol":"ETH/BTC",
        //         "priceChange":"0.00030",
        //         "priceChangePercent":"1.21",
        //         "weightedAvgPrice":"0.02481",
        //         "prevClosePrice":"0.02447",
        //         "lastPrice":"0.02477",
        //         "lastQty":"60.0",
        //         "bidPrice":"0.02477",
        //         "askPrice":"0.02484",
        //         "openPrice":"0.02447",
        //         "highPrice":"0.02524",
        //         "lowPrice":"0.02438",
        //         "volume":"11.97",
        //         "quoteVolume":"0.298053",
        //         "openTime":1590969600000,
        //         "closeTime":1591000072693
        //     }
        //
        // fetchTickers
        //
        //     {
        //          "symbol": "SHIB/USD_LEVERAGE",
        //          "weightedAvgPrice": "0.000027595",
        //          "lastPrice": "0.00002737",
        //          "lastQty": "1.11111111E8",
        //          "bidPrice": "0.00002737",
        //          "askPrice": "0.00002782",
        //          "highPrice": "0.00002896",
        //          "lowPrice": "0.00002738",
        //          "volume": "16472160000",
        //          "quoteVolume": "454796.3376",
        //          "openTime": "1645187472000",
        //          "closeTime": "1645273872000",
        //     }
        //
        // ws:marketData.subscribe
        //
        //     {
        //          "symbolName":"TXN",
        //          "bid":139.85,
        //          "bidQty":2500,
        //          "ofr":139.92000000000002,
        //          "ofrQty":2500,
        //          "timestamp":1597850971558
        //      }
        //
        const timestamp = this.safeInteger2 (ticker, 'closeTime', 'timestamp');
        const marketId = this.safeString2 (ticker, 'symbol', 'symbolName');
        market = this.safeMarket (marketId, market, '/');
        const last = this.safeString (ticker, 'lastPrice');
        return this.safeTicker ({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeString (ticker, 'highPrice'),
            'low': this.safeString (ticker, 'lowPrice'),
            'bid': this.safeString2 (ticker, 'bidPrice', 'bid'),
            'bidVolume': this.safeString (ticker, 'bidQty'),
            'ask': this.safeString2 (ticker, 'askPrice', 'ofr'),
            'askVolume': this.safeString (ticker, 'ofrQty'),
            'vwap': this.safeString (ticker, 'weightedAvgPrice'),
            'open': this.safeString (ticker, 'openPrice'),
            'close': last,
            'last': last,
            'previousClose': this.safeString (ticker, 'prevClosePrice'), // previous day close
            'change': this.safeString (ticker, 'priceChange'),
            'percentage': this.safeString (ticker, 'priceChangePercent'),
            'average': undefined,
            'baseVolume': this.safeString (ticker, 'volume'),
            'quoteVolume': this.safeString (ticker, 'quoteVolume'),
            'info': ticker,
        }, market);
    }

    async fetchTicker (symbol, params = {}) {
        /**
         * @method
         * @name currencycom#fetchTicker
         * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
         * @param {str} symbol unified symbol of the market to fetch the ticker for
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        const response = await this.publicGetV2Ticker24hr (this.extend (request, params));
        //
        //     {
        //         "symbol":"ETH/BTC",
        //         "priceChange":"0.00030",
        //         "priceChangePercent":"1.21",
        //         "weightedAvgPrice":"0.02481",
        //         "prevClosePrice":"0.02447",
        //         "lastPrice":"0.02477",
        //         "lastQty":"60.0",
        //         "bidPrice":"0.02477",
        //         "askPrice":"0.02484",
        //         "openPrice":"0.02447",
        //         "highPrice":"0.02524",
        //         "lowPrice":"0.02438",
        //         "volume":"11.97",
        //         "quoteVolume":"0.298053",
        //         "openTime":1590969600000,
        //         "closeTime":1591000072693
        //     }
        //
        return this.parseTicker (response, market);
    }

    async fetchTickers (symbols = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchTickers
         * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
         * @param {[str]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
         */
        await this.loadMarkets ();
        const response = await this.publicGetV2Ticker24hr (params);
        //
        //     [
        //         {
        //              "symbol": "SHIB/USD_LEVERAGE",
        //              "weightedAvgPrice": "0.000027595",
        //              "lastPrice": "0.00002737",
        //              "lastQty": "1.11111111E8",
        //              "bidPrice": "0.00002737",
        //              "askPrice": "0.00002782",
        //              "highPrice": "0.00002896",
        //              "lowPrice": "0.00002738",
        //              "volume": "16472160000",
        //              "quoteVolume": "454796.3376",
        //              "openTime": "1645187472000",
        //              "closeTime": "1645273872000",
        //         }
        //     ]
        //
        return this.parseTickers (response, symbols);
    }

    parseOHLCV (ohlcv, market = undefined) {
        //
        //     [
        //         1590971040000,
        //         "0.02454",
        //         "0.02456",
        //         "0.02452",
        //         "0.02456",
        //         249
        //     ]
        //
        return [
            this.safeInteger (ohlcv, 0),
            this.safeNumber (ohlcv, 1),
            this.safeNumber (ohlcv, 2),
            this.safeNumber (ohlcv, 3),
            this.safeNumber (ohlcv, 4),
            this.safeNumber (ohlcv, 5),
        ];
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchOHLCV
         * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
         * @param {str} symbol unified symbol of the market to fetch OHLCV data for
         * @param {str} timeframe the length of time each candle represents
         * @param {int|undefined} since timestamp in ms of the earliest candle to fetch
         * @param {int|undefined} limit the maximum amount of candles to fetch
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
            'interval': this.timeframes[timeframe],
        };
        if (since !== undefined) {
            request['startTime'] = since;
        }
        if (limit !== undefined) {
            request['limit'] = limit; // default 500, max 1000
        }
        const response = await this.publicGetV2Klines (this.extend (request, params));
        //
        //     [
        //         [1590971040000,"0.02454","0.02456","0.02452","0.02456",249],
        //         [1590971100000,"0.02455","0.02457","0.02452","0.02456",300],
        //         [1590971160000,"0.02455","0.02456","0.02453","0.02454",286],
        //     ]
        //
        return this.parseOHLCVs (response, market, timeframe, since, limit);
    }

    parseTrade (trade, market = undefined) {
        //
        // fetchTrades (public aggregate trades)
        //
        //     {
        //         "a":"1658318071",    // Aggregate tradeId
        //         "p":"0.02476",       // Price
        //         "q":"0.0",           // Official doc says: "Quantity (should be ignored)"
        //         "T":"1591001423382", // Epoch timestamp in MS
        //         "m":false            // Was the buyer the maker
        //     }
        //
        // createOrder fills (private)
        //
        //     {
        //         "price": "9807.05",
        //         "qty": "0.01",
        //         "commission": "0",
        //         "commissionAsset": "dUSD"
        //     }
        //
        // fetchMyTrades
        //
        //     {
        //         "symbol": "DOGE/USD",
        //         "id": "116046000",
        //         "orderId": "00000000-0000-0000-0000-000006dbb8ad",
        //         "price": "0.14094",
        //         "qty": "40.0",
        //         "commission": "0.01",
        //         "commissionAsset": "USD",
        //         "time": "1645283022351",
        //         "buyer": false,
        //         "maker": false,
        //         "isBuyer": false,
        //         "isMaker": false
        //     }
        //
        const timestamp = this.safeInteger2 (trade, 'T', 'time');
        const priceString = this.safeString2 (trade, 'p', 'price');
        const amountString = this.safeString2 (trade, 'q', 'qty');
        const id = this.safeString2 (trade, 'a', 'id');
        let side = undefined;
        const orderId = this.safeString (trade, 'orderId');
        let takerOrMaker = undefined;
        if ('m' in trade) {
            side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally [TODO: needs reason to be mentioned]
            takerOrMaker = 'taker'; // in public trades, it's always taker
        } else if ('isBuyer' in trade) {
            side = (trade['isBuyer']) ? 'buy' : 'sell'; // this is a true side
            takerOrMaker = trade['isMaker'] ? 'maker' : 'taker';
        }
        let fee = undefined;
        if ('commission' in trade) {
            fee = {
                'cost': this.safeString (trade, 'commission'),
                'currency': this.safeCurrencyCode (this.safeString (trade, 'commissionAsset')),
            };
        }
        const marketId = this.safeString (trade, 'symbol');
        const symbol = this.safeSymbol (marketId, market);
        return this.safeTrade ({
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': undefined,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': undefined,
            'fee': fee,
            'info': trade,
        }, market);
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchTrades
         * @description get the list of most recent trades for a particular symbol
         * @param {str} symbol unified symbol of the market to fetch trades for
         * @param {int|undefined} since timestamp in ms of the earliest trade to fetch
         * @param {int|undefined} limit the maximum amount of trades to fetch
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
            // 'limit': 500, // default 500, max 1000
        };
        if (limit !== undefined) {
            request['limit'] = limit; // default 500, max 1000
        }
        if (since !== undefined) {
            request['startTime'] = since;
        }
        const response = await this.publicGetV2AggTrades (this.extend (request, params));
        //
        // [
        //     {
        //         "a":"1658318071",    // Aggregate tradeId
        //         "p":"0.02476",       // Price
        //         "q":"0.0",           // Official doc says: "Quantity (should be ignored)"
        //         "T":"1591001423382", // Epoch timestamp in MS
        //         "m":false            // Was the buyer the maker
        //     },
        // ]
        //
        return this.parseTrades (response, market, since, limit);
    }

    parseOrder (order, market = undefined) {
        //
        // createOrder
        //
        // limit
        //
        //     {
        //         "symbol": "BTC/USD",
        //         "orderId": "00000000-0000-0000-0000-000006eacaa0",
        //         "transactTime": "1645281669295",
        //         "price": "30000.00000000",
        //         "origQty": "0.0002",
        //         "executedQty": "0.0",  // positive for BUY, negative for SELL
        //         "status": "NEW",
        //         "timeInForce": "GTC",
        //         "type": "LIMIT",
        //         "side": "BUY",
        //     }
        //
        // market
        //
        //     {
        //         "symbol": "DOGE/USD",
        //         "orderId": "00000000-0000-0000-0000-000006eab2ad",
        //         "transactTime": "1645283022252",
        //         "price": "0.14066000",
        //         "origQty": "40",
        //         "executedQty": "40.0",  // positive for BUY, negative for SELL
        //         "status": "FILLED",
        //         "timeInForce": "FOK",
        //         "type": "MARKET",
        //         "side": "SELL",
        //         "fills": [
        //             {
        //                 "price": "0.14094",
        //                 "qty": "40.0",
        //                 "commission": "0",
        //                 "commissionAsset": "dUSD",
        //             },
        //         ],
        //     }
        //
        // cancelOrder
        //
        //     {
        //         "symbol": "DOGE/USD",
        //         "orderId": "00000000-0000-0003-0000-000006db714c",
        //         "price": "0.13",
        //         "origQty": "30.0",
        //         "executedQty": "0.0",
        //         "status": "CANCELED",
        //         "timeInForce": "GTC",
        //         "type": "LIMIT",
        //         "side": "BUY",
        //     }
        //
        // fetchOpenOrders
        //
        //   {
        //       "symbol": "DOGE/USD",
        //       "orderId": "00000000-0000-0003-0000-000004bcc27a",
        //       "price": "0.13",
        //       "origQty": "39.0",
        //       "executedQty": "0.0",
        //       "status": "NEW",
        //       "timeInForce": "GTC",
        //       "type": "LIMIT",
        //       "side": "BUY",
        //       "time": "1645284216240",
        //       "updateTime": "1645284216240",
        //       "leverage": false, // whether it's swap or not
        //       "working": true,
        //   }
        //
        const marketId = this.safeString (order, 'symbol');
        const symbol = this.safeSymbol (marketId, market, '/');
        const id = this.safeString (order, 'orderId');
        const price = this.safeString (order, 'price');
        const amount = this.safeString (order, 'origQty');
        const filledRaw = this.safeString (order, 'executedQty');
        const filled = Precise.stringAbs (filledRaw);
        const status = this.parseOrderStatus (this.safeString (order, 'status'));
        const timeInForce = this.parseOrderTimeInForce (this.safeString (order, 'timeInForce'));
        const type = this.parseOrderType (this.safeString (order, 'type'));
        const side = this.parseOrderSide (this.safeString (order, 'side'));
        const timestamp = this.safeInteger2 (order, 'time', 'transactTime');
        const fills = this.safeValue (order, 'fills');
        return this.safeOrder ({
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'side': side,
            'price': price,
            'stopPrice': undefined,
            'amount': amount,
            'cost': undefined,
            'average': undefined,
            'filled': filled,
            'remaining': undefined,
            'status': status,
            'fee': undefined,
            'trades': fills,
        }, market);
    }

    parseOrderStatus (status) {
        const statuses = {
            'NEW': 'open',
            'PARTIALLY_FILLED': 'open',
            'FILLED': 'closed',
            'CANCELED': 'canceled',
            'PENDING_CANCEL': 'canceling',
            'REJECTED': 'rejected',
            'EXPIRED': 'expired',
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderType (status) {
        const statuses = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'STOP': 'stop',
            // temporarily we remove custom mappings
            // 'LIMIT_MAKER': '',
            // 'STOP_LOSS': 'stop-loss',
            // 'STOP_LOSS_LIMIT': 'stop-limit',
            // 'TAKE_PROFIT': 'take-profit',
            // 'TAKE_PROFIT_LIMIT': 'take-profit',
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderTimeInForce (status) {
        const statuses = {
            'GTC': 'GTC',
            'FOK': 'FOK',
            'IOC': 'IOC',
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderSide (status) {
        const statuses = {
            'BUY': 'buy',
            'SELL': 'sell',
        };
        return this.safeString (statuses, status, status);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#createOrder
         * @description create a trade order
         * @param {str} symbol unified symbol of the market to create an order in
         * @param {str} type 'market' or 'limit'
         * @param {str} side 'buy' or 'sell'
         * @param {float} amount how much of currency you want to trade in units of base currency
         * @param {float|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        let accountId = undefined;
        if (market['margin']) {
            accountId = this.safeString (this.options, 'accountId');
            accountId = this.safeString (params, 'accountId', accountId);
            if (accountId === undefined) {
                throw new ArgumentsRequired (this.id + " createOrder() requires an accountId parameter or an exchange.options['accountId'] option for " + market['type'] + ' markets');
            }
        }
        const newOrderRespType = this.safeValue (this.options['newOrderRespType'], type, 'RESULT');
        const request = {
            'symbol': market['id'],
            'quantity': this.amountToPrecision (symbol, amount),
            'type': type.toUpperCase (),
            'side': side.toUpperCase (),
            'newOrderRespType': newOrderRespType, // 'RESULT' for full order or 'FULL' for order with fills
            // 'leverage': 1,
            // 'accountId': 5470306579272968, // required for leverage markets
            // 'takeProfit': '123.45',
            // 'stopLoss': '54.321',
            // 'guaranteedStopLoss': '54.321',
        };
        if (type === 'limit') {
            request['price'] = this.priceToPrecision (symbol, price);
            request['timeInForce'] = this.options['defaultTimeInForce'];
        } else {
            if (type === 'stop') {
                request['type'] = 'STOP';
                request['price'] = this.priceToPrecision (symbol, price);
            } else if (type === 'market') {
                const stopPrice = this.safeNumber (params, 'stopPrice');
                params = this.omit (params, 'stopPrice');
                if (stopPrice !== undefined) {
                    request['type'] = 'STOP';
                    request['price'] = this.priceToPrecision (symbol, stopPrice);
                }
            }
        }
        const response = await this.privatePostV2Order (this.extend (request, params));
        //
        // limit
        //
        //     {
        //         "symbol": "BTC/USD",
        //         "orderId": "00000000-0000-0000-0000-000006eaaaa0",
        //         "transactTime": "1645281669295",
        //         "price": "30000.00000000",
        //         "origQty": "0.0002",
        //         "executedQty": "0.0",  // positive for BUY, negative for SELL
        //         "status": "NEW",
        //         "timeInForce": "GTC",
        //         "type": "LIMIT",
        //         "side": "BUY",
        //     }
        //
        // market
        //
        //     {
        //         "symbol": "DOGE/USD",
        //         "orderId": "00000000-0000-0000-0000-000006eab8ad",
        //         "transactTime": "1645283022252",
        //         "price": "0.14066000",
        //         "origQty": "40",
        //         "executedQty": "40.0",  // positive for BUY, negative for SELL
        //         "status": "FILLED",
        //         "timeInForce": "FOK",
        //         "type": "MARKET",
        //         "side": "BUY",
        //         "fills": [
        //             {
        //                 "price": "0.14094",
        //                 "qty": "40.0",
        //                 "commission": "0",
        //                 "commissionAsset": "dUSD"
        //             }
        //         ]
        //     }
        //
        return this.parseOrder (response, market);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchOpenOrders
         * @description fetch all unfilled currently open orders
         * @param {str|undefined} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch open orders for
         * @param {int|undefined} limit the maximum number of  open orders structures to retrieve
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        let market = undefined;
        const request = {};
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) {
            const symbols = this.symbols;
            const numSymbols = symbols.length;
            const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2);
            throw new ExchangeError (this.id + ' fetchOpenOrders() WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.');
        }
        const response = await this.privateGetV2OpenOrders (this.extend (request, params));
        //
        //     [
        //         {
        //             "symbol": "DOGE/USD",
        //             "orderId": "00000000-0000-0003-0000-000004bac57a",
        //             "price": "0.13",
        //             "origQty": "39.0",
        //             "executedQty": "0.0",  // positive for BUY, negative for SELL
        //             "status": "NEW",
        //             "timeInForce": "GTC",
        //             "type": "LIMIT",
        //             "side": "BUY",
        //             "time": "1645284216240",
        //             "updateTime": "1645284216240",
        //             "leverage": false,
        //             "working": true
        //         },
        //     ]
        //
        return this.parseOrders (response, market, since, limit, params);
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#cancelOrder
         * @description cancels an open order
         * @param {str} id order id
         * @param {str} symbol unified symbol of the market the order was made in
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const origClientOrderId = this.safeValue (params, 'origClientOrderId');
        const request = {
            'symbol': market['id'],
            // 'orderId': parseInt (id),
            // 'origClientOrderId': id,
        };
        if (origClientOrderId === undefined) {
            request['orderId'] = id;
        } else {
            request['origClientOrderId'] = origClientOrderId;
        }
        const response = await this.privateDeleteV2Order (this.extend (request, params));
        //
        //     {
        //         "symbol": "DOGE/USD",
        //         "orderId": "00000000-0000-0003-0000-000006db764c",
        //         "price": "0.13",
        //         "origQty": "30.0",
        //         "executedQty": "0.0",  // positive for BUY, negative for SELL
        //         "status": "CANCELED",
        //         "timeInForce": "GTC",
        //         "type": "LIMIT",
        //         "side": "BUY",
        //     }
        //
        return this.parseOrder (response, market);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchMyTrades
         * @description fetch all trades made by the user
         * @param {str} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch trades for
         * @param {int|undefined} limit the maximum number of trades structures to retrieve
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html#trade-structure}
         */
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        const response = await this.privateGetV2MyTrades (this.extend (request, params));
        //
        //     [
        //         {
        //             "symbol": "DOGE/USD",
        //             "id": "116046000",
        //             "orderId": "00000000-0000-0000-0000-000006dbb8ad",
        //             "price": "0.14094",
        //             "qty": "40.0",
        //             "commission": "0.01",
        //             "commissionAsset": "USD",
        //             "time": "1645283022351",
        //             "buyer": false,
        //             "maker": false,
        //             "isBuyer": false,
        //             "isMaker": false
        //         },
        //     ]
        //
        return this.parseTrades (response, market, since, limit);
    }

    async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchDeposits
         * @description fetch all deposits made to an account
         * @param {str|undefined} code unified currency code
         * @param {int|undefined} since the earliest time in ms to fetch deposits for
         * @param {int|undefined} limit the maximum number of deposits structures to retrieve
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [transaction structures]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        return await this.fetchTransactionsByMethod ('privateGetV2Deposits', code, since, limit, params);
    }

    async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchWithdrawals
         * @description fetch all withdrawals made from an account
         * @param {str|undefined} code unified currency code
         * @param {int|undefined} since the earliest time in ms to fetch withdrawals for
         * @param {int|undefined} limit the maximum number of withdrawals structures to retrieve
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [transaction structures]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        return await this.fetchTransactionsByMethod ('privateGetV2Withdrawals', code, since, limit, params);
    }

    async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchTransactions
         * @description fetch history of deposits and withdrawals
         * @param {str|undefined} code unified currency code for the currency of the transactions, default is undefined
         * @param {int|undefined} since timestamp in ms of the earliest transaction, default is undefined
         * @param {int|undefined} limit max number of transactions to return, default is undefined
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a list of [transaction structure]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        return await this.fetchTransactionsByMethod ('privateGetV2Transactions', code, since, limit, params);
    }

    async fetchTransactionsByMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {};
        let currency = undefined;
        if (code !== undefined) {
            currency = this.currency (code);
        }
        if (since !== undefined) {
            request['startTime'] = since;
        }
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        const response = await this[method] (this.extend (request, params));
        //
        //     [
        //       {
        //         "id": "616769213",
        //         "balance": "2.088",
        //         "amount": "1.304",   // negative for 'withdrawal'
        //         "currency": "CAKE",
        //         "type": "deposit",
        //         "timestamp": "1645282121023",
        //         "paymentMethod": "BLOCKCHAIN",
        //         "blockchainTransactionHash": "0x57c68c1f2ae74d5eda5a2a00516361d241a5c9e1ee95bf32573523857c38c112",
        //         "status": "PROCESSED",
        //         "commission": "0.14", // this property only exists in withdrawal
        //       },
        //     ]
        //
        return this.parseTransactions (response, currency, since, limit, params);
    }

    parseTransaction (transaction, currency = undefined) {
        const id = this.safeString (transaction, 'id');
        const txHash = this.safeString (transaction, 'blockchainTransactionHash');
        const amount = this.safeNumber (transaction, 'amount');
        const timestamp = this.safeInteger (transaction, 'timestamp');
        const currencyId = this.safeString (transaction, 'currency');
        const code = this.safeCurrencyCode (currencyId, currency);
        const state = this.parseTransactionStatus (this.safeString (transaction, 'state'));
        const type = this.parseTransactionType (this.safeString (transaction, 'type'));
        const feeCost = this.safeString (transaction, 'commission');
        let fee = undefined;
        if (feeCost !== undefined) {
            fee = { 'currency': code, 'cost': feeCost };
        }
        const result = {
            'id': id,
            'txid': txHash,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'network': undefined,
            'addressFrom': undefined,
            'address': undefined,
            'addressTo': undefined,
            'tagFrom': undefined,
            'tag': undefined,
            'tagTo': undefined,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': state,
            'updated': undefined,
            'comment': undefined,
            'fee': fee,
            'info': transaction,
        };
        return result;
    }

    parseTransactionStatus (status) {
        const statuses = {
            'APPROVAL': 'pending',
            'PROCESSED': 'ok',
        };
        return this.safeString (statuses, status, status);
    }

    parseTransactionType (type) {
        const types = {
            'deposit': 'deposit',
            'withdrawal': 'withdrawal',
        };
        return this.safeString (types, type, type);
    }

    async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchLedger
         * @description fetch the history of changes, actions done by the user or operations that altered balance of the user
         * @param {str|undefined} code unified currency code, default is undefined
         * @param {int|undefined} since timestamp in ms of the earliest ledger entry, default is undefined
         * @param {int|undefined} limit max number of ledger entrys to return, default is undefined
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a [ledger structure]{@link https://docs.ccxt.com/en/latest/manual.html#ledger-structure}
         */
        await this.loadMarkets ();
        const request = {};
        let currency = undefined;
        if (code !== undefined) {
            currency = this.currency (code);
        }
        if (since !== undefined) {
            request['startTime'] = since;
        }
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        const response = await this.privateGetV2Ledger (this.extend (request, params));
        // in the below example, first item expresses withdrawal/deposit type, second example expresses trade
        //
        // [
        //     {
        //       "id": "619031398",
        //       "balance": "0.0",
        //       "amount": "-1.088",
        //       "currency": "CAKE",
        //       "type": "withdrawal",
        //       "timestamp": "1645460496425",
        //       "commission": "0.13",
        //       "paymentMethod": "BLOCKCHAIN", // present in withdrawal/deposit
        //       "blockchainTransactionHash": "0x400ac905557c3d34638b1c60eba110b3ee0f97f4eb0f7318015ab76e7f16b7d6", // present in withdrawal/deposit
        //       "status": "PROCESSED"
        //     },
        //     {
        //       "id": "619031034",
        //       "balance": "8.17223588",
        //       "amount": "-0.01326294",
        //       "currency": "USD",
        //       "type": "exchange_commission",
        //       "timestamp": "1645460461235",
        //       "commission": "0.01326294",
        //       "status": "PROCESSED"
        //     },
        // ]
        //
        return this.parseLedger (response, currency, since, limit);
    }

    parseLedgerEntry (item, currency = undefined) {
        const id = this.safeString (item, 'id');
        const amountString = this.safeString (item, 'amount');
        const amount = Precise.stringAbs (amountString);
        const timestamp = this.safeInteger (item, 'timestamp');
        const currencyId = this.safeString (item, 'currency');
        const code = this.safeCurrencyCode (currencyId, currency);
        const feeCost = this.safeString (item, 'commission');
        let fee = undefined;
        if (feeCost !== undefined) {
            fee = { 'currency': code, 'cost': feeCost };
        }
        const direction = Precise.stringLt (amountString, '0') ? 'out' : 'in';
        const result = {
            'id': id,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'direction': direction,
            'account': undefined,
            'referenceId': this.safeString (item, 'blockchainTransactionHash'),
            'referenceAccount': undefined,
            'type': this.parseLedgerEntryType (this.safeString (item, 'type')),
            'currency': code,
            'amount': amount,
            'before': undefined,
            'after': this.safeString (item, 'balance'),
            'status': this.parseLedgerEntryStatus (this.safeString (item, 'status')),
            'fee': fee,
            'info': item,
        };
        return result;
    }

    parseLedgerEntryStatus (status) {
        const statuses = {
            'APPROVAL': 'pending',
            'PROCESSED': 'ok',
            'CANCELLED': 'canceled',
        };
        return this.safeString (statuses, status, status);
    }

    parseLedgerEntryType (type) {
        const types = {
            'deposit': 'transaction',
            'withdrawal': 'transaction',
            'exchange_commission': 'fee',
        };
        return this.safeString (types, type, type);
    }

    async fetchLeverage (symbol, params = {}) {
        /**
         * @method
         * @name currencycom#fetchLeverage
         * @description fetch the set leverage for a market
         * @param {str} symbol unified market symbol
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} a [leverage structure]{@link https://docs.ccxt.com/en/latest/manual.html#leverage-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        const response = await this.privateGetV2LeverageSettings (this.extend (request, params));
        //
        // {
        //     "values": [ 1, 2, 5, 10, ],
        //     "value": "10",
        // }
        //
        return this.safeNumber (response, 'value');
    }

    async fetchDepositAddress (code, params = {}) {
        /**
         * @method
         * @name currencycom#fetchDepositAddress
         * @description fetch the deposit address for a currency associated with this account
         * @param {str} code unified currency code
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {dict} an [address structure]{@link https://docs.ccxt.com/en/latest/manual.html#address-structure}
         */
        await this.loadMarkets ();
        const currency = this.currency (code);
        const request = {
            'coin': currency['id'],
        };
        const response = await this.privateGetV2DepositAddress (this.extend (request, params));
        //
        //     { "address":"0x97d64eb014ac779194991e7264f01c74c90327f0" }
        //
        return this.parseDepositAddress (response, currency);
    }

    parseDepositAddress (depositAddress, currency = undefined) {
        const address = this.safeString (depositAddress, 'address');
        this.checkAddress (address);
        currency = this.safeCurrency (undefined, currency);
        return {
            'currency': currency['code'],
            'address': address,
            'tag': undefined,
            'network': undefined,
            'info': depositAddress,
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + path;
        if (path === 'historicalTrades') {
            headers = {
                'X-MBX-APIKEY': this.apiKey,
            };
        }
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let query = this.urlencode (this.extend ({
                'timestamp': this.nonce (),
                'recvWindow': this.options['recvWindow'],
            }, params));
            const signature = this.hmac (this.encode (query), this.encode (this.secret));
            query += '&' + 'signature=' + signature;
            headers = {
                'X-MBX-APIKEY': this.apiKey,
            };
            if ((method === 'GET') || (method === 'DELETE')) {
                url += '?' + query;
            } else {
                body = query;
                headers['Content-Type'] = 'application/x-www-form-urlencoded';
            }
        } else {
            if (Object.keys (params).length) {
                url += '?' + this.urlencode (params);
            }
        }
        url = this.implodeHostname (url);
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async fetchPositions (symbols = undefined, params = {}) {
        /**
         * @method
         * @name currencycom#fetchPositions
         * @description fetch all open positions
         * @param {[str]|undefined} symbols list of unified market symbols
         * @param {dict} params extra parameters specific to the currencycom api endpoint
         * @returns {[dict]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
         */
        await this.loadMarkets ();
        const response = await this.privateGetV2TradingPositions (params);
        //
        // {
        //     "positions": [
        //       {
        //         "accountId": "109698017416453793",
        //         "id": "00a18490-0079-54c4-0000-0000803e73d3",
        //         "instrumentId": "45463225268524228",
        //         "orderId": "00a18490-0079-54c4-0000-0000803e73d2",
        //         "openQuantity": "13.6",
        //         "openPrice": "0.75724",
        //         "closeQuantity": "0.0",
        //         "closePrice": "0",
        //         "rpl": "-0.007723848",
        //         "rplConverted": "0",
        //         "upl": "-0.006664",
        //         "uplConverted": "-0.006664",
        //         "swap": "0",
        //         "swapConverted": "0",
        //         "fee": "-0.007723848",
        //         "dividend": "0",
        //         "margin": "0.2",
        //         "state": "ACTIVE",
        //         "currency": "USD",
        //         "createdTimestamp": "1645473877236",
        //         "openTimestamp": "1645473877193",
        //         "type": "NET",
        //         "cost": "2.0583600",
        //         "symbol": "XRP/USD_LEVERAGE"
        //       }
        //     ]
        // }
        //
        const data = this.safeValue (response, 'positions', []);
        return this.parsePositions (data, symbols);
    }

    parsePosition (position, market = undefined) {
        market = this.safeMarket (this.safeString (position, 'symbol'), market);
        const symbol = market['symbol'];
        const timestamp = this.safeNumber (position, 'createdTimestamp');
        const quantityRaw = this.safeString (position, 'openQuantity');
        const side = Precise.stringGt (quantityRaw, '0') ? 'long' : 'short';
        const quantity = Precise.stringAbs (quantityRaw);
        const entryPrice = this.safeNumber (position, 'openPrice');
        const unrealizedProfit = this.safeNumber (position, 'upl');
        const marginCoeff = this.safeString (position, 'margin');
        const leverage = Precise.stringDiv ('1', marginCoeff);
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'contracts': this.parseNumber (quantity),
            'contractSize': undefined,
            'entryPrice': entryPrice,
            'collateral': undefined,
            'side': side,
            // 'realizedProfit': this.safeNumber (position, 'rpl'),
            'unrealizedProfit': unrealizedProfit,
            'leverage': leverage,
            'percentage': undefined,
            'marginMode': undefined,
            'notional': undefined,
            'markPrice': undefined,
            'liquidationPrice': undefined,
            'initialMargin': undefined,
            'initialMarginPercentage': undefined,
            'maintenanceMargin': this.parseNumber (marginCoeff),
            'maintenanceMarginPercentage': undefined,
            'marginRatio': undefined,
            'info': position,
        };
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if ((httpCode === 418) || (httpCode === 429)) {
            throw new DDoSProtection (this.id + ' ' + httpCode.toString () + ' ' + reason + ' ' + body);
        }
        // error response in a form: { "code": -1013, "msg": "Invalid quantity." }
        // following block cointains legacy checks against message patterns in "msg" property
        // will switch "code" checks eventually, when we know all of them
        if (httpCode >= 400) {
            if (body.indexOf ('Price * QTY is zero or less') >= 0) {
                throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body);
            }
            if (body.indexOf ('LOT_SIZE') >= 0) {
                throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body);
            }
            if (body.indexOf ('PRICE_FILTER') >= 0) {
                throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body);
            }
        }
        if (response === undefined) {
            return; // fallback to default error handler
        }
        //
        //     {"code":-1128,"msg":"Combination of optional parameters invalid."}
        //
        const errorCode = this.safeString (response, 'code');
        if ((errorCode !== undefined) && (errorCode !== '0')) {
            const feedback = this.id + ' ' + this.json (response);
            this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback);
            const message = this.safeString (response, 'msg');
            this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
            throw new ExchangeError (feedback);
        }
    }
};