text: 'use strict';
  
  //  ---------------------------------------------------------------------------
  
  const Exchange = require ('./base/Exchange');
  const { ExchangeError, ArgumentsRequired, InsufficientFunds, OrderNotFound, InvalidOrder, AuthenticationError, InvalidAddress } = require ('./base/errors');
  
  module.exports = class max extends Exchange {
      describe () {
          return this.deepExtend (super.describe (), {
              'id': 'max',
              'name': 'Max',
              'countries': [ 'TW' ],
              'rateLimit': 1200,
              'certified': false,
              'urls': {
                  'logo': '',
                  'api': {
                      'public': 'https://max-api.maicoin.com',
                      'private': 'https://max-api.maicoin.com',
                  },
                  'www': 'https://max.maicoin.com',
                  'doc': ['https://max.maicoin.com/documents/api'],
                  'fees': 'https://max.maicoin.com/docs/fees',
              },
              'has': {
                  'cancelAllOrders': true,
                  'cancelOrder': true,
                  'cancelOrders': false,
                  'CORS': false,
                  'createDepositAddress': true,
                  'createLimitOrder': true,
                  'createMarketOrder': true,
                  'createOrder': true,
                  'deposit': false,
                  'editOrder': 'emulated',
                  'fetchBalance': true,
                  'fetchBidsAsks': false,
                  'fetchClosedOrders': true,
                  'fetchCurrencies': true,
                  'fetchDepositAddress': true,
                  'fetchDeposits': true,
                  'fetchFundingFees': true,
                  'fetchL2OrderBook': false,
                  'fetchLedger': false,
                  'fetchMarkets': true,
                  'fetchMyTrades': true,
                  'fetchOHLCV': true,
                  'fetchOpenOrders': true,
                  'fetchOrder': true,
                  'fetchOrderBook': true,
                  'fetchOrderBooks': false,
                  'fetchOrders': true,
                  'fetchOrderTrades': false,
                  'fetchStatus': 'emulated',
                  'fetchTicker': true,
                  'fetchTickers': true,
                  'fetchTime': true,
                  'fetchTrades': true,
                  'fetchTradingFee': false,
                  'fetchTradingFees': true,
                  'fetchTradingLimits': false,
                  'fetchTransactions': false,
                  'fetchWithdrawals': true,
                  'privateAPI': true,
                  'publicAPI': true,
                  'withdraw': false,
              },
              'api': {
                  'public': {
                      'get': [
                          'summary',
                          'markets',
                          'currencies',
                          'tickers/{market_id}',
                          'tickers',
                          'withdrawal/constraint',
                          'depth',
                          'trades',
                          'k',
                          'timestamp',
                          'vip_levels',
                          'vip_levels/{level}',
                      ],
                  },
                  'private': {
                      'get': [
                          'members/profile',
                          'members/accounts/{currency_id}',
                          'members/accounts',
                          'members/vip_level',
                          'members/me',
                          'deposits',
                          'deposit',
                          'deposit_addresses',
                          'withdrawals',
                          'withdrawal',
                          'withdrawal_addresses',
                          'orders',
                          'order',
                          'trades/my/of_order',
                          'trades/my',
                          'internal_transfers',
                          'internal_transfer',
                          'rewards/{reward_type}',
                          'rewards',
                          'max_rewards/yesterday',
                      ],
                      'post': [
                          'deposit_addresses',
                          'orders/clear',
                          'orders',
                          'orders/multi/onebyone',
                          'order/delete',
                      ],
                  },
              },
              'timeframes': {
                  '1m': '1',
                  '5m': '5',
                  '15m': '15',
                  '30m': '30',
                  '1h': '60',
                  '2h': '120',
                  '4h': '240',
                  '6h': '360',
                  '12h': '720',
                  '1d': '1440',
                  '3d': '4320',
                  '1w': '10080',
              },
              'fees': {
                  'trading': {
                      'tierBased': true,
                      'percentage': true,
                      'maker': this.parseNumber ('0.0005'),
                      'taker': this.parseNumber ('0.0015'),
                      'tiers': {
                          // https://max-vip-en.maicoin.com/
                          'taker': [
                              // volume in TWD
                              [this.parseNumber ('0'), this.parseNumber ('0.0015')],
                              [this.parseNumber ('3000000'), this.parseNumber ('0.00135')],
                              [this.parseNumber ('10000000'), this.parseNumber ('0.0012')],
                              [this.parseNumber ('30000000'), this.parseNumber ('0.00105')],
                              [this.parseNumber ('150000000'), this.parseNumber ('0.0009')],
                              [this.parseNumber ('300000000'), this.parseNumber ('0.00075')],
                              [this.parseNumber ('600000000'), this.parseNumber ('0.0006')],
                              [this.parseNumber ('1000000000'), this.parseNumber ('0.00055')],
                              [this.parseNumber ('1500000000'), this.parseNumber ('0.0005')],
                              [this.parseNumber ('2000000000'), this.parseNumber ('0.00045')],
                          ],
                          'maker': [
                              // volume in TWD
                              [this.parseNumber ('0'), this.parseNumber ('0.0005')],
                              [this.parseNumber ('3000000'), this.parseNumber ('0.00045')],
                              [this.parseNumber ('10000000'), this.parseNumber ('0.0004')],
                              [this.parseNumber ('30000000'), this.parseNumber ('0.00035')],
                              [this.parseNumber ('150000000'), this.parseNumber ('0.0003')],
                              [this.parseNumber ('300000000'), this.parseNumber ('0.00025')],
                              [this.parseNumber ('600000000'), this.parseNumber ('0.0002')],
                              [this.parseNumber ('1000000000'), this.parseNumber ('0.0001')],
                              [this.parseNumber ('1500000000'), this.parseNumber ('0.0000')],
                              [this.parseNumber ('2000000000'), this.parseNumber ('0.0000')],
                          ],
                      },
                  },
                  'funding': {
                      'tierBased': false,
                      'percentage': false,
                      'withdraw': {},
                      'deposit': {},
                  },
              },
              'requiredCredentials': {
                  'apiKey': true,
                  'secret': true,
              },
              'commonCurrencies': {
              },
              'options': {
                  'timeDifference': 0, // the difference between system clock and Max clock
                  'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation
                  // TODO: use option before max api providing minimum amount of currenncy in orders
                  'minimumAmountOfCurrencies': {
                      'ADA': 4.0,
                      'BTC': 0.00015,
                      'BCNT': 170.0,
                      'BCH': 0.01,
                      'COMP': 0.03,
                      'DOGE': 33.0,
                      'DOT': 0.3,
                      'ETH': 0.002,
                      'GRT': 9.0,
                      'LINK': 0.3,
                      'LTC': 0.045,
                      'MAX': 21.0,
                      'MITH': 190.0,
                      'SAND': 3.0,
                      'SHIB': 300000.0,
                      'SOL': 0.045,
                      'TWD': 250.0,
                      'USDC': 9.0,
                      'USDT': 8.0,
                      'XRP': 9.0,
                  },
              },
              'exceptions': {
                  '2002': InvalidOrder, // Order volume too small
                  '2003': OrderNotFound, // Failed to cancel order
                  '2004': OrderNotFound, // Order doesn't exist
                  '2005': AuthenticationError, // Signature is incorrect.
                  '2006': AuthenticationError, // The nonce has already been used by access key.
                  '2007': AuthenticationError, // The nonce is invalid. (30 secconds difference from server time)
                  '2008': AuthenticationError, // The access key does not exist.
                  '2009': AuthenticationError, // The access key is disabled.
                  '2011': AuthenticationError, // Requested API is out of access key scopes.
                  '2014': AuthenticationError, // Payload is not consistent with body or wrong path in payload.
                  '2015': AuthenticationError, // Payload is invalid
                  '2016': InvalidOrder, // amount_too_small
                  '2018': InsufficientFunds, // cannot lock funds
              },
          });
      }
  
      async fetchTime (params = {}) {
          const response = await this.publicGetTimestamp ();
          return parseInt (response, 10) * 1000;
      }
  
      nonce () {
          return this.milliseconds () - this.options['timeDifference'];
      }
  
      async loadTimeDifference () {
          const serverTimestamp = await this.fetchTime ();
          const after = this.milliseconds ();
          this.options['timeDifference'] = after - serverTimestamp;
          return this.options['timeDifference'];
      }
  
      async fetchFundingFees (params = {}) {
          const response = await this.publicGetWithdrawalConstraint (params);
          //
          //     [
          //       {
          //          "currency": "eth",
          //          "fee": "0.002",
          //          "ratio": "0.0",
          //          "min_amount": "0.02"
          //        }
          //     ]
          //
          const withdrawFees = {};
          for (let i = 0; i < response.length; i++) {
              const id = this.safeValue (response[i], 'currency');
              const code = this.safeCurrencyCode (id);
              withdrawFees[code] = this.safeNumber (response[i], 'fee');
          }
          return {
              'withdraw': withdrawFees,
              'deposit': {},
              'info': response,
          };
      }
  
      async fetchCurrencies (params = {}) {
          const currenciesResponse = await this.publicGetCurrencies (params);
          const withdrawalConstraintResponse = await this.publicGetWithdrawalConstraint ();
          const summaryResponse = await this.publicGetSummary ();
          const withdrawalConstraint = this.indexBy (withdrawalConstraintResponse, 'currency');
          const minimumAmountOfCurrencies = this.safeValue (this.options, 'minimumAmountOfCurrencies');
          const coins = this.safeValue (summaryResponse, 'coins');
          const result = {};
          for (let i = 0; i < currenciesResponse.length; i++) {
              const currency = currenciesResponse[i];
              const id = this.safeString (currency, 'id');
              const code = this.safeCurrencyCode (id);
              const coin = this.safeValue (coins, id);
              const name = this.safeString (coin, 'name', code);
              const fiat = id === 'twd' ? true : false;
              const precision = this.safeInteger (currency, 'precision');
              const withdrawal = this.safeValue (withdrawalConstraint, id);
              const withdrawalFee = this.safeNumber (withdrawal, 'fee');
              const withdrawalLimitMin = this.safeNumber (withdrawal, 'min_amount');
              const minAmount = this.safeNumber (minimumAmountOfCurrencies, code);
              const withdrawEnable = this.safeString (coin, 'withdraw');
              const depositEnable = this.safeString (coin, 'deposit');
              const tradeEnable = this.safeString (coin, 'trade');
              const active = id === 'twd' ? true : (withdrawEnable === 'ON' && depositEnable === 'ON' && tradeEnable === 'ON');
              result[code] = {
                  'id': id,
                  'code': code,
                  'name': name,
                  'active': active,
                  'fiat': fiat,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': minAmount,
                          'max': undefined,
                      },
                      'withdraw': {
                          'min': withdrawalLimitMin,
                          'max': undefined,
                      },
                  },
                  'funding': {
                      'withdraw': {
                          'fee': withdrawalFee,
                      },
                      'deposit': {
                          'fee': undefined,
                      },
                  },
                  'info': currency,
              };
          }
          return result;
      }
  
      async fetchMarkets (params = {}) {
          //
          //    {
          //      id: 'btcusdt',
          //      name: 'BTC/USDT',
          //      base_unit: 'btc',
          //      base_unit_precision: 6,
          //      min_base_amount: 0.001,
          //      quote_unit: 'usdt',
          //      quote_unit_precision: 2,
          //      min_quote_amount: 8.0
          //    }
          //
          const markets = await this.publicGetMarkets ();
          const summaryResponse = await this.publicGetSummary ();
          const coins = this.safeValue (summaryResponse, 'coins');
          if (this.options['adjustForTimeDifference']) {
              await this.loadTimeDifference ();
          }
          const result = [];
          for (let i = 0; i < markets.length; i++) {
              const market = markets[i];
              const id = this.safeString (market, 'id');
              const baseId = this.safeString (market, 'base_unit');
              const quoteId = this.safeString (market, 'quote_unit');
              const base = this.safeCurrencyCode (baseId);
              const quote = this.safeCurrencyCode (quoteId);
              const symbol = base + '/' + quote;
              const precision = {
                  'amount': this.safeInteger (market, 'base_unit_precision'),
                  'price': this.safeInteger (market, 'quote_unit_precision'),
              };
              const isBaseTradeEnable = (baseId === 'twd' ? 'ON' : this.safeValue (this.safeValue (coins, baseId), 'trade')) === 'ON';
              const isQuoteTradeEnable = (quoteId === 'twd' ? 'ON' : this.safeValue (this.safeValue (coins, quoteId), 'trade')) === 'ON';
              const active = isBaseTradeEnable && isQuoteTradeEnable;
              const baseMinSize = this.safeNumber (market, 'min_base_amount');
              const quoteMinSize = this.safeNumber (market, 'min_quote_amount');
              const entry = {
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'info': market,
                  'active': active,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': baseMinSize,
                          'max': undefined,
                      },
                      'price': {
                          'min': Math.pow (10, -precision['price']),
                          'max': undefined,
                      },
                      'cost': {
                          'min': quoteMinSize,
                          'max': undefined,
                      },
                  },
              };
              result.push (entry);
          }
          return result;
      }
  
      calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
          const market = this.markets[symbol];
          const rate = market[takerOrMaker];
          let cost = amount * rate;
          let key = 'quote';
          if (side === 'sell') {
              cost *= price;
          } else {
              key = 'base';
          }
          const code = market[key];
          const currency = this.safeValue (this.currencies, code);
          if (currency !== undefined) {
              const precision = this.safeInteger (currency, 'precision');
              if (precision !== undefined) {
                  cost = parseFloat (this.currencyToPrecision (code, cost));
              }
          }
          return {
              'type': takerOrMaker,
              'currency': market[key],
              'rate': rate,
              'cost': cost,
          };
      }
  
      async fetchBalance (params = {}) {
          await this.loadMarkets ();
          const response = await this.privateGetMembersAccounts (params);
          const result = { 'info': response };
          for (let i = 0; i < response.length; i++) {
              const balance = response[i];
              const currencyId = this.safeString (balance, 'currency');
              const code = this.safeCurrencyCode (currencyId);
              const account = this.account ();
              account['free'] = this.safeString (balance, 'balance');
              account['used'] = this.safeString (balance, 'locked');
              result[code] = account;
          }
          return this.parseBalance (result, false);
      }
  
      async fetchOrderBook (symbol, limit = undefined, params = {}) {
          await this.loadMarkets ();
          const market = this.market (symbol);
          const request = {
              'market': market['id'],
          };
          if (limit !== undefined) {
              request['limit'] = limit; // default = 300
          }
          const response = await this.publicGetDepth (this.extend (request, params));
          const timestamp = this.safeTimestamp (response, 'timestamp');
          const orderbook = this.parseOrderBook (response, symbol, timestamp);
          return orderbook;
      }
  
      parseTicker (ticker, market = undefined) {
          const timestamp = this.safeTimestamp (ticker, 'at');
          let symbol = undefined;
          if (market !== undefined) {
              symbol = market['symbol'];
          } else if ('symbol' in ticker) {
              const marketId = this.safeString (ticker, 'symbol');
              if (marketId !== undefined) {
                  if (marketId in this.markets_by_id) {
                      market = this.markets_by_id[marketId];
                      symbol = market['symbol'];
                  }
              }
          }
          const last = this.safeNumber (ticker, 'last');
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': this.iso8601 (timestamp),
              'high': this.safeNumber (ticker, 'high'),
              'low': this.safeNumber (ticker, 'low'),
              'bid': this.safeNumber (ticker, 'buy'),
              'bidVolume': undefined,
              'ask': this.safeNumber (ticker, 'sell'),
              'askVolume': undefined,
              'vwap': undefined,
              'open': this.safeNumber (ticker, 'open'),
              'close': last,
              'last': last,
              'previousClose': undefined,
              'change': undefined,
              'percentage': undefined,
              'average': undefined,
              'baseVolume': this.safeNumber (ticker, 'vol'),
              'quoteVolume': undefined,
              'info': ticker,
          };
      }
  
      async fetchTicker (symbol, params = {}) {
          await this.loadMarkets ();
          const market = this.market (symbol);
          const response = await this.publicGetTickersMarketId (this.extend ({
              'market_id': market['id'],
          }, params));
          response['symbol'] = market['id'];
          return this.parseTicker (response, market);
      }
  
      async fetchTickers (symbols = undefined, params = {}) {
          await this.loadMarkets ();
          const response = await this.publicGetTickers (params);
          const tickerKeys = Object.keys (response);
          const result = {};
          for (let i = 0; i < tickerKeys.length; i++) {
              const key = tickerKeys[i];
              response[key]['symbol'] = key;
              const ticker = this.parseTicker (response[key]);
              const symbol = ticker['symbol'];
              result[symbol] = ticker;
          }
          return this.filterByArray (result, 'symbol', symbols);
      }
  
      parseOHLCV (ohlcv, market = undefined) {
          return [
              this.safeTimestamp (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 = {}) {
          await this.loadMarkets ();
          const market = this.market (symbol);
          const request = {
              'market': market['id'],
              'period': this.timeframes[timeframe],
          };
          if (since !== undefined) {
              request['timestamp'] = parseInt (since / 1000);
          }
          if (limit !== undefined) {
              request['limit'] = limit; // default = 30
          }
          const response = await this.publicGetK (this.extend (request, params));
          return this.parseOHLCVs (response, market, timeframe, since, limit);
      }
  
      parseDepositAddress (code, response) {
          if (response.length < 1) {
              throw new InvalidAddress (this.id + ' fetchDepositAddress ' + code + ' returned empty address.');
          }
          const depositAddress = response[0];
          let address = this.safeString (depositAddress, 'address');
          if (address === 'suspended') {
              throw new InvalidAddress (this.id + ' fetchDepositAddress ' + code + ' returned an suspended address.');
          }
          let tag = undefined;
          if (code === 'XRP' && address) {
              const splitted = address.split ('?dt=');
              address = splitted[0];
              tag = splitted[1];
          }
          this.checkAddress (address);
          return {
              'info': response,
              'currency': code,
              'address': address,
              'tag': tag,
          };
      }
  
      async createDepositAddress (code, params = {}) {
          await this.loadMarkets ();
          const currency = this.currency (code);
          const request = {
              'currency': currency['id'],
          };
          const response = await this.privatePostDepositAddresses (this.extend (request, params));
          return this.parseDepositAddress (code, response);
      }
  
      async fetchDepositAddress (code, params = {}) {
          await this.loadMarkets ();
          const currency = this.currency (code);
          const request = {
              'currency': currency['id'],
          };
          const response = await this.privateGetDepositAddresses (this.extend (request, params));
          return this.parseDepositAddress (code, response);
      }
  
      parseTransactionStatusByType (status, type = undefined) {
          const statusesByType = {
              'deposit': {
                  'submitting': 'pending',
                  'cancelled': 'canceled',
                  'submitted': 'pending',
                  'suspended': 'pending',
                  'rejected': 'failed',
                  'accepted': 'ok',
                  'checking': 'pending',
                  'refunded': 'failed',
                  'suspect': 'pending',
                  'refund_cancelled': 'ok',
              },
              'withdrawal': {
                  'submitting': 'pending',
                  'submitted': 'pending',
                  'rejected': 'failed',
                  'accepted': 'pending',
                  'suspect': 'pending',
                  'approved': 'pending',
                  'processing': 'pending',
                  'retryable': 'pending',
                  'sent': 'pending',
                  'canceled': 'canceled',
                  'failed': 'failed',
                  'pending': 'pending',
                  'confirmed': 'ok',
                  'kgi_manually_processing': 'pending',
                  'kgi_instruction_sent': 'pending',
                  'kgi_manually_confirmed': 'ok',
                  'kgi_possible_failed': 'pending',
                  'sygna_verifying': 'pending',
              },
          };
          const statuses = this.safeValue (statusesByType, type, {});
          return this.safeString (statuses, status, status);
      }
  
      parseTransaction (transaction, currency = undefined) {
          //
          // fetchDeposits
          //
          //   {
          //      currency: 'eth',
          //      currency_version: 'eth',
          //      amount: '1.12345',
          //      fee: '0.123',
          //      txid: '0x123456789abcdef...',
          //      created_at: 1584584162,
          //      updated_at: 1584584199,
          //      confirmations: '12',
          //      state: 'accepted',
          //      type: 'deposit'
          //    }
          //
          // fetchWithdrawals
          //
          //    {
          //      uuid: '0123456789...',
          //      currency: 'eth',
          //      currency_version: 'eth',
          //      amount: '1.12345',
          //      fee: '0.123',
          //      fee_currency: 'max',
          //      txid: '0x123456789abcdef...',
          //      created_at: 1597164616,
          //      updated_at: 1598847790,
          //      state: 'confirmed',
          //      type: 'withdrawal'
          //    }
          //
          const txid = this.safeString (transaction, 'txid');
          const id = this.safeString (transaction, 'uuid', txid);
          const currencyId = this.safeString (transaction, 'currency');
          const code = this.safeCurrencyCode (currencyId, currency);
          const timestamp = this.safeTimestamp (transaction, 'created_at');
          const updated = this.safeTimestamp (transaction, 'updated_at');
          const amount = this.safeNumber (transaction, 'amount');
          let feeCurrencyId = this.safeString (transaction, 'fee_currency');
          let feeCurrency = undefined;
          if (feeCurrencyId in this.currencies_by_id) {
              feeCurrency = this.currencies_by_id[feeCurrencyId];
          }
          if (feeCurrency !== undefined) {
              feeCurrencyId = feeCurrency['code'];
          } else {
              feeCurrencyId = this.safeCurrencyCode (feeCurrencyId);
          }
          const fee = {
              'cost': this.safeNumber (transaction, 'fee'),
              'currency': feeCurrencyId,
          };
          const type = this.safeString (transaction, 'type');
          const status = this.parseTransactionStatusByType (this.safeString (transaction, 'state'), type);
          return {
              'info': transaction,
              'id': id,
              'txid': txid,
              'timestamp': timestamp,
              'datetime': this.iso8601 (timestamp),
              'address': undefined,
              'tag': undefined,
              'type': type,
              'amount': amount,
              'currency': code,
              'status': status,
              'updated': updated,
              'fee': fee,
          };
      }
  
      async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
          await this.loadMarkets ();
          let currency = undefined;
          const request = {};
          if (code !== undefined) {
              currency = this.currency (code);
              request['currency'] = currency['id'];
          }
          if (since !== undefined) {
              request['from'] = Math.floor (parseInt (since, 10) / 1000);
          }
          if (limit !== undefined) {
              request['limit'] = limit;
          }
          const response = await this.privateGetWithdrawals (this.extend (request, params));
          for (let i = 0; i < response.length; i++) {
              response[i]['type'] = 'withdrawal';
          }
          return this.parseTransactions (response, currency, since, limit);
      }
  
      async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
          await this.loadMarkets ();
          let currency = undefined;
          const request = {};
          if (code !== undefined) {
              currency = this.currency (code);
              request['currency'] = currency['id'];
          }
          if (since !== undefined) {
              request['from'] = Math.floor (parseInt (since, 10) / 1000);
          }
          if (limit !== undefined) {
              request['limit'] = limit;
          }
          const response = await this.privateGetDeposits (this.extend (request, params));
          for (let i = 0; i < response.length; i++) {
              response[i]['type'] = 'deposit';
          }
          return this.parseTransactions (response, currency, since, limit);
      }
  
      parseSide (side) {
          if (!side) {
              return undefined;
          }
          if (side === 'buy' || side === 'sell') {
              return side;
          }
          if (side === 'bid') {
              return 'buy';
          }
          if (side === 'ask') {
              return 'sell';
          }
          // self-trade
          return side;
      }
  
      parseTrade (trade, market = undefined) {
          //
          // public trades
          //
          //    {
          //        "id": 4813073,
          //        "price": "3980.0",
          //        "volume": "0.000264",
          //        "funds": "1.05072",
          //        "market": "btcusdt",
          //        "market_name": "BTC/USDT",
          //        "created_at": 1553341297,
          //        "created_at_in_ms": 1553341297000,
          //        "side": "bid"
          //    }
          //
          //
          // private trades
          //
          //    {
          //        "id": 3175037,
          //        "price": "3986.97",
          //        "volume": "0.125",
          //        "funds": "498.37125",
          //        "market": "btcusdt",
          //        "market_name": "BTC/USDT",
          //        "created_at": 1543941724,
          //        "created_at_in_ms": 1543941724000,
          //        "side": "ask",
          //        "fee": "0.747557",
          //        "fee_currency": "usdt",
          //        "order_id": 18298466
          //        "info": {
          //            "maker": "ask",
          //            "ask": {"fee": "0.747557", "fee_currency": "usdt", "order_id": 18298466},
          //            "bid": null
          //        }
          //    }
          const timestamp = this.safeTimestamp (trade, 'created_at');
          const priceString = this.safeString (trade, 'price');
          const amountString = this.safeString (trade, 'volume');
          const id = this.safeString (trade, 'id');
          const side = this.safeString (trade, 'side');
          const orderId = this.safeString (trade, 'order_id');
          const costString = this.safeString (trade, 'funds');
          let fee = undefined;
          const feeCurrencyId = this.safeString (trade, 'fee_currency');
          const feeCode = this.safeCurrencyCode (feeCurrencyId);
          const feeCostString = this.safeString (trade, 'fee');
          if (feeCostString !== undefined) {
              fee = {
                  'cost': feeCostString,
                  'currency': feeCode,
              };
          }
          let takerOrMaker = undefined;
          const tradeInfo = this.safeValue (trade, 'info');
          if (tradeInfo !== undefined) {
              const tradeMakerSide = this.safeString (tradeInfo, 'maker');
              if (tradeMakerSide !== undefined && side !== undefined) {
                  takerOrMaker = (tradeMakerSide === side) ? 'maker' : 'taker';
              }
          }
          const marketId = this.safeString (trade, 'market');
          const symbol = this.safeSymbol (marketId, market);
          return this.safeTrade ({
              'info': trade,
              'timestamp': timestamp,
              'datetime': this.iso8601 (timestamp),
              'symbol': symbol,
              'id': id,
              'order': orderId,
              'type': undefined,
              'takerOrMaker': takerOrMaker,
              'side': this.parseSide (side),
              'price': priceString,
              'amount': amountString,
              'cost': costString,
              'fee': fee,
          });
      }
  
      async fetchTradingFees (params = {}) {
          //    {
          //      current_vip_level: {
          //        level: 0,
          //        minimum_trading_volume: 0,
          //        minimum_staking_volume: 0,
          //        maker_fee: 0.0005,
          //        taker_fee: 0.0015
          //      },
          //      next_vip_level: {
          //        level: 1,
          //        minimum_trading_volume: 3000000,
          //        minimum_staking_volume: 500,
          //        maker_fee: 0.00045,
          //        taker_fee: 0.00135
          //      }
          //    }
          await this.loadMarkets ();
          const response = await this.privateGetMembersVipLevel (params);
          const level = this.safeValue (response, 'current_vip_level', {});
          return {
              'info': response,
              'maker': this.safeNumber (level, 'maker_fee'),
              'taker': this.safeNumber (level, 'taker_fee'),
          };
      }
  
      async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
          await this.loadMarkets ();
          const market = this.market (symbol);
          const request = {
              'market': market['id'],
          };
          // since is not supported
          // if (since !== undefined) {
          //     request['timestamp'] = Math.floor (parseInt (since, 10) / 1000);
          // }
          if (limit !== undefined) {
              request['limit'] = limit; // default = 50, maximum = 1000
          }
          const response = await this.publicGetTrades (this.extend (request, params));
          return this.parseTrades (response, market, since, limit);
      }
  
      async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
          if (symbol === undefined) {
              throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument');
          }
          await this.loadMarkets ();
          const market = this.market (symbol);
          const request = {
              'market': market['id'],
          };
          if (limit !== undefined) {
              request['limit'] = limit;
          }
          // since is not supported
          // if (since !== undefined) {
          //     request['timestamp'] = Math.floor (parseInt (since, 10) / 1000);
          // }
          const response = await this.privateGetTradesMy (this.extend (request, params));
          return this.parseTrades (response, market, since, limit);
      }
  
      async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
          const request = {
              'id': id,
              // 'client_oid': client_oid, // user specific order id in RFC 4122 format. only persist for 24 hours
          };
          const response = await this.privateGetTradesMyOfOrder (this.extend (request, params));
          return this.parseTrades (response);
      }
  
      parseOrderStatus (status) {
          const statuses = {
              'wait': 'open',
              'cancel': 'canceled',
              'done': 'closed',
              'convert': 'open',
              'finalizing': 'open',
              'failed': 'canceled',
          };
          return (status in statuses) ? statuses[status] : status;
      }
  
      parseOrder (order, market = undefined) {
          //   {
          //     id: 12345678,
          //     client_oid: null,
          //     side: 'buy',
          //     ord_type: 'limit',
          //     price: '0.01',
          //     stop_price: null,
          //     avg_price: '0.0',
          //     state: 'wait',
          //     market: 'maxusdt',
          //     created_at: 1604983942,
          //     created_at_in_ms: 1604983942224,
          //     updated_at: 1604983942,
          //     updated_at_in_ms: 1604983942224,
          //     volume: '2000.0',
          //     remaining_volume: '2000.0',
          //     executed_volume: '0.0',
          //     trades_count: 0,
          //     group_id: 1
          //   }
          const status = this.parseOrderStatus (this.safeString (order, 'state'));
          const marketId = this.safeString (order, 'market');
          const symbol = this.safeSymbol (marketId, market);
          const timestamp = this.safeTimestamp (order, 'created_at');
          const id = this.safeString (order, 'id');
          const clientOrderId = this.safeString (order, 'client_oid');
          const averageString = this.safeString (order, 'avg_price');
          const priceString = this.safeString (order, 'price', averageString);
          const amountString = this.safeString (order, 'volume');
          const filledString = this.safeString (order, 'executed_volume');
          const remainingString = this.safeString (order, 'remaining_volume');
          const type = this.safeString (order, 'ord_type'); // limit, market, stop_limit, stop_market, post_only, ioc_limit
          const stopPrice = this.safeNumber (order, 'stop_price');
          const side = this.safeString (order, 'side');
          let timeInForce = undefined;
          if (type === 'ioc_limit') {
              timeInForce = 'IOC';
          }
          const postOnly = type === 'post_only';
          return this.safeOrder2 ({
              'info': order,
              'id': id,
              'clientOrderId': clientOrderId,
              'timestamp': timestamp,
              'datetime': this.iso8601 (timestamp),
              'lastTradeTimestamp': undefined,
              'symbol': symbol,
              'type': type,
              'timeInForce': timeInForce,
              'postOnly': postOnly,
              'side': side,
              'price': priceString,
              'stopPrice': stopPrice,
              'amount': amountString,
              'cost': undefined,
              'average': averageString,
              'filled': filledString,
              'remaining': remainingString,
              'status': status,
              'fee': undefined,
              'trades': undefined,
          }, market);
      }
  
      async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
          await this.loadMarkets ();
          const market = this.market (symbol);
          const lowercaseType = type.toLowerCase ();
          const order = {
              'market': market['id'],
              'volume': this.amountToPrecision (symbol, amount),
              'ord_type': lowercaseType,
              'side': side,
          };
          const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId');
          if (clientOrderId !== undefined) {
              order['client_oid'] = clientOrderId;
              params = this.omit (params, [ 'client_oid', 'clientOrderId' ]);
          }
          let priceIsRequired = false;
          let stopPriceIsRequired = false;
          if (lowercaseType === 'limit' || lowercaseType === 'stop_limit' || lowercaseType === 'post_only' || lowercaseType === 'ioc_limit') {
              priceIsRequired = true;
          }
          if (lowercaseType === 'stop_limit' || lowercaseType === 'stop_market') {
              stopPriceIsRequired = true;
          }
          if (priceIsRequired) {
              if (price === undefined) {
                  throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + lowercaseType + ' order');
              }
              order['price'] = this.priceToPrecision (symbol, price);
          }
          if (stopPriceIsRequired) {
              const stop_price = this.safeNumber (params, 'stop_price');
              if (stop_price === undefined) {
                  throw new InvalidOrder (this.id + ' createOrder method requires a stop_price extra param for a ' + lowercaseType + ' order');
              }
              params = this.omit (params, 'stop_price');
              order['stop_price'] = this.priceToPrecision (symbol, stop_price);
          }
          const response = await this.privatePostOrders (this.extend (order, params));
          return this.parseOrder (response, market);
      }
  
      async cancelAllOrders (symbol = undefined, params = {}) {
          const request = {};
          if (symbol !== undefined) {
              await this.loadMarkets ();
              const marketId = this.marketId (symbol);
              request['market'] = marketId;
          }
          return this.privatePostOrdersClear (this.extend (request, params));
      }
  
      async cancelOrder (id, symbol = undefined, params = {}) {
          const request = {};
          const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId');
          if (clientOrderId !== undefined) {
              request['client_oid'] = clientOrderId;
              params = this.omit (params, [ 'client_oid', 'clientOrderId' ]);
          } else {
              request['id'] = id;
          }
          const response = await this.privatePostOrderDelete (this.extend (request, params));
          return this.parseOrder (response);
      }
  
      async fetchOrder (id, symbol = undefined, params = {}) {
          await this.loadMarkets ();
          const request = {};
          const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId');
          if (clientOrderId !== undefined) {
              request['client_oid'] = clientOrderId;
              params = this.omit (params, [ 'client_oid', 'clientOrderId' ]);
          } else {
              request['id'] = id;
          }
          const response = await this.privateGetOrder (this.extend (request, params));
          return this.parseOrder (response);
      }
  
      async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
          if (symbol === undefined) {
              throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument');
          }
          await this.loadMarkets ();
          const market = this.market (symbol);
          const request = {
              'market': market['id'],
          };
          if (limit !== undefined) {
              request['limit'] = limit;
          }
          // since is not supported
          // if (since !== undefined) {
          //     request['timestamp'] = Math.floor (parseInt (since, 10) / 1000);
          // }
          const response = await this.privateGetOrders (this.extend (request, params));
          return this.parseOrders (response, market, since, limit);
      }
  
      async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
          return this.fetchOrders (symbol, since, limit, this.extend (params, { 'state': ['cancel', 'done', 'failed'] }));
      }
  
      async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
          return this.fetchOrders (symbol, since, limit, this.extend (params, { 'state': ['wait', 'convert', 'finalizing'] }));
      }
  
      sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
          let newParams = params;
          const request = '/api/v2/' + this.implodeParams (path, params);
          let url = this.urls['api'][api];
          url += request;
          if (!headers) {
              headers = {};
          }
          headers['X-MAX-AGENT'] = 'ccxt';
          if (api === 'private') {
              this.checkRequiredCredentials ();
              const nonce = this.nonce ();
              newParams = this.extend (params, {
                  'nonce': nonce,
              });
              const paramsWithPath = this.extend (params, {
                  'nonce': nonce,
                  'path': request,
              });
              const payload = this.stringToBase64 (this.encode (this.json (paramsWithPath)));
              const signature = this.hmac (payload, this.encode (this.secret));
              headers = this.extend (headers, {
                  'X-MAX-ACCESSKEY': this.apiKey,
                  'X-MAX-PAYLOAD': this.decode (payload),
                  'X-MAX-SIGNATURE': signature,
              });
          }
          if (method === 'GET' || method === 'DELETE') {
              if (!this.isEmpty (newParams)) {
                  const newParamsIsArray = {};
                  const newParamsOthers = {};
                  const newParamsKeys = Object.keys (newParams);
                  for (let i = 0; i < newParamsKeys.length; i++) {
                      const key = newParamsKeys[i];
                      if (Array.isArray (newParams[key])) {
                          newParamsIsArray[key] = newParams[key];
                      } else {
                          newParamsOthers[key] = newParams[key];
                      }
                  }
                  url += '?';
                  if (!this.isEmpty (newParamsOthers)) {
                      url += this.urlencode (newParamsOthers);
                  }
                  if (!this.isEmpty (newParamsOthers) && !this.isEmpty (newParamsIsArray)) {
                      url += '&';
                  }
                  if (!this.isEmpty (newParamsIsArray)) {
                      const result = [];
                      const newParamsIsArrayKeys = Object.keys (newParamsIsArray);
                      for (let i = 0; i < newParamsIsArrayKeys.length; i++) {
                          const key = newParamsIsArrayKeys[i];
                          for (let j = 0; j < newParamsIsArray[key].length; j++) {
                              result.push (key + '%5B%5D=' + newParamsIsArray[key][j]);
                          }
                      }
                      url += result.join ('&');
                  }
              }
          } else {
              body = this.json (newParams);
              headers = this.extend (headers, {
                  'Content-Type': 'application/json',
              });
          }
          return {
              'url': url,
              'method': method,
              'body': body,
              'headers': headers,
          };
      }
  
      handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
          if (response === undefined) {
              return; // fallback to default error handler
          }
          const error = this.safeValue (response, 'error');
          if (typeof error === 'string') {
              return;
          }
          const code = error && this.safeString (error, 'code');
          if (code) {
              const feedback = this.id + ' ' + this.safeString (error, 'message');
              if (code in this.exceptions) {
                  throw new this.exceptions[code] (feedback);
              } else {
                  throw new ExchangeError (feedback);
              }
          }
      }
  };
  
  