<?php

namespace ccxt\async;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use \ccxt\ExchangeError;
use \ccxt\ArgumentsRequired;
use \ccxt\BadSymbol;
use \ccxt\InvalidOrder;

class xena extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'xena',
            'name' => 'Xena Exchange',
            'countries' => array( 'VC', 'UK' ),
            'rateLimit' => 100,
            'certified' => true,
            'has' => array(
                'CORS' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createDepositAddress' => true,
                'createOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrderBook' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchWithdrawals' => true,
                'withdraw' => true,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg',
                'api' => array(
                    'public' => 'https://trading.xena.exchange/api',
                    'private' => 'https://api.xena.exchange',
                ),
                'www' => 'https://xena.exchange',
                'doc' => 'https://support.xena.exchange/support/solutions/44000808700',
                'fees' => 'https://trading.xena.exchange/en/platform-specification/fee-schedule',
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '4h' => '4h',
                '12h' => '12h',
                '1d' => '1d',
                '1w' => '1w',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'common/currencies',
                        'common/instruments',
                        'common/features',
                        'common/commissions',
                        'common/news',
                        'market-data/candles/{marketId}/{timeframe}',
                        'market-data/market-watch',
                        'market-data/dom/{symbol}',
                        'market-data/candles/{symbol}/{timeframe}',
                        'market-data/trades/{symbol}',
                        'market-data/server-time',
                        'market-data/v2/candles/{symbol}/{timeframe}',
                        'market-data/v2/trades/{symbol}',
                        'market-data/v2/dom/{symbol}/',
                        'market-data/v2/server-time',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'trading/accounts/{accountId}/order',
                        'trading/accounts/{accountId}/active-orders',
                        'trading/accounts/{accountId}/last-order-statuses',
                        'trading/accounts/{accountId}/positions',
                        'trading/accounts/{accountId}/positions-history',
                        'trading/accounts/{accountId}/margin-requirements',
                        'trading/accounts',
                        'trading/accounts/{accountId}/balance',
                        'trading/accounts/{accountId}/trade-history',
                        // 'trading/accounts/{accountId}/trade-history?symbol=BTC/USDT&client_order_id=EMBB8Veke&trade_id=220143254',
                        'transfers/accounts',
                        'transfers/accounts/{accountId}',
                        'transfers/accounts/{accountId}/deposit-address/{currency}',
                        'transfers/accounts/{accountId}/deposits',
                        'transfers/accounts/{accountId}/trusted-addresses',
                        'transfers/accounts/{accountId}/withdrawals',
                        'transfers/accounts/{accountId}/balance-history',
                        // 'transfers/accounts/{accountId}/balance-history?currency={currency}&from={time}&to={time}&kind={kind}&kind={kind}',
                        // 'transfers/accounts/{accountId}/balance-history?page={page}&limit={limit}',
                        // 'transfers/accounts/{accountId}/balance-history?txid=3e1db982c4eed2d6355e276c5bae01a52a27c9cef61574b0e8c67ee05fc26ccf',
                    ),
                    'post' => array(
                        'trading/order/new',
                        'trading/order/heartbeat',
                        'trading/order/cancel',
                        'trading/order/mass-cancel',
                        'trading/order/replace',
                        'trading/position/maintenance',
                        'transfers/accounts/{accountId}/withdrawals',
                        'transfers/accounts/{accountId}/deposit-address/{currency}',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'maker' => 0.0005,
                    'taker' => 0.001,
                    'tierBased' => true,
                    'percentage' => true,
                ),
                'funding' => array(
                    'tierBased' => false,
                    'percentage' => false,
                    'withdraw' => array(),
                    'deposit' => array(),
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'Validation failed' => '\\ccxt\\BadRequest',
                    'Unknown derivative symbol' => '\\ccxt\\BadSymbol', // array("error":"Unknown derivative symbol")
                    'Unknown account' => '\\ccxt\\BadRequest', // array("error":"Unknown account")
                    'Wrong TransactTime' => '\\ccxt\\BadRequest', // array("error":"Wrong TransactTime")
                    'ClOrdId is empty' => '\\ccxt\\BadRequest', // array("error":"ClOrdId is empty")
                ),
                'broad' => array(
                    'Invalid aggregation ratio or depth' => '\\ccxt\\BadRequest',
                    'address' => '\\ccxt\\InvalidAddress',
                    'Money not enough' => '\\ccxt\\InsufficientFunds',
                    'parse error' => '\\ccxt\\BadRequest',
                    'Not enough' => '\\ccxt\\InsufficientFunds', // array("error":"Not enough free margin")
                ),
            ),
            'options' => array(
                'defaultType' => 'margin', // 'margin',
                'accountId' => null, // '1012838157',
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        $response = yield $this->publicGetMarketDataV2ServerTime ($params);
        //
        //     {
        //         "msgType":"0",
        //         "$transactTime":1594774454112817637
        //     }
        //
        $transactTime = $this->safe_integer($response, 'transactTime');
        return intval($transactTime / 1000000);
    }

    public function fetch_markets($params = array ()) {
        $response = yield $this->publicGetCommonInstruments ($params);
        //
        //     array(
        //         {
        //             "$id":"ETHUSD_3M_250920",
        //             "$type":"Margin",
        //             "$marginType":"XenaFuture",
        //             "$symbol":"ETHUSD_3M_250920",
        //             "baseCurrency":"ETH",
        //             "quoteCurrency":"USD",
        //             "settlCurrency":"BTC",
        //             "tickSize":2,
        //             "minOrderQuantity":"1",
        //             "orderQtyStep":"1",
        //             "limitOrderMaxDistance":"10",
        //             "priceInputMask":"0000.00",
        //             "enabled":true,
        //             "liquidationMaxDistance":"0.01",
        //             "contractValue":"1",
        //             "contractCurrency":"BTC",
        //             "lotSize":"1",
        //             "tickValue":"0.00000001", // linear contracts only
        //             "maxOrderQty":"175000",
        //             "maxPosVolume":"1750000",
        //             "mark":".ETHUSD_3M_250920",
        //             "underlying":".ETHUSD_TWAP",
        //             "openInterest":".ETHUSD_3M_250920_OpenInterest",
        //             "floatingPL":"BidAsk", // perpetual contracts only
        //             "addUvmToFreeMargin":"ProfitAndLoss",
        //             "margin":array(
        //                 "netting":"PositionsAndOrders",
        //                 "rates":array(
        //                     array("maxVolume":"175000","initialRate":"0.05","maintenanceRate":"0.0125"),
        //                     array("maxVolume":"350000","initialRate":"0.1","maintenanceRate":"0.025"),
        //                     array("maxVolume":"500000","initialRate":"0.2","maintenanceRate":"0.05"),
        //                     array("maxVolume":"750000","initialRate":"0.3","maintenanceRate":"0.075"),
        //                     array("maxVolume":"1050000","initialRate":"0.4","maintenanceRate":"0.1"),
        //                     array("maxVolume":"1400000","initialRate":"0.5","maintenanceRate":"0.125"),
        //                     array("maxVolume":"1750000","initialRate":"1","maintenanceRate":"0.25")
        //                 ),
        //                 "rateMultipliers":array(
        //                     "LimitBuy":"1",
        //                     "LimitSell":"1",
        //                     "Long":"1",
        //                     "MarketBuy":"1",
        //                     "MarketSell":"1",
        //                     "Short":"1",
        //                     "StopBuy":"0",
        //                     "StopSell":"0"
        //                 }
        //             ),
        //             "clearing":array("enabled":true,"index":".ETHUSD_3M_250920"),
        //             "premium":array("enabled":true,"index":".XBTUSD_Premium_IR_Corrected"), // perpetual contracts only
        //             "riskAdjustment":array("enabled":true,"index":".RiskAdjustment_IR"),
        //             "expiration":array("enabled":true,"index":".ETHUSD_TWAP"), // futures only
        //             "$pricePrecision":3,
        //             "priceRange":array(
        //                 "enabled":true,
        //                 "distance":"0.03",
        //                 "movingBoundary":"0",
        //                 "lowIndex":".ETHUSD_3M_250920_LOWRANGE",
        //                 "highIndex":".ETHUSD_3M_250920_HIGHRANGE"
        //             ),
        //             "priceLimits":array(
        //                 "enabled":true,
        //                 "distance":"0.5",
        //                 "movingBoundary":"0",
        //                 "lowIndex":".ETHUSD_3M_250920_LOWLIMIT",
        //                 "highIndex":".ETHUSD_3M_250920_HIGHLIMIT"
        //             ),
        //             "$inverse":true, // $inverse contracts only
        //             "serie":"ETHUSD", // futures only
        //             "tradingStartDate":"2020-03-27 07:00:00",
        //             "expiryDate":"2020-09-25 08:00:00" // futures only
        //         ),
        //         array(
        //             "$type":"Index",
        //             "$symbol":".ETHUSD_Premium_IR_Corrected",
        //             "tickSize":6,
        //             "enabled":true,
        //             "basis":365
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $market = $response[$i];
            $type = $this->safe_string_lower($market, 'type');
            $id = $this->safe_string($market, 'symbol');
            $numericId = $this->safe_string($market, 'id');
            $marginType = $this->safe_string($market, 'marginType');
            $baseId = $this->safe_string($market, 'baseCurrency');
            $quoteId = $this->safe_string($market, 'quoteCurrency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $id;
            if ($type === 'margin') {
                if ($marginType === 'XenaFuture') {
                    $type = 'future';
                } else if ($marginType === 'XenaListedPerpetual') {
                    $type = 'swap';
                    $symbol = $base . '/' . $quote;
                }
            }
            $future = ($type === 'future');
            $swap = ($type === 'swap');
            $pricePrecision = $this->safe_integer_2($market, 'tickSize', 'pricePrecision');
            $precision = array(
                'price' => $pricePrecision,
                'amount' => 0,
            );
            $maxCost = $this->safe_float($market, 'maxOrderQty');
            $minCost = $this->safe_float($market, 'minOrderQuantity');
            $limits = array(
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $minCost,
                    'max' => $maxCost,
                ),
            );
            $active = $this->safe_value($market, 'enabled', false);
            $inverse = $this->safe_value($market, 'inverse', false);
            $result[] = array(
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'numericId' => $numericId,
                'active' => $active,
                'type' => $type,
                'spot' => false,
                'future' => $future,
                'swap' => $swap,
                'inverse' => $inverse,
                'precision' => $precision,
                'limits' => $limits,
                'info' => $market,
            );
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = yield $this->publicGetCommonCurrencies ($params);
        //
        //     {
        //         "BAB" => {
        //             "$name":"BAB",
        //             "title":"Bitcoin ABC",
        //             "blockchain":array(
        //                 "$name":"BAB",
        //                 "title":"Bitcoin ABC",
        //                 "deposit":array("confirmations":6),
        //                 "$withdraw":array("confirmations":1),
        //                 "addressReuseAllowed":false,
        //                 "view":array(
        //                     "uriTemplate":"bitcoinabc:%s?message=Xena Exchange",
        //                     "recommendedFee":"0.00001",
        //                     "transactionUrl":"https://blockchair.com/bitcoin-cash/transaction/${txId}",
        //                     "walletUrl":"https://blockchair.com/bitcoin-cash/address/${walletId}"
        //                 }
        //             ),
        //             "$precision":5,
        //             "$withdraw":array("minAmount":"0.01","commission":"0.001"),
        //             "view":array(
        //                 "color":"#DC7C08",
        //                 "site":"https://www.bitcoinabc.org"
        //             ),
        //             "$enabled":true
        //         ),
        //     }
        $ids = is_array($response) ? array_keys($response) : array();
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $ids[$i];
            $currency = $response[$id];
            $code = $this->safe_currency_code($id);
            $name = $this->safe_string($currency, 'title');
            $precision = $this->safe_integer($currency, 'precision');
            $enabled = $this->safe_value($currency, 'enabled');
            $active = ($enabled === true);
            $withdraw = $this->safe_value($currency, 'withdraw', array());
            $result[$code] = array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'name' => $name,
                'active' => $active,
                'fee' => $this->safe_float($withdraw, 'commission'),
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => $this->safe_float($withdraw, 'minAmount'),
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker, fetchTickers
        //
        //     {
        //         "$symbol":".XBTUSD_3M_250920_MID",
        //         "firstPx":"9337.49",
        //         "lastPx":"9355.81",
        //         "highPx":"9579.42",
        //         "lowPx":"9157.63",
        //         "$buyVolume":"0",
        //         "$sellVolume":"0",
        //         "bid":"0",
        //         "ask":"0"
        //     }
        //
        $timestamp = $this->milliseconds();
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_float($ticker, 'lastPx');
        $open = $this->safe_float($ticker, 'firstPx');
        $percentage = null;
        $change = null;
        $average = null;
        if (($last !== null) && ($open !== null)) {
            $change = $last - $open;
            $average = $this->sum($last, $open) / 2;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
        }
        $buyVolume = $this->safe_float($ticker, 'buyVolume');
        $sellVolume = $this->safe_float($ticker, 'sellVolume');
        $baseVolume = $this->sum($buyVolume, $sellVolume);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'highPx'),
            'low' => $this->safe_float($ticker, 'lowPx'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => null,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $tickers = yield $this->fetch_tickers(null, $params);
        if (is_array($tickers) && array_key_exists($symbol, $tickers)) {
            return $tickers[$symbol];
        }
        throw new BadSymbol($this->id . ' fetchTicker could not find a ticker with $symbol ' . $symbol);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        yield $this->load_markets();
        $tickers = yield $this->publicGetMarketDataMarketWatch ($params);
        //
        //     array(
        //         {
        //             "$symbol":".XBTUSD_3M_250920_MID",
        //             "firstPx":"9337.49",
        //             "lastPx":"9355.81",
        //             "highPx":"9579.42",
        //             "lowPx":"9157.63",
        //             "buyVolume":"0",
        //             "sellVolume":"0",
        //             "bid":"0",
        //             "ask":"0"
        //         }
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($tickers); $i++) {
            $ticker = $this->parse_ticker($tickers[$i]);
            $symbol = $ticker['symbol'];
            $result[$symbol] = $ticker;
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'symbol' => $this->market_id($symbol),
        );
        if ($limit !== null) {
            $request['depth'] = $limit;
        }
        $response = yield $this->publicGetMarketDataV2DomSymbol (array_merge($request, $params));
        //
        //     {
        //         "msgType":"W",
        //         "mdStreamId":"DOM:XBTUSD:aggregated",
        //         "$lastUpdateTime":1594772683037691997,
        //         "mdBookType":"2",
        //         "$symbol":"XBTUSD",
        //         "lowRangePx":"9132.24",
        //         "highRangePx":"9410.36",
        //         "lowLimitPx":"9132.24",
        //         "highLimitPx":"9410.36",
        //         "clearingPx":"9253.4",
        //         "bestBid":"9269.8",
        //         "bestAsk":"9275.9",
        //         "$mdEntry":array(
        //             array("mdEntryType":"1","mdEntryPx":"9275.9","mdEntrySize":"3000","numberOfOrders":1),
        //             array("mdEntryType":"1","mdEntryPx":"9277.7","mdEntrySize":"50000","numberOfOrders":1),
        //             array("mdEntryType":"1","mdEntryPx":"9277.8","mdEntrySize":"2000","numberOfOrders":1),
        //             array("mdEntryType":"0","mdEntryPx":"9269.8","mdEntrySize":"2000","numberOfOrders":1),
        //             array("mdEntryType":"0","mdEntryPx":"9267.9","mdEntrySize":"3000","numberOfOrders":1),
        //             array("mdEntryType":"0","mdEntryPx":"9267.8","mdEntrySize":"50000","numberOfOrders":1),
        //         )
        //     }
        //
        $mdEntry = $this->safe_value($response, 'mdEntry', array());
        $mdEntriesByType = $this->group_by($mdEntry, 'mdEntryType');
        $lastUpdateTime = $this->safe_integer($response, 'lastUpdateTime');
        $timestamp = intval($lastUpdateTime / 1000000);
        return $this->parse_order_book($mdEntriesByType, $timestamp, '0', '1', 'mdEntryPx', 'mdEntrySize');
    }

    public function fetch_accounts($params = array ()) {
        $response = yield $this->privateGetTradingAccounts ($params);
        //
        //     {
        //         "$accounts" => array(
        //             array( "id":8273231, "kind" => "Spot" ),
        //             array( "id":10012833469, "kind" => "Margin", "currency" => "BTC" )
        //         )
        //     }
        //
        $accounts = $this->safe_value($response, 'accounts');
        $result = array();
        for ($i = 0; $i < count($accounts); $i++) {
            $account = $accounts[$i];
            $accountId = $this->safe_string($account, 'id');
            $currencyId = $this->safe_string($account, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $type = $this->safe_string_lower($account, 'kind');
            $result[] = array(
                'id' => $accountId,
                'type' => $type,
                'currency' => $code,
                'info' => $account,
            );
        }
        return $result;
    }

    public function find_account_by_type($type) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountsByType = $this->group_by($this->accounts, 'type');
        $accounts = $this->safe_value($accountsByType, $type);
        if ($accounts === null) {
            throw new ExchangeError($this->id . " findAccountByType() could not find an accountId with $type '" . $type . "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes
        }
        $numAccounts = is_array($accounts) ? count($accounts) : 0;
        if ($numAccounts > 1) {
            throw new ExchangeError($this->id . " findAccountByType() found more than one accountId with $type '" . $type . "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes
        }
        return $accounts[0];
    }

    public function get_account_id($params) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $defaultAccountId = $this->safe_string($this->options, 'accountId');
        $accountId = $this->safe_string($params, 'accountId', $defaultAccountId);
        if ($accountId !== null) {
            return $accountId;
        }
        $defaultType = $this->safe_string($this->options, 'defaultType', 'margin');
        $type = $this->safe_string($params, 'type', $defaultType);
        $params = $this->omit($params, 'type');
        if ($type === null) {
            throw new ArgumentsRequired($this->id . " requires an 'accountId' parameter or a 'type' parameter ('spot' or 'margin')");
        }
        $account = yield $this->find_account_by_type($type);
        return $account['id'];
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'accountId' => $accountId,
        );
        $response = yield $this->privateGetTradingAccountsAccountIdBalance (array_merge($request, $params));
        //
        //     {
        //         "$balances" => array(
        //             array("available":"0","onHold":"0","settled":"0","equity":"0","currency":"BAB","lastUpdated":1564811790485125345),
        //             array("available":"0","onHold":"0","settled":"0","equity":"0","currency":"BSV","lastUpdated":1564811790485125345),
        //             array("available":"0","onHold":"0","settled":"0","equity":"0","currency":"BTC","lastUpdated":1564811790485125345),
        //         )
        //     }
        //
        $result = array( 'info' => $response );
        $balances = $this->safe_value($response, 'balances', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'available');
            $account['used'] = $this->safe_float($balance, 'onHold');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function parse_trade($trade, $market = null) {
        //
        //     {
        //         "mdUpdateAction":"0",
        //         "mdEntryType":"2",
        //         "mdEntryPx":"9225.16",
        //         "mdEntrySize":"10000",
        //         "transactTime":1594728504524977655,
        //         "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
        //         "aggressorSide":"1"
        //     }
        //
        // fetchMyTrades
        //
        //     {
        //         "msgType":"8",
        //         "account":1012838158,
        //         "clOrdId":"xXWKLQVl3",
        //         "$orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
        //         "$symbol":"ETHUSD",
        //         "transactTime":1595143349089739000,
        //         "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
        //         "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
        //         "$side":"2",
        //         "lastQty":"1",
        //         "lastPx":"234.58",
        //         "avgPx":"234.58",
        //         "calculatedCcyLastQty":"0",
        //         "netMoney":"0",
        //         "lastLiquidityInd":"2",
        //         "commission":"0.00000011",
        //         "commRate":"0.00045",
        //         "commCurrency":"BTC",
        //         "positionId":132162662,
        //         "positionEffect":"C"
        //     }
        //
        $id = $this->safe_string($trade, 'tradeId');
        $timestamp = $this->safe_integer($trade, 'transactTime');
        if ($timestamp !== null) {
            $timestamp = intval($timestamp / 1000000);
        }
        $side = $this->safe_string_lower_2($trade, 'side', 'aggressorSide');
        if ($side === '1') {
            $side = 'buy';
        } else if ($side === '2') {
            $side = 'sell';
        }
        $orderId = $this->safe_string($trade, 'orderId');
        $marketId = $this->safe_string($trade, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $price = $this->safe_float_2($trade, 'lastPx', 'mdEntryPx');
        $amount = $this->safe_float_2($trade, 'lastQty', 'mdEntrySize');
        $cost = null;
        if ($price !== null) {
            if ($amount !== null) {
                $cost = $price * $amount;
            }
        }
        $fee = null;
        $feeCost = $this->safe_float($trade, 'commission');
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'commCurrency');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $feeRate = $this->safe_float($trade, 'commRate');
            $fee = array(
                'cost' => $feeCost,
                'rate' => $feeRate,
                'currency' => $feeCurrencyCode,
            );
        }
        return array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => null,
            'order' => $orderId,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'accountId' => $accountId,
            // 'page' => 1,
            // 'limit' => integer,
            // 'from' => time,
            // 'to' => time,
            // 'symbol' => currency['id'],
            // 'trade_id' => id,
            // 'client_order_id' => id,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['from'] = $since * 1000000;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = yield $this->privateGetTradingAccountsAccountIdTradeHistory (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "msgType":"8",
        //             "account":1012838158,
        //             "clOrdId":"xXWKLQVl3",
        //             "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
        //             "$symbol":"ETHUSD",
        //             "transactTime":1595143349089739000,
        //             "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
        //             "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
        //             "side":"2",
        //             "lastQty":"1",
        //             "lastPx":"234.58",
        //             "avgPx":"234.58",
        //             "calculatedCcyLastQty":"0",
        //             "netMoney":"0",
        //             "lastLiquidityInd":"2",
        //             "commission":"0.00000011",
        //             "commRate":"0.00045",
        //             "commCurrency":"BTC",
        //             "positionId":132162662,
        //             "positionEffect":"C"
        //         ),
        //         {
        //             "msgType":"8",
        //             "account":1012838158,
        //             "clOrdId":"3ce8c305-9936-4e97-9206-71ae3ff40305",
        //             "orderId":"a93c686d-990e-44d9-9cbe-61107744b990",
        //             "$symbol":"ETHUSD",
        //             "transactTime":1595143315369226000,
        //             "execId":"1c745881722ad966a4ce71600cd058d59da0d1c3",
        //             "tradeId":"77f75bd8-27c4-4b1a-a5e8-0d59239ce216",
        //             "side":"1",
        //             "lastQty":"1",
        //             "lastPx":"234.72",
        //             "avgPx":"234.72",
        //             "calculatedCcyLastQty":"0",
        //             "netMoney":"0",
        //             "lastLiquidityInd":"2",
        //             "commission":"0.00000011",
        //             "commRate":"0.00045",
        //             "commCurrency":"BTC",
        //             "positionId":132162662,
        //             "positionEffect":"O"
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "$transactTime":1594784700000000000,
        //         "firstPx":"9246.3",
        //         "lastPx":"9232.8",
        //         "highPx":"9246.3",
        //         "lowPx":"9232.8",
        //         "$buyVolume":"0",
        //         "$sellVolume":"0"
        //     }
        //
        $transactTime = $this->safe_integer($ohlcv, 'transactTime');
        $timestamp = intval($transactTime / 1000000);
        $buyVolume = $this->safe_float($ohlcv, 'buyVolume');
        $sellVolume = $this->safe_float($ohlcv, 'sellVolume');
        $volume = $this->sum($buyVolume, $sellVolume);
        return array(
            $timestamp,
            $this->safe_float($ohlcv, 'firstPx'),
            $this->safe_float($ohlcv, 'highPx'),
            $this->safe_float($ohlcv, 'lowPx'),
            $this->safe_float($ohlcv, 'lastPx'),
            $volume,
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'timeframe' => $this->timeframes[$timeframe],
        );
        $durationInSeconds = $this->parse_timeframe($timeframe);
        $duration = $durationInSeconds * 1000;
        if ($since !== null) {
            $request['from'] = $since * 1000000;
            if ($limit !== null) {
                $request['to'] = $this->sum($since, $limit * $duration) * 1000000;
            }
        } else {
            $now = $this->milliseconds();
            // max $limit is 1000
            if ($limit !== null) {
                $request['from'] = ($now - $limit * $duration) * 1000000;
            }
        }
        $response = yield $this->publicGetMarketDataV2CandlesSymbolTimeframe (array_merge($request, $params));
        //
        //     {
        //         "$mdEntry":array(
        //             array("transactTime":1594784700000000000,"firstPx":"9246.3","lastPx":"9232.8","highPx":"9246.3","lowPx":"9232.8","buyVolume":"0","sellVolume":"0"),
        //             array("transactTime":1594785600000000000,"firstPx":"9231.8","lastPx":"9227.3","highPx":"9232.8","lowPx":"9227.3","buyVolume":"0","sellVolume":"0"),
        //             array("transactTime":1594786500000000000,"firstPx":"9226.3","lastPx":"9230.3","highPx":"9230.3","lowPx":"9220.6","buyVolume":"0","sellVolume":"0")
        //         )
        //     }
        //
        $mdEntry = $this->safe_value($response, 'mdEntry', array());
        return $this->parse_ohlcvs($mdEntry, $market, $timeframe, $since, $limit);
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'from' => $this->iso8601($since),
            // 'to' => $this->iso8601($this->milliseconds()),
            // 'page' => 1,
            // 'limit' => $limit,
        );
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = yield $this->publicGetMarketDataV2TradesSymbol (array_merge($request, $params));
        //
        //     {
        //         "msgType":"W",
        //         "lastUpdateTime":1594737830902223803,
        //         "$symbol":"XBTUSD",
        //         "$mdEntry":array(
        //             array(
        //                 "mdUpdateAction":"0",
        //                 "mdEntryType":"2",
        //                 "mdEntryPx":"9225.16",
        //                 "mdEntrySize":"10000",
        //                 "transactTime":1594728504524977655,
        //                 "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
        //                 "aggressorSide":"1"
        //             ),
        //         )
        //     }
        //
        $mdEntry = $this->safe_value($response, 'mdEntry', array());
        return $this->parse_trades($mdEntry, $market, $since, $limit);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'A' => 'open', // PendingNew
            '0' => 'open', // New
            '1' => 'open', // PartiallyFilled
            '2' => 'closed', // Filled
            '6' => 'canceled', // PendingCancel
            '4' => 'canceled', // Cancelled
            'E' => 'open', // PendingReplace
            '8' => 'rejected', // Rejected
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder
        //
        //     {
        //         "msgType":"8",
        //         "account":1012838720,
        //         "clOrdId":"XAq0pRQ1g",
        //         "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        //         "$symbol":"XBTUSD",
        //         "ordType":"2",
        //         "$price":"9000",
        //         "$transactTime":1593778763271127920,
        //         "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        //         "execType":"I",
        //         "ordStatus":"0",
        //         "$side":"1",
        //         "orderQty":"1",
        //         "leavesQty":"1",
        //         "cumQty":"0",
        //         "positionEffect":"O",
        //         "marginAmt":"0.00000556",
        //         "marginAmtType":"11"
        //     }
        //
        $id = $this->safe_string($order, 'orderId');
        $clientOrderId = $this->safe_string($order, 'clOrdId');
        $transactTime = $this->safe_integer($order, 'transactTime');
        $timestamp = intval($transactTime / 1000000);
        $status = $this->parse_order_status($this->safe_string($order, 'ordStatus'));
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $price = $this->safe_float($order, 'price');
        $amount = $this->safe_float($order, 'orderQty');
        $filled = $this->safe_float($order, 'cumQty');
        $remaining = $this->safe_float($order, 'leavesQty');
        $cost = null;
        $side = $this->safe_string_lower($order, 'side');
        if ($side === '1') {
            $side = 'buy';
        } else if ($side === '1') {
            $side = 'sell';
        }
        $type = $this->safe_string_lower($order, 'ordType');
        if ($type === '1') {
            $type = 'market';
        } else if ($type === '2') {
            $type = 'limit';
        } else if ($type === '3') {
            $type = 'stop';
        } else if ($type === '4') {
            $type = 'stop-limit';
        }
        if ($cost === null) {
            if (($price !== null) && ($filled !== null)) {
                $cost = $price * $filled;
            }
        }
        return array(
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'info' => $order,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'amount' => $amount,
            'cost' => $cost,
            'average' => null,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        );
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $orderTypes = array(
            'market' => '1',
            'limit' => '2',
            'stop' => '3',
            'stop-limit' => '4',
        );
        $orderType = $this->safe_string($orderTypes, $type);
        if ($orderType === null) {
            throw new InvalidOrder($this->id . ' createOrder does not support order $type ' . $type . ', supported order types are $market, limit, stop, stop-limit');
        }
        $orderSides = array(
            'buy' => '1',
            'sell' => '2',
        );
        $orderSide = $this->safe_string($orderSides, $side);
        if ($orderSide === null) {
            throw new InvalidOrder($this->id . ' createOrder does not support order $side ' . $side . ', supported order sides are buy, sell');
        }
        $market = $this->market($symbol);
        $request = array(
            'account' => intval($accountId),
            'symbol' => $market['id'],
            'ordType' => $orderType,
            'side' => $orderSide,
            'orderQty' => $this->amount_to_precision($symbol, $amount),
            'transactTime' => $this->milliseconds() * 1000000,
            // 'clOrdId' => $this->uuid(), // required
            // 'price' => $this->price_to_precision($symbol, $price), // required for limit and stop-limit orders
            // 'stopPx' => $this->price_to_precision($symbol, $stopPx), // required for stop and stop-limit orders
            // 'timeInForce' => '1', // default '1' = GoodTillCancelled, '3' = ImmediateOrCancel, '4' = FillOrKill
            // 'execInst' => '0',
            //     '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
            //     '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
            //     'o' = CancelOnConnectionLoss
            // 'positionID' => 1013838923, // required when positionEffect == 'C' with hedged accounting
            // 'positionEffect' => 'O', // 'C' = Close, 'O' = Open, send C along with the positionID if the order must close a position with hedged accounting mode
            // 'text' => 'comment', // optional
            // 'grpID' => 'group-identifier', // group identifier for cancel on disconnect orders
        );
        if (($type === 'limit') || ($type === 'stop-limit')) {
            if ($price === null) {
                throw new InvalidOrder($this->id . ' createOrder() requires a $price argument for order $type ' . $type);
            }
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        if (($type === 'stop') || ($type === 'stop-limit')) {
            $stopPx = $this->safe_float($params, 'stopPx');
            if ($stopPx === null) {
                throw new InvalidOrder($this->id . ' createOrder() requires a $stopPx param for order $type ' . $type);
            }
            $request['stopPx'] = $this->price_to_precision($symbol, $stopPx);
            $params = $this->omit($params, 'stopPx');
        }
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'clOrdId', $this->uuid());
        if ($clientOrderId !== null) {
            $request['clOrdId'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientOrderId', 'clOrdId' ));
        }
        $response = yield $this->privatePostTradingOrderNew (array_merge($request, $params));
        //
        //     {
        //         "msgType":"8",
        //         "account":1012838720,
        //         "clOrdId":"XAq0pRQ1g",
        //         "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        //         "$symbol":"XBTUSD",
        //         "ordType":"2",
        //         "$price":"9000",
        //         "transactTime":1593778763271127920,
        //         "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        //         "execType":"I",
        //         "ordStatus":"0",
        //         "$side":"1",
        //         "orderQty":"1",
        //         "leavesQty":"1",
        //         "cumQty":"0",
        //         "positionEffect":"O",
        //         "marginAmt":"0.00000556",
        //         "marginAmtType":"11"
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount = null, $price = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $market = $this->market($symbol);
        $request = array(
            'account' => intval($accountId),
            'clOrdId' => $this->uuid(),
            'symbol' => $market['id'],
            'transactTime' => $this->milliseconds() * 1000000,
            // 'origClOrdId' => $this->uuid(), // one of orderId or origClOrdId is required
            // 'orderId' => $id,
            // 'side' => '1', // 1 = buy, 2 = sell
            // 'execInst' => '0',
            //     '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
            //     '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
            //     'o' = CancelOnConnectionLoss
            // 'orderQty' => 38 M decimal
            // 'price' => $this->price_to_precision($symbol, $price), // required for limit and stop-limit orders
            // 'stopPx' => $this->price_to_precision($symbol, $stopPx), // required for stop and stop-limit orders
            // 'capPrice' => $this->price_to_precision($symbol, $capPrice), // the $price beyond which the order will not move for trailing stop and attempt-zero-loss
            // 'pegPriceType' => '8', // '8' = TrailingStopPeg, identifies a trailing stop or an attempt-zero-loss order
            // 'pegOffsetType' => '2', // '2' = BasisPoints, the unit of the distance to the stop $price for a trailing stop or an attempt-zero-loss order
            // 'pegOffsetValue' => 123, // distance to the trailing stop or attempt-zero-loss
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'origClOrdId');
        if ($clientOrderId !== null) {
            $request['origClOrdId'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientOrderId', 'origClOrdId' ));
        } else {
            $request['orderId'] = $id;
        }
        if ($amount !== null) {
            $request['orderQty'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $stopPx = $this->safe_float($params, 'stopPx');
        if ($stopPx !== null) {
            $request['stopPx'] = $this->price_to_precision($symbol, $stopPx);
            $params = $this->omit($params, 'stopPx');
        }
        $capPrice = $this->safe_float($params, 'capPrice');
        if ($capPrice !== null) {
            $request['capPrice'] = $this->price_to_precision($symbol, $capPrice);
            $params = $this->omit($params, 'capPrice');
        }
        $response = yield $this->privatePostTradingOrderReplace (array_merge($request, $params));
        return $this->parse_order($response, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'origClOrdId');
        $params = $this->omit($params, array( 'clientOrderId', 'origClOrdId' ));
        $market = $this->market($symbol);
        $request = array(
            'account' => intval($accountId),
            'symbol' => $market['id'],
            'clOrdId' => $this->uuid(),
            'transactTime' => $this->milliseconds() * 1000000,
        );
        if ($clientOrderId !== null) {
            $request['origClOrdId'] = $clientOrderId;
        } else {
            $request['orderId'] = $id;
        }
        $response = yield $this->privatePostTradingOrderCancel (array_merge($request, $params));
        //
        //     {
        //         "msgType":"8",
        //         "account":1012838158,
        //         "clOrdId":"0fa3fb55-9dc0-4cfc-a1db-6aa8b7dd2d98",
        //         "origClOrdId":"3b2878bb-24d8-4922-9d2a-5b8009416677",
        //         "orderId":"665b418e-9d09-4461-b733-d317f6bff43f",
        //         "$symbol":"ETHUSD",
        //         "ordType":"2",
        //         "price":"640",
        //         "transactTime":1595060080941618739,
        //         "execId":"c541c0ca437c0e6501c3a50a9d4dc8f575f49972",
        //         "execType":"6",
        //         "ordStatus":"6",
        //         "side":"2",
        //         "orderQty":"1",
        //         "leavesQty":"0",
        //         "cumQty":"0",
        //         "positionEffect":"O",
        //         "marginAmt":"0.000032",
        //         "marginAmtType":"11"
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'account' => intval($accountId),
            'clOrdId' => $this->uuid(),
            // 'side' => '1', // 1 = buy, 2 = sell, optional filter, cancel only orders with the given side
            // 'positionEffect' => 'C', // C = Close, O = Open, optional filter, cancel only orders with the given positionEffect, applicable only for accounts with hedged accounting
        );
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $request['massCancelRequestType'] = '1'; // CancelOrdersForASecurity
        } else {
            $request['massCancelRequestType'] = '7'; // CancelAllOrders
        }
        $response = yield $this->privatePostTradingOrderMassCancel (array_merge($request, $params));
        //
        //     {
        //         "msgType":"r",
        //         "clOrdId":"b3e95759-e43e-4b3a-b664-a4d213e281a7",
        //         "massActionReportID":"e915b6f4-a7ca-4c5c-b8d6-e39862530248",
        //         "massCancelResponse":"1",
        //         "$symbol":"ETHUSD",
        //         "transactTime":1595065630133756426,
        //         "totalAffectedOrders":2,
        //         "account":1012838158
        //     }
        //
        return $response;
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'accountId' => $accountId,
            // 'symbol' => $market['id'],
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $response = yield $this->privateGetTradingAccountsAccountIdActiveOrders (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "msgType":"8",
        //             "account":1012838720,
        //             "clOrdId":"XAq0pRQ1g",
        //             "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        //             "$symbol":"XBTUSD",
        //             "ordType":"2",
        //             "price":"9000",
        //             "transactTime":1593778763271127920,
        //             "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        //             "execType":"I",
        //             "ordStatus":"0",
        //             "side":"1",
        //             "orderQty":"1",
        //             "leavesQty":"1",
        //             "cumQty":"0",
        //             "positionEffect":"O",
        //             "marginAmt":"0.00000556",
        //             "marginAmtType":"11"
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'accountId' => $accountId,
            // 'from' => $this->iso8601($since) * 1000000,
            // 'to' => $this->iso8601($this->milliseconds()) * 1000000, // max range is 7 days
            // 'symbol' => $market['id'],
            // 'limit' => 100,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['from'] = $this->iso8601($since) * 1000000;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = yield $this->privateGetTradingAccountsAccountIdLastOrderStatuses (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "msgType":"8",
        //             "account":1012838720,
        //             "clOrdId":"XAq0pRQ1g",
        //             "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
        //             "$symbol":"XBTUSD",
        //             "ordType":"2",
        //             "price":"9000",
        //             "transactTime":1593778763271127920,
        //             "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
        //             "execType":"I",
        //             "ordStatus":"0",
        //             "side":"1",
        //             "orderQty":"1",
        //             "leavesQty":"1",
        //             "cumQty":"0",
        //             "positionEffect":"O",
        //             "marginAmt":"0.00000556",
        //             "marginAmtType":"11"
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function create_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $currency = $this->currency($code);
        $request = array(
            'accountId' => $accountId,
            'currency' => $currency['id'],
        );
        $response = yield $this->privatePostTransfersAccountsAccountIdDepositAddressCurrency (array_merge($request, $params));
        //
        //     {
        //         "$address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //         "uri" => "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange",
        //         "allowsRenewal" => true
        //     }
        //
        $address = $this->safe_value($response, 'address');
        $tag = null;
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $response,
        );
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $currency = $this->currency($code);
        $request = array(
            'accountId' => $accountId,
            'currency' => $currency['id'],
        );
        $response = yield $this->privateGetTransfersAccountsAccountIdDepositAddressCurrency (array_merge($request, $params));
        //
        //     {
        //         "$address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //         "uri" => "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange",
        //         "allowsRenewal" => true
        //     }
        //
        $address = $this->safe_value($response, 'address');
        $tag = null;
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $response,
        );
    }

    public function fetch_transactions_by_type($type, $code = null, $since = null, $limit = null, $params = array ()) {
        if ($code === null) {
            throw new ArgumentsRequired($this->id . ' fetchTransactions() requires a $currency `$code` argument');
        }
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
            'accountId' => $accountId,
        );
        if ($since !== null) {
            $request['since'] = intval($since / 1000);
        }
        $method = 'privateGetTransfersAccountsAccountId' . $this->capitalize($type);
        $response = yield $this->$method (array_merge($request, $params));
        //
        //     {
        //         "withdrawals" => array(
        //             {
        //                 "withdrawalRequestId" => 47383243,
        //                 "externalId" => "...",    // external ID submitted by the client when creating the $request
        //                 "status" => 1,
        //                 "statusMessage" => "Pending confirmation",
        //                 "amount" => "10.2",
        //                 "$currency" => "BTC",
        //                 "lastUpdated" => <UNIX nanoseconds>,
        //                 "blockchain" => "Bitcoin",
        //                 "address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //                 "txId" => "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98"
        //             }
        //         )
        //     }
        //
        //     {
        //         "deposits" => array(
        //             {
        //                 "$currency" => "BTC",
        //                 "amount" => "1.2",
        //                 "status" => 1,
        //                 "statusMessage" => "Processing",
        //                 "blockchain" => "Bitcoin",
        //                 "txId" => "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98",
        //                 "address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //                 "lastUpdated" => <UNIX nanoseconds>
        //                 "confirmations" => 2,
        //                 "requiredConfirmations" => 6
        //             }
        //         )
        //     }
        //
        //
        $transactions = $this->safe_value($response, $type, array());
        return $this->parse_transactions($transactions, $currency, $since, $limit);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        return yield $this->fetch_transactions_by_type('withdrawals', $code, $since, $limit, $params);
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        return yield $this->fetch_transactions_by_type('deposits', $code, $since, $limit, $params);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // withdraw()
        //
        //     {
        //         "withdrawalRequestId" => 47383243,
        //         "$status" => 1,
        //         "statusMessage" => "Pending confirmation"
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "withdrawalRequestId" => 47383243,
        //         "externalId" => "...",    // external ID submitted by the client when creating the request
        //         "$status" => 1,
        //         "statusMessage" => "Pending confirmation",
        //         "$amount" => "10.2",
        //         "$currency" => "BTC",
        //         "lastUpdated" => <UNIX nanoseconds>,
        //         "blockchain" => "Bitcoin",
        //         "$address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //         "txId" => "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98"
        //     }
        //
        // fetchDeposits
        //
        //     {
        //         "$currency" => "BTC",
        //         "$amount" => "1.2",
        //         "$status" => 1,
        //         "statusMessage" => "Processing",
        //         "blockchain" => "Bitcoin",
        //         "txId" => "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98",
        //         "$address" => "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9",
        //         "lastUpdated" => <UNIX nanoseconds>
        //         "confirmations" => 2,
        //         "requiredConfirmations" => 6
        //     }
        //
        $id = $this->safe_string($transaction, 'withdrawalRequestId');
        $type = ($id === null) ? 'deposit' : 'withdrawal';
        $updated = $this->safe_integer($transaction, 'lastUpdated');
        if ($updated !== null) {
            $updated = intval($updated / 1000000);
        }
        $timestamp = null;
        $txid = $this->safe_string($transaction, 'txId');
        $currencyId = $this->safe_string($transaction, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $address = $this->safe_string($transaction, 'address');
        $addressFrom = null;
        $addressTo = $address;
        $amount = $this->safe_float($transaction, 'amount');
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $fee = null;
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => $addressFrom,
            'addressTo' => $addressTo,
            'address' => $address,
            'tagFrom' => null,
            'tagTo' => null,
            'tag' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'fee' => $fee,
        );
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            '1' => 'pending', // new
            '2' => 'ok', // completed
            '3' => 'failed', // duplicate
            '4' => 'failed', // not enough money
            '5' => 'pending', // waiting for manual approval from XENA
            '100' => 'pending', // request is being processed
            '101' => 'pending', // request is being processed
            '102' => 'pending', // request is being processed
            '103' => 'pending', // request is being processed
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        $this->check_address($address);
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $currency = $this->currency($code);
        $uuid = $this->uuid();
        $uuid = explode('-', $uuid);
        $uuid = implode('', $uuid);
        $request = array(
            'currency' => $currency['id'],
            'accountId' => $accountId,
            'amount' => $this->currency_to_precision($code, $amount),
            'address' => $address,
            'id' => $uuid, // mandatory external ID (string), used by the client to identify his $request
        );
        $response = yield $this->privatePostTransfersAccountsAccountIdWithdrawals (array_merge($request, $params));
        //
        //     {
        //         "withdrawalRequestId" => 47383243,
        //         "status" => 1,
        //         "statusMessage" => "Pending confirmation"
        //     }
        //
        return $this->parse_transaction($response, $currency);
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'deposit' => 'transaction',
            'withdrawal' => 'transaction',
            'internal deposit' => 'transfer',
            'internal withdrawal' => 'transfer',
            'rebate' => 'rebate',
            'reward' => 'reward',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry($item, $currency = null) {
        //
        //     {
        //         "accountId":8263118,
        //         "ts":1551974415000000000,
        //         "$amount":"-1",
        //         "$currency":"BTC",
        //         "kind":"internal withdrawal",
        //         "commission":"0",
        //         "$id":96
        //     }
        //
        $id = $this->safe_string($item, 'id');
        $direction = null;
        $account = $this->safe_string($item, 'accountId');
        $referenceId = null;
        $referenceAccount = null;
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'kind'));
        $code = $this->safe_currency_code($this->safe_string($item, 'currency'), $currency);
        $amount = $this->safe_float($item, 'amount');
        if ($amount < 0) {
            $direction = 'out';
            $amount = abs($amount);
        } else {
            $direction = 'in';
        }
        $timestamp = $this->safe_integer($item, 'ts');
        if ($timestamp !== null) {
            $timestamp = intval($timestamp / 1000000);
        }
        $fee = array(
            'cost' => $this->safe_float($item, 'commission'),
            'currency' => $code,
        );
        $before = null;
        $after = $this->safe_float($item, 'balance');
        $status = 'ok';
        return array(
            'info' => $item,
            'id' => $id,
            'direction' => $direction,
            'account' => $account,
            'referenceId' => $referenceId,
            'referenceAccount' => $referenceAccount,
            'type' => $type,
            'currency' => $code,
            'amount' => $amount,
            'before' => $before,
            'after' => $after,
            'status' => $status,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'fee' => $fee,
        );
    }

    public function fetch_ledger($code = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        yield $this->load_accounts();
        $accountId = yield $this->get_account_id($params);
        $request = array(
            'accountId' => $accountId,
            // 'page' => 1,
            // 'limit' => 5000, // max 5000
            // 'from' => time,
            // 'to' => time,
            // 'symbol' => $currency['id'],
            // 'trade_id' => id,
            // 'client_order_id' => id,
            // 'txid' => txid,
            // 'kind' => 'deposit', // 'withdrawal, 'internal deposit', 'internal withdrawal', 'rebate', 'reward'
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['symbol'] = $currency['id'];
        }
        if ($since !== null) {
            $request['from'] = $since * 1000000;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // max 5000
        }
        $response = yield $this->privateGetTransfersAccountsAccountIdBalanceHistory (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "$accountId":8263118,
        //             "ts":1551974415000000000,
        //             "amount":"-1",
        //             "$currency":"BTC",
        //             "kind":"internal withdrawal",
        //             "commission":"0",
        //             "id":96
        //         ),
        //         {
        //             "$accountId":8263118,
        //             "ts":1551964677000000000,
        //             "amount":"-1",
        //             "$currency":"BTC",
        //             "kind":"internal deposit",
        //             "commission":"0",
        //             "id":95
        //         }
        //     )
        //
        return $this->parse_ledger($response, $currency, $since, $limit);
    }

    public function nonce() {
        return $this->milliseconds();
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api'][$api] . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'public') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else if ($api === 'private') {
            $this->check_required_credentials();
            $nonce = $this->nonce();
            // php does not format it properly
            // therefore we use string concatenation here
            // $nonce *= 1000000;
            $nonce = (string) $nonce;
            $nonce = $nonce . '000000'; // see the comment a few lines above
            $payload = 'AUTH' . $nonce;
            $secret = mb_substr($this->secret, 14, 78 - 14);
            $ecdsa = $this->ecdsa($payload, $secret, 'p256', 'sha256');
            $signature = $ecdsa['r'] . $ecdsa['s'];
            $headers = array(
                'X-AUTH-API-KEY' => $this->apiKey,
                'X-AUTH-API-PAYLOAD' => $payload,
                'X-AUTH-API-SIGNATURE' => $signature,
                'X-AUTH-API-NONCE' => $nonce,
            );
            if ($method === 'GET') {
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            } else if ($method === 'POST') {
                $body = $this->json($query);
                $headers['Content-Type'] = 'application/json';
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return;
        }
        //
        //     array("error":"Validation failed","fields":["address"])
        //     array("error":"Money not enough. You have only => 0 ETH","fields":["amount"])
        //
        if ($code >= 400) {
            $feedback = $this->id . ' ' . $this->json($response);
            $message = $this->safe_string($response, 'error');
            $exact = $this->exceptions['exact'];
            if (is_array($exact) && array_key_exists($message, $exact)) {
                throw new $exact[$message]($feedback);
            }
            $broad = $this->exceptions['broad'];
            $broadKey = $this->find_broadly_matched_key($broad, $body);
            if ($broadKey !== null) {
                throw new $broad[$broadKey]($feedback);
            }
            throw new ExchangeError($feedback); // unknown $message
        }
    }
}
