<?php

namespace ccxt;

// 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;

class delta extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'delta',
            'name' => 'Delta Exchange',
            'countries' => array( 'VC' ), // Saint Vincent and the Grenadines
            'rateLimit' => 300,
            'version' => 'v2',
            // new metainfo interface
            'has' => array(
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchDepositAddress' => true,
                'fetchCurrencies' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrderBook' => true,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '6h' => '6h',
                '1d' => '1d',
                '7d' => '7d',
                '1w' => '1w',
                '2w' => '2w',
                '1M' => '30d',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/99450025-3be60a00-2931-11eb-9302-f4fd8d8589aa.jpg',
                'test' => array(
                    'public' => 'https://testnet-api.delta.exchange',
                    'private' => 'https://testnet-api.delta.exchange',
                ),
                'api' => array(
                    'public' => 'https://api.delta.exchange',
                    'private' => 'https://api.delta.exchange',
                ),
                'www' => 'https://www.delta.exchange',
                'doc' => array(
                    'https://docs.delta.exchange',
                ),
                'fees' => 'https://www.delta.exchange/fees',
                'referral' => 'https://www.delta.exchange/app/signup/?code=IULYNB',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'assets',
                        'settings',
                        'indices',
                        'products',
                        'tickers',
                        'tickers/{symbol}',
                        'l2orderbook/{symbol}',
                        'trades/{symbol}',
                        'history/candles',
                        'history/sparklines',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'orders',
                        'orders/leverage',
                        'positions',
                        'positions/margined',
                        'orders/history',
                        'fills',
                        'fills/history/download/csv',
                        'wallet/balances',
                        'wallet/transactions',
                        'wallet/transactions/download',
                        'deposits/address',
                    ),
                    'post' => array(
                        'orders',
                        'orders/batch',
                        'orders/leverage',
                        'positions/change_margin',
                    ),
                    'put' => array(
                        'orders',
                        'orders/batch',
                    ),
                    'delete' => array(
                        'orders',
                        'orders/all',
                        'orders/batch',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => 0.15 / 100,
                    'maker' => 0.10 / 100,
                    'tiers' => array(
                        'taker' => [
                            [0, 0.15 / 100],
                            [100, 0.13 / 100],
                            [250, 0.13 / 100],
                            [1000, 0.1 / 100],
                            [5000, 0.09 / 100],
                            [10000, 0.075 / 100],
                            [20000, 0.065 / 100],
                        ],
                        'maker' => [
                            [0, 0.1 / 100],
                            [100, 0.1 / 100],
                            [250, 0.09 / 100],
                            [1000, 0.075 / 100],
                            [5000, 0.06 / 100],
                            [10000, 0.05 / 100],
                            [20000, 0.05 / 100],
                        ],
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => false,
            ),
            'exceptions' => array(
                'exact' => array(
                    // Margin required to place order with selected leverage and quantity is insufficient.
                    'insufficient_margin' => '\\ccxt\\InsufficientFunds', // array("error":array("code":"insufficient_margin","context":array("available_balance":"0.000000000000000000","required_additional_balance":"1.618626000000000000000000000")),"success":false)
                    'order_size_exceed_available' => '\\ccxt\\InvalidOrder', // The order book doesn't have sufficient liquidity, hence the order couldnt be filled, for example, ioc orders
                    'risk_limits_breached' => '\\ccxt\\BadRequest', // orders couldn't be placed as it will breach allowed risk limits.
                    'invalid_contract' => '\\ccxt\\BadSymbol', // The contract/product is either doesn't exist or has already expired.
                    'immediate_liquidation' => '\\ccxt\\InvalidOrder', // Order will cause immediate liquidation.
                    'out_of_bankruptcy' => '\\ccxt\\InvalidOrder', // Order prices are out of position bankruptcy limits.
                    'self_matching_disrupted_post_only' => '\\ccxt\\InvalidOrder', // Self matching is not allowed during auction.
                    'immediate_execution_post_only' => '\\ccxt\\InvalidOrder', // orders couldn't be placed as it includes post only orders which will be immediately executed
                    'bad_schema' => '\\ccxt\\BadRequest', // array("error":array("code":"bad_schema","context":array("schema_errors":[array("code":"validation_error","message":"id is required","param":"")])),"success":false)
                    'invalid_api_key' => '\\ccxt\\AuthenticationError', // array("success":false,"error":array("code":"invalid_api_key"))
                    'invalid_signature' => '\\ccxt\\AuthenticationError', // array("success":false,"error":array("code":"invalid_signature"))
                    'open_order_not_found' => '\\ccxt\\OrderNotFound', // array("error":array("code":"open_order_not_found"),"success":false)
                    'unavailable' => '\\ccxt\\ExchangeNotAvailable', // array("error":array("code":"unavailable"),"success":false)
                ),
                'broad' => array(
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        $response = $this->publicGetSettings ($params);
        //
        //     {
        //         "$result":array(
        //             "server_time":1605472733766141,
        //             "deto_referral_mining_daily_reward":"25000",
        //             "deto_total_reward_pool":"100000000",
        //             "deto_trade_mining_daily_reward":"75000",
        //             "kyc_deposit_limit":"20",
        //             "kyc_withdrawal_limit":"2",
        //             "under_maintenance":"false"
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->safe_integer_product($result, 'server_time', 0.001);
    }

    public function fetch_status($params = array ()) {
        $response = $this->publicGetSettings ($params);
        $result = $this->safe_value($response, 'result', array());
        $underMaintenance = $this->safe_value($result, 'under_maintenance');
        $status = ($underMaintenance === 'true') ? 'maintenance' : 'ok';
        $updated = $this->safe_integer_product($result, 'server_time', 0.001);
        $this->status = array_merge($this->status, array(
            'status' => $status,
            'updated' => $updated,
        ));
        return $this->status;
    }

    public function fetch_currencies($params = array ()) {
        $response = $this->publicGetAssets ($params);
        //
        //     {
        //         "$result":array(
        //             array(
        //                 "base_withdrawal_fee":"0.0005",
        //                 "deposit_status":"enabled",
        //                 "$id":2,
        //                 "interest_credit":true,
        //                 "interest_slabs":array(
        //                     array("limit":"0.1","rate":"0"),
        //                     array("limit":"1","rate":"0.05"),
        //                     array("limit":"5","rate":"0.075"),
        //                     array("limit":"10","rate":"0.1"),
        //                     array("limit":"9999999999999999","rate":"0")
        //                 ),
        //                 "kyc_deposit_limit":"10",
        //                 "kyc_withdrawal_limit":"2",
        //                 "min_withdrawal_amount":"0.001",
        //                 "minimum_precision":4,
        //                 "name":"Bitcoin",
        //                 "$precision":8,
        //                 "sort_priority":1,
        //                 "symbol":"BTC",
        //                 "variable_withdrawal_fee":"0",
        //                 "withdrawal_status":"enabled"
        //             ),
        //         ),
        //         "success":true
        //     }
        //
        $currencies = $this->safe_value($response, 'result', array());
        $result = array();
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $id = $this->safe_string($currency, 'symbol');
            $numericId = $this->safe_integer($currency, 'id');
            $code = $this->safe_currency_code($id);
            $depositStatus = $this->safe_string($currency, 'deposit_status');
            $withdrawalStatus = $this->safe_string($currency, 'withdrawal_status');
            $depositsEnabled = ($depositStatus === 'enabled');
            $withdrawalsEnabled = ($withdrawalStatus === 'enabled');
            $active = $depositsEnabled && $withdrawalsEnabled;
            $precision = $this->safe_integer($currency, 'precision');
            $result[$code] = array(
                'id' => $id,
                'numericId' => $numericId,
                'code' => $code,
                'name' => $this->safe_string($currency, 'name'),
                'info' => $currency, // the original payload
                'active' => $active,
                'fee' => $this->safe_float($currency, 'base_withdrawal_fee'),
                'precision' => 1 / pow(10, $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($currency, 'min_withdrawal_amount'),
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function load_markets($reload = false, $params = array ()) {
        $markets = parent::load_markets($reload, $params);
        $currenciesByNumericId = $this->safe_value($this->options, 'currenciesByNumericId');
        if (($currenciesByNumericId === null) || $reload) {
            $this->options['currenciesByNumericId'] = $this->index_by($this->currencies, 'numericId');
        }
        $marketsByNumericId = $this->safe_value($this->options, 'marketsByNumericId');
        if (($marketsByNumericId === null) || $reload) {
            $this->options['marketsByNumericId'] = $this->index_by($this->markets, 'numericId');
        }
        return $markets;
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetProducts ($params);
        //
        //     {
        //         "meta":array(
        //             "after":null,
        //             "before":null,
        //             "limit":100,
        //             "total_count":81
        //         ),
        //         "$result":[
        //             array(
        //                 "annualized_funding":"5.475000000000000000",
        //                 "is_quanto":false,
        //                 "ui_config":array(
        //                     "default_trading_view_candle":"15",
        //                     "leverage_slider_values":[1,3,5,10,25,50],
        //                     "price_clubbing_values":[0.001,0.005,0.05,0.1,0.5,1,5],
        //                     "show_bracket_orders":false,
        //                     "sort_priority":29,
        //                     "tags":array()
        //                 ),
        //                 "basis_factor_max_limit":"0.15",
        //                 "$symbol":"P-LINK-D-151120",
        //                 "$id":1584,
        //                 "default_leverage":"5.000000000000000000",
        //                 "maker_commission_rate":"0.0005",
        //                 "contract_unit_currency":"LINK",
        //                 "strike_price":"12.507948",
        //                 "settling_asset":array(
        //                     // asset structure
        //                 ),
        //                 "auction_start_time":null,
        //                 "auction_finish_time":null,
        //                 "settlement_time":"2020-11-15T12:00:00Z",
        //                 "launch_time":"2020-11-14T11:55:05Z",
        //                 "spot_index":array(
        //                     // index structure
        //                 ),
        //                 "trading_status":"operational",
        //                 "tick_size":"0.001",
        //                 "position_size_limit":100000,
        //                 "notional_type":"vanilla", // vanilla, inverse
        //                 "price_band":"0.4",
        //                 "barrier_price":null,
        //                 "description":"Daily LINK PUT options quoted in USDT and settled in USDT",
        //                 "insurance_fund_margin_contribution":"1",
        //                 "quoting_asset":array(
        //                     // asset structure
        //                 ),
        //                 "liquidation_penalty_factor":"0.2",
        //                 "product_specs":array("max_volatility":3,"min_volatility":0.3,"spot_price_band":"0.40"),
        //                 "initial_margin_scaling_factor":"0.0001",
        //                 "underlying_asset":array(
        //                     // asset structure
        //                 ),
        //                 "$state":"live",
        //                 "contract_value":"1",
        //                 "initial_margin":"2",
        //                 "impact_size":5000,
        //                 "settlement_price":null,
        //                 "contract_type":"put_options", // put_options, call_options, move_options, perpetual_futures, interest_rate_swaps, futures, spreads
        //                 "taker_commission_rate":"0.0005",
        //                 "maintenance_margin":"1",
        //                 "short_description":"LINK Daily PUT Options",
        //                 "maintenance_margin_scaling_factor":"0.00005",
        //                 "funding_method":"mark_price",
        //                 "max_leverage_notional":"20000"
        //             ),
        //         ],
        //         "success":true
        //     }
        //
        $markets = $this->safe_value($response, 'result', array());
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $type = $this->safe_string($market, 'contract_type');
            // $settlingAsset = $this->safe_value($market, 'settling_asset', array());
            $quotingAsset = $this->safe_value($market, 'quoting_asset', array());
            $underlyingAsset = $this->safe_value($market, 'underlying_asset', array());
            $baseId = $this->safe_string($underlyingAsset, 'symbol');
            $quoteId = $this->safe_string($quotingAsset, 'symbol');
            $id = $this->safe_string($market, 'symbol');
            $numericId = $this->safe_integer($market, 'id');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $id;
            $swap = false;
            $future = false;
            $option = false;
            if ($type === 'perpetual_futures') {
                $type = 'swap';
                $swap = true;
                $future = false;
                $option = false;
                $symbol = $base . '/' . $quote;
            } else if (($type === 'call_options') || ($type === 'put_options') || ($type === 'move_options')) {
                $type = 'option';
                $swap = false;
                $option = true;
                $future = false;
            } else if ($type === 'futures') {
                $type = 'future';
                $swap = false;
                $option = false;
                $future = true;
            }
            $precision = array(
                'amount' => 1.0, // number of contracts
                'price' => $this->safe_float($market, 'tick_size'),
            );
            $limits = array(
                'amount' => array(
                    'min' => 1.0,
                    'max' => $this->safe_float($market, 'position_size_limit'),
                ),
                'price' => array(
                    'min' => $precision['price'],
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_float($market, 'min_size'),
                    'max' => null,
                ),
            );
            $state = $this->safe_string($market, 'state');
            $active = ($state === 'live');
            $maker = $this->safe_float($market, 'maker_commission_rate');
            $taker = $this->safe_float($market, 'taker_commission_rate');
            $result[] = array(
                'id' => $id,
                'numericId' => $numericId,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'type' => $type,
                'option' => $option,
                'swap' => $swap,
                'future' => $future,
                'maker' => $maker,
                'taker' => $taker,
                'precision' => $precision,
                'limits' => $limits,
                'info' => $market,
                'active' => $active,
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker, fetchTickers
        //
        //     {
        //         "close":15837.5,
        //         "high":16354,
        //         "low":15751.5,
        //         "mark_price":"15820.100867",
        //         "$open":16140.5,
        //         "product_id":139,
        //         "size":640552,
        //         "spot_price":"15827.050000000001",
        //         "$symbol":"BTCUSDT",
        //         "$timestamp":1605373550208262,
        //         "turnover":10298630.3735,
        //         "turnover_symbol":"USDT",
        //         "turnover_usd":10298630.3735,
        //         "volume":640.5520000000001
        //     }
        //
        $timestamp = $this->safe_integer_product($ticker, 'timestamp', 0.001);
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_float($ticker, 'close');
        $open = $this->safe_float($ticker, 'open');
        $change = null;
        $average = null;
        $percentage = null;
        if (($open !== null) && ($last !== null)) {
            $change = $last - $open;
            $average = $this->sum($last, $open) / 2;
            if ($open !== 0.0) {
                $percentage = ($change / $open) * 100;
            }
        }
        $baseVolume = $this->safe_float($ticker, 'volume');
        $quoteVolume = $this->safe_float($ticker, 'turnover');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high'),
            'low' => $this->safe_float($ticker, 'low'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetTickersSymbol (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             "close":15837.5,
        //             "high":16354,
        //             "low":15751.5,
        //             "mark_price":"15820.100867",
        //             "open":16140.5,
        //             "product_id":139,
        //             "size":640552,
        //             "spot_price":"15827.050000000001",
        //             "$symbol":"BTCUSDT",
        //             "timestamp":1605373550208262,
        //             "turnover":10298630.3735,
        //             "turnover_symbol":"USDT",
        //             "turnover_usd":10298630.3735,
        //             "volume":640.5520000000001
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_ticker($result, $market);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->publicGetTickers ($params);
        //
        //     {
        //         "$result":array(
        //             array(
        //                 "close":0.003966,
        //                 "high":0.004032,
        //                 "low":0.003606,
        //                 "mark_price":"0.00396328",
        //                 "open":0.003996,
        //                 "product_id":1327,
        //                 "size":6242,
        //                 "spot_price":"0.0039555",
        //                 "$symbol":"AAVEBTC",
        //                 "timestamp":1605374143864107,
        //                 "turnover":23.997904999999996,
        //                 "turnover_symbol":"BTC",
        //                 "turnover_usd":387957.4544782897,
        //                 "volume":6242
        //             ),
        //         ),
        //         "success":true
        //     }
        //
        $tickers = $this->safe_value($response, 'result', array());
        $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 ()) {
        $this->load_markets();
        $request = array(
            'symbol' => $this->market_id($symbol),
        );
        if ($limit !== null) {
            $request['depth'] = $limit;
        }
        $response = $this->publicGetL2orderbookSymbol (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             "buy":array(
        //                 array("price":"15814.0","size":912),
        //                 array("price":"15813.5","size":1279),
        //                 array("price":"15813.0","size":1634),
        //             ),
        //             "sell":array(
        //                 array("price":"15814.5","size":625),
        //                 array("price":"15815.0","size":982),
        //                 array("price":"15815.5","size":1328),
        //             ),
        //             "$symbol":"BTCUSDT"
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_order_book($result, null, 'buy', 'sell', 'price', 'size');
    }

    public function parse_trade($trade, $market = null) {
        //
        // public fetchTrades
        //
        //     {
        //         "buyer_role":"maker",
        //         "$price":"15896.5",
        //         "seller_role":"taker",
        //         "size":241,
        //         "$symbol":"BTCUSDT",
        //         "$timestamp":1605376684714595
        //     }
        //
        // private fetchMyTrades
        //
        //     {
        //         "commission":"0.008335000000000000",
        //         "created_at":"2020-11-16T19:07:19Z",
        //         "fill_type":"normal",
        //         "$id":"e7ff05c233a74245b72381f8dd91d1ce",
        //         "meta_data":array(
        //             "effective_commission_rate":"0.0005",
        //             "order_price":"16249",
        //             "order_size":1,
        //             "order_type":"market_order",
        //             "order_unfilled_size":0,
        //             "trading_fee_credits_used":"0"
        //         ),
        //         "order_id":"152999629",
        //         "$price":"16669",
        //         "$product":array(
        //             "contract_type":"perpetual_futures",
        //             "contract_unit_currency":"BTC",
        //             "contract_value":"0.001",
        //             "$id":139,
        //             "notional_type":"vanilla",
        //             "quoting_asset":array("minimum_precision":2,"precision":6,"$symbol":"USDT"),
        //             "settling_asset":array("minimum_precision":2,"precision":6,"$symbol":"USDT"),
        //             "$symbol":"BTCUSDT",
        //             "tick_size":"0.5",
        //             "underlying_asset":array("minimum_precision":4,"precision":8,"$symbol":"BTC")
        //         ),
        //         "product_id":139,
        //         "role":"taker",
        //         "$side":"sell",
        //         "size":1
        //     }
        //
        $id = $this->safe_string($trade, 'id');
        $orderId = $this->safe_string($trade, 'order_id');
        $timestamp = $this->parse8601($this->safe_string($trade, 'created_at'));
        $timestamp = $this->safe_integer_product($trade, 'timestamp', 0.001, $timestamp);
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'size');
        $cost = null;
        if (($amount !== null) && ($price !== null)) {
            $cost = $amount * $price;
        }
        $product = $this->safe_value($trade, 'product', array());
        $marketId = $this->safe_string($product, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $sellerRole = $this->safe_string($trade, 'seller_role');
        $side = $this->safe_string($trade, 'side');
        if ($side === null) {
            if ($sellerRole === 'taker') {
                $side = 'sell';
            } else if ($sellerRole === 'maker') {
                $side = 'buy';
            }
        }
        $takerOrMaker = $this->safe_string($trade, 'role');
        $metaData = $this->safe_value($trade, 'meta_data', array());
        $type = $this->safe_string($metaData, 'order_type');
        if ($type !== null) {
            $type = str_replace('_order', '', $type);
        }
        $feeCost = $this->safe_float($trade, 'commission');
        $fee = null;
        if ($feeCost !== null) {
            $settlingAsset = $this->safe_value($product, 'settling_asset', array());
            $feeCurrencyId = $this->safe_string($settlingAsset, 'symbol');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        return array(
            'id' => $id,
            'order' => $orderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'takerOrMaker' => $takerOrMaker,
            'fee' => $fee,
            'info' => $trade,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->publicGetTradesSymbol (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             {
        //                 "buyer_role":"maker",
        //                 "price":"15896.5",
        //                 "seller_role":"taker",
        //                 "size":241,
        //                 "$symbol":"BTCUSDT",
        //                 "timestamp":1605376684714595
        //             }
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_trades($result, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "time":1605393120,
        //         "open":15989,
        //         "high":15989,
        //         "low":15987.5,
        //         "close":15987.5,
        //         "volume":565
        //     }
        //
        return array(
            $this->safe_timestamp($ohlcv, 'time'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'volume'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'resolution' => $this->timeframes[$timeframe],
        );
        $duration = $this->parse_timeframe($timeframe);
        $limit = $limit ? $limit : 2000; // max 2000
        if ($since === null) {
            $end = $this->seconds();
            $request['end'] = $end;
            $request['start'] = $end - $limit * $duration;
        } else {
            $start = intval($since / 1000);
            $request['start'] = $start;
            $request['end'] = $this->sum($start, $limit * $duration);
        }
        $response = $this->publicGetHistoryCandles (array_merge($request, $params));
        //
        //     {
        //         "success":true,
        //         "$result":array(
        //             array("time":1605393120,"open":15989,"high":15989,"low":15987.5,"close":15987.5,"volume":565),
        //             array("time":1605393180,"open":15966,"high":15966,"low":15959,"close":15959,"volume":24),
        //             array("time":1605393300,"open":15973,"high":15973,"low":15973,"close":15973,"volume":1288),
        //         )
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_ohlcvs($result, $market, $timeframe, $since, $limit);
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $response = $this->privateGetWalletBalances ($params);
        //
        //     {
        //         "$result":array(
        //             array(
        //                 "asset_id":1,
        //                 "available_balance":"0",
        //                 "$balance":"0",
        //                 "commission":"0",
        //                 "id":154883,
        //                 "interest_credit":"0",
        //                 "order_margin":"0",
        //                 "pending_referral_bonus":"0",
        //                 "pending_trading_fee_credit":"0",
        //                 "position_margin":"0",
        //                 "trading_fee_credit":"0",
        //                 "user_id":22142
        //             ),
        //         ),
        //         "success":true
        //     }
        //
        $balances = $this->safe_value($response, 'result', array());
        $result = array( 'info' => $response );
        $currenciesByNumericId = $this->safe_value($this->options, 'currenciesByNumericId', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'asset_id');
            $currency = $this->safe_value($currenciesByNumericId, $currencyId);
            $code = ($currency === null) ? $currencyId : $currency['code'];
            $account = $this->account();
            $account['total'] = $this->safe_float($balance, 'balance');
            $account['free'] = $this->safe_float($balance, 'available_balance');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function fetch_position($symbol, $params = null) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'product_id' => $market['numericId'],
        );
        $response = $this->privateGetPositions (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             "entry_price":null,
        //             "size":0,
        //             "timestamp":1605454074268079
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $result;
    }

    public function fetch_positions($symbols = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $response = $this->privateGetPositionsMargined ($params);
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             {
        //                 "user_id" => 0,
        //                 "size" => 0,
        //                 "entry_price" => "string",
        //                 "margin" => "string",
        //                 "liquidation_price" => "string",
        //                 "bankruptcy_price" => "string",
        //                 "adl_level" => 0,
        //                 "product_id" => 0
        //             }
        //         )
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $result;
    }

    public function parse_order_status($status) {
        $statuses = array(
            'open' => 'open',
            'pending' => 'open',
            'closed' => 'closed',
            'cancelled' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder, cancelOrder, editOrder, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "average_fill_price":null,
        //         "bracket_order":null,
        //         "bracket_stop_loss_limit_price":null,
        //         "bracket_stop_loss_price":null,
        //         "bracket_take_profit_limit_price":null,
        //         "bracket_take_profit_price":null,
        //         "bracket_trail_amount":null,
        //         "cancellation_reason":null,
        //         "client_order_id":null,
        //         "close_on_trigger":"false",
        //         "commission":"0",
        //         "created_at":"2020-11-16T02:38:26Z",
        //         "$id":152870626,
        //         "limit_price":"10000",
        //         "meta_data":array("source":"api"),
        //         "order_type":"limit_order",
        //         "paid_commission":"0",
        //         "product_id":139,
        //         "reduce_only":false,
        //         "$side":"buy",
        //         "size":0,
        //         "state":"open",
        //         "stop_order_type":null,
        //         "stop_price":null,
        //         "stop_trigger_method":"mark_price",
        //         "time_in_force":"gtc",
        //         "trail_amount":null,
        //         "unfilled_size":0,
        //         "user_id":22142
        //     }
        //
        $id = $this->safe_string($order, 'id');
        $clientOrderId = $this->safe_string($order, 'client_order_id');
        $timestamp = $this->parse8601($this->safe_string($order, 'created_at'));
        $marketId = $this->safe_string($order, 'product_id');
        $marketsByNumericId = $this->safe_value($this->options, 'marketsByNumericId', array());
        $market = $this->safe_value($marketsByNumericId, $marketId, $market);
        $symbol = ($market === null) ? $marketId : $market['symbol'];
        $status = $this->parse_order_status($this->safe_string($order, 'state'));
        $side = $this->safe_string($order, 'side');
        $type = $this->safe_string($order, 'order_type');
        $type = str_replace('_order', '', $type);
        $price = $this->safe_float($order, 'limit_price');
        $amount = $this->safe_float($order, 'size');
        $remaining = $this->safe_float($order, 'unfilled_size');
        $filled = null;
        if (($amount !== null) && ($remaining !== null)) {
            $filled = max (0, $amount - $remaining);
        }
        $cost = null;
        $average = $this->safe_float($order, 'average_fill_price');
        if (($average !== null) && $filled) {
            $cost = $average * $filled;
        }
        $fee = null;
        $feeCost = $this->safe_float($order, 'paid_commission');
        if ($feeCost !== null) {
            $feeCurrencyCode = null;
            if ($market !== null) {
                $settlingAsset = $this->safe_value($market['info'], 'settling_asset', array());
                $feeCurrencyId = $this->safe_string($settlingAsset, 'symbol');
                $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            }
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        );
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $orderType = $type . '_order';
        $market = $this->market($symbol);
        $request = array(
            'product_id' => $market['numericId'],
            // 'limit_price' => $this->price_to_precision($symbol, $price),
            'size' => $this->amount_to_precision($symbol, $amount),
            'side' => $side,
            'order_type' => $orderType,
            // 'client_order_id' => 'string',
            // 'time_in_force' => 'gtc', // gtc, ioc, fok
            // 'post_only' => 'false', // 'true',
            // 'reduce_only' => 'false', // 'true',
        );
        if ($type === 'limit') {
            $request['limit_price'] = $this->price_to_precision($symbol, $price);
        }
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'client_order_id');
        $params = $this->omit($params, array( 'clientOrderId', 'client_order_id' ));
        if ($clientOrderId !== null) {
            $request['client_order_id'] = $clientOrderId;
        }
        $response = $this->privatePostOrders (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             "average_fill_price":null,
        //             "bracket_order":null,
        //             "bracket_stop_loss_limit_price":null,
        //             "bracket_stop_loss_price":null,
        //             "bracket_take_profit_limit_price":null,
        //             "bracket_take_profit_price":null,
        //             "bracket_trail_amount":null,
        //             "cancellation_reason":null,
        //             "client_order_id":null,
        //             "close_on_trigger":"false",
        //             "commission":"0",
        //             "created_at":"2020-11-16T02:38:26Z",
        //             "id":152870626,
        //             "limit_price":"10000",
        //             "meta_data":array("source":"api"),
        //             "order_type":"limit_order",
        //             "paid_commission":"0",
        //             "product_id":139,
        //             "reduce_only":false,
        //             "$side":"buy",
        //             "size":0,
        //             "state":"open",
        //             "stop_order_type":null,
        //             "stop_price":null,
        //             "stop_trigger_method":"mark_price",
        //             "time_in_force":"gtc",
        //             "trail_amount":null,
        //             "unfilled_size":0,
        //             "user_id":22142
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_order($result, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'id' => intval($id),
            'product_id' => $market['numericId'],
            // 'limit_price' => $this->price_to_precision($symbol, $price),
            // 'size' => $this->amount_to_precision($symbol, $amount),
        );
        if ($amount !== null) {
            $request['size'] = intval($this->amount_to_precision($symbol, $amount));
        }
        if ($price !== null) {
            $request['limit_price'] = $this->price_to_precision($symbol, $price);
        }
        $response = $this->privatePutOrders (array_merge($request, $params));
        //
        //     {
        //         "success" => true,
        //         "$result" => {
        //             "$id" => "ashb1212",
        //             "product_id" => 27,
        //             "limit_price" => "9200",
        //             "$side" => "buy",
        //             "size" => 100,
        //             "unfilled_size" => 50,
        //             "user_id" => 1,
        //             "order_type" => "limit_order",
        //             "state" => "open",
        //             "created_at" => "..."
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result');
        return $this->parse_order($result, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'id' => intval($id),
            'product_id' => $market['numericId'],
        );
        $response = $this->privateDeleteOrders (array_merge($request, $params));
        //
        //     {
        //         "$result":array(
        //             "average_fill_price":null,
        //             "bracket_order":null,
        //             "bracket_stop_loss_limit_price":null,
        //             "bracket_stop_loss_price":null,
        //             "bracket_take_profit_limit_price":null,
        //             "bracket_take_profit_price":null,
        //             "bracket_trail_amount":null,
        //             "cancellation_reason":"cancelled_by_user",
        //             "client_order_id":null,
        //             "close_on_trigger":"false",
        //             "commission":"0",
        //             "created_at":"2020-11-16T02:38:26Z",
        //             "$id":152870626,
        //             "limit_price":"10000",
        //             "meta_data":array("source":"api"),
        //             "order_type":"limit_order",
        //             "paid_commission":"0",
        //             "product_id":139,
        //             "reduce_only":false,
        //             "side":"buy",
        //             "size":0,
        //             "state":"cancelled",
        //             "stop_order_type":null,
        //             "stop_price":null,
        //             "stop_trigger_method":"mark_price",
        //             "time_in_force":"gtc",
        //             "trail_amount":null,
        //             "unfilled_size":0,
        //             "user_id":22142
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result');
        return $this->parse_order($result, $market);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'product_id' => $market['numericId'],
            // 'cancel_limit_orders' => 'true',
            // 'cancel_stop_orders' => 'true',
        );
        $response = $this->privateDeleteOrdersAll (array_merge($request, $params));
        //
        //     {
        //         "result":array(),
        //         "success":true
        //     }
        //
        return $response;
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        return $this->fetch_orders_with_method('privateGetOrders', $symbol, $since, $limit, $params);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        return $this->fetch_orders_with_method('privateGetOrdersHistory', $symbol, $since, $limit, $params);
    }

    public function fetch_orders_with_method($method, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'product_ids' => $market['id'], // comma-separated
            // 'contract_types' => types, // comma-separated, futures, perpetual_futures, call_options, put_options, interest_rate_swaps, move_options, spreads
            // 'order_types' => types, // comma-separated, $market, $limit, stop_market, stop_limit, all_stop
            // 'start_time' => $since * 1000,
            // 'end_time' => $this->microseconds(),
            // 'after' => string, // after cursor for pagination
            // 'before' => string, // before cursor for pagination
            // 'page_size' => $limit, // number of records per page
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['product_ids'] = $market['numericId']; // accepts a comma-separated list of ids
        }
        if ($since !== null) {
            $request['start_time'] = (string) $since . '000';
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             {
        //                 "id" => "ashb1212",
        //                 "product_id" => 27,
        //                 "limit_price" => "9200",
        //                 "side" => "buy",
        //                 "size" => 100,
        //                 "unfilled_size" => 50,
        //                 "user_id" => 1,
        //                 "order_type" => "limit_order",
        //                 "state" => "open",
        //                 "created_at" => "..."
        //             }
        //         ),
        //         "meta" => {
        //             "after" => "string",
        //             "before" => "string"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'product_ids' => $market['id'], // comma-separated
            // 'contract_types' => types, // comma-separated, futures, perpetual_futures, call_options, put_options, interest_rate_swaps, move_options, spreads
            // 'start_time' => $since * 1000,
            // 'end_time' => $this->microseconds(),
            // 'after' => string, // after cursor for pagination
            // 'before' => string, // before cursor for pagination
            // 'page_size' => $limit, // number of records per page
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['product_ids'] = $market['numericId']; // accepts a comma-separated list of ids
        }
        if ($since !== null) {
            $request['start_time'] = (string) $since . '000';
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        $response = $this->privateGetFills (array_merge($request, $params));
        //
        //     {
        //         "meta":array(
        //             "after":null,
        //             "before":null,
        //             "$limit":10,
        //             "total_count":2
        //         ),
        //         "$result":array(
        //             {
        //                 "commission":"0.008335000000000000",
        //                 "created_at":"2020-11-16T19:07:19Z",
        //                 "fill_type":"normal",
        //                 "id":"e7ff05c233a74245b72381f8dd91d1ce",
        //                 "meta_data":array(
        //                     "effective_commission_rate":"0.0005",
        //                     "order_price":"16249",
        //                     "order_size":1,
        //                     "order_type":"market_order",
        //                     "order_unfilled_size":0,
        //                     "trading_fee_credits_used":"0"
        //                 ),
        //                 "order_id":"152999629",
        //                 "price":"16669",
        //                 "product":array(
        //                     "contract_type":"perpetual_futures",
        //                     "contract_unit_currency":"BTC",
        //                     "contract_value":"0.001",
        //                     "id":139,
        //                     "notional_type":"vanilla",
        //                     "quoting_asset":array("minimum_precision":2,"precision":6,"$symbol":"USDT"),
        //                     "settling_asset":array("minimum_precision":2,"precision":6,"$symbol":"USDT"),
        //                     "$symbol":"BTCUSDT",
        //                     "tick_size":"0.5",
        //                     "underlying_asset":array("minimum_precision":4,"precision":8,"$symbol":"BTC")
        //                 ),
        //                 "product_id":139,
        //                 "role":"taker",
        //                 "side":"sell",
        //                 "size":1
        //             }
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_trades($result, $market, $since, $limit);
    }

    public function fetch_ledger($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'asset_id' => $currency['numericId'],
            // 'end_time' => $this->seconds(),
            // 'after' => 'string', // after cursor for pagination
            // 'before' => 'string', // before cursor for pagination
            // 'page_size' => $limit,
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['asset_id'] = $currency['numericId'];
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        $response = $this->privateGetWalletTransactions (array_merge($request, $params));
        //
        //     {
        //         "meta":array("after":null,"before":null,"$limit":10,"total_count":1),
        //         "$result":array(
        //             {
        //                 "amount":"29.889184",
        //                 "asset_id":5,
        //                 "balance":"29.889184",
        //                 "created_at":"2020-11-15T21:25:01Z",
        //                 "meta_data":array(
        //                     "deposit_id":3884,
        //                     "transaction_id":"0x41a60174849828530abb5008e98fc63c9b598288743ec4ba9620bcce900a3b8d"
        //                 ),
        //                 "transaction_type":"deposit",
        //                 "user_id":22142,
        //                 "uuid":"70bb5679da3c4637884e2dc63efaa846"
        //             }
        //         ),
        //         "success":true
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_ledger($result, $currency, $since, $limit);
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'pnl' => 'pnl',
            'deposit' => 'transaction',
            'withdrawal' => 'transaction',
            'commission' => 'fee',
            'conversion' => 'trade',
            // 'perpetual_futures_funding' => 'perpetual_futures_funding',
            // 'withdrawal_cancellation' => 'withdrawal_cancellation',
            'referral_bonus' => 'referral',
            'commission_rebate' => 'rebate',
            // 'promo_credit' => 'promo_credit',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry($item, $currency = null) {
        //
        //     {
        //         "$amount":"29.889184",
        //         "asset_id":5,
        //         "balance":"29.889184",
        //         "created_at":"2020-11-15T21:25:01Z",
        //         "meta_data":array(
        //             "deposit_id":3884,
        //             "transaction_id":"0x41a60174849828530abb5008e98fc63c9b598288743ec4ba9620bcce900a3b8d"
        //         ),
        //         "transaction_type":"deposit",
        //         "user_id":22142,
        //         "uuid":"70bb5679da3c4637884e2dc63efaa846"
        //     }
        //
        $id = $this->safe_string($item, 'uuid');
        $direction = null;
        $account = null;
        $metaData = $this->safe_value($item, 'meta_data', array());
        $referenceId = $this->safe_string($metaData, 'transaction_id');
        $referenceAccount = null;
        $type = $this->safe_string($item, 'transaction_type');
        if (($type === 'deposit') || ($type === 'commission_rebate') || ($type === 'referral_bonus') || ($type === 'pnl') || ($type === 'withdrawal_cancellation') || ($type === 'promo_credit')) {
            $direction = 'in';
        } else if (($type === 'withdrawal') || ($type === 'commission') || ($type === 'conversion') || ($type === 'perpetual_futures_funding')) {
            $direction = 'out';
        }
        $type = $this->parse_ledger_entry_type($type);
        $currencyId = $this->safe_integer($item, 'asset_id');
        $currenciesByNumericId = $this->safe_value($this->options, 'currenciesByNumericId');
        $currency = $this->safe_value($currenciesByNumericId, $currencyId, $currency);
        $code = ($currency === null) ? null : $currency['code'];
        $amount = $this->safe_float($item, 'amount');
        $timestamp = $this->parse8601($this->safe_string($item, 'created_at'));
        $after = $this->safe_float($item, 'balance');
        $before = max (0, $after - $amount);
        $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' => null,
        );
    }

    public function fetch_deposit_address($code, $params = array ()) {
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset_symbol' => $currency['id'],
        );
        $response = $this->privateGetDepositsAddress (array_merge($request, $params));
        //
        //     {
        //         "success":true,
        //         "$result":{
        //             "id":19628,
        //             "user_id":22142,
        //             "$address":"0x0eda26523397534f814d553a065d8e46b4188e9a",
        //             "status":"active",
        //             "updated_at":"2020-11-15T20:25:53.000Z",
        //             "created_at":"2020-11-15T20:25:53.000Z",
        //             "asset_symbol":"USDT",
        //             "custodian":"onc"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $address = $this->safe_string($result, 'address');
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => null,
            'info' => $response,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $requestPath = '/' . $this->version . '/' . $this->implode_params($path, $params);
        $url = $this->urls['api'][$api] . $requestPath;
        $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();
            $timestamp = (string) $this->seconds();
            $headers = array(
                'api-key' => $this->apiKey,
                'timestamp' => $timestamp,
            );
            $auth = $method . $timestamp . $requestPath;
            if (($method === 'GET') || ($method === 'DELETE')) {
                if ($query) {
                    $queryString = '?' . $this->urlencode($query);
                    $auth .= $queryString;
                    $url .= $queryString;
                }
            } else {
                $body = $this->json($query);
                $auth .= $body;
                $headers['Content-Type'] = 'application/json';
            }
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret));
            $headers['signature'] = $signature;
        }
        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":array("$code":"insufficient_margin","context":array("available_balance":"0.000000000000000000","required_additional_balance":"1.618626000000000000000000000")),"success":false)
        //
        $error = $this->safe_value($response, 'error', array());
        $errorCode = $this->safe_string($error, 'code');
        if ($errorCode !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorCode, $feedback);
            throw new ExchangeError($feedback); // unknown message
        }
    }
}
