<?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\InvalidOrder;
use \ccxt\OrderNotFound;

class aofex extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'aofex',
            'name' => 'AOFEX',
            'countries' => array( 'GB' ),
            'rateLimit' => 1000,
            'has' => array(
                'fetchMarkets' => true,
                'fetchCurrencies' => false,
                'fetchOrderBook' => true,
                'fetchTrades' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchOHLCV' => true,
                'fetchBalance' => true,
                'createOrder' => true,
                'cancelOrder' => true,
                'cancelAllOrders' => true,
                'fetchOpenOrders' => true,
                'fetchClosedOrders' => true,
                'fetchClosedOrder' => true,
                'fetchOrderTrades' => true,
                'fetchTradingFee' => true,
            ),
            'timeframes' => array(
                '1m' => '1min',
                '5m' => '5min',
                '15m' => '15min',
                '30m' => '30min',
                '1h' => '1hour',
                '6h' => '6hour',
                '12h' => '12hour',
                '1d' => '1day',
                '1w' => '1week',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/77670271-056d1080-6f97-11ea-9ac2-4268e9ed0c1f.jpg',
                'api' => array(
                    'public' => 'https://openapi.aofex.com/openApi',
                    'private' => 'https://openapi.aofex.com/openApi',
                ),
                'www' => 'https://aofex.com',
                'doc' => 'https://aofex.zendesk.com/hc/en-us/sections/360005576574-API',
                'fees' => 'https://aofex.zendesk.com/hc/en-us/articles/360025814934-Fees-on-AOFEX',
                'referral' => 'https://aofex.com/#/register?key=9763840',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'market/symbols',
                        'market/trade',
                        'market/depth',
                        'market/kline',
                        'market/precision',
                        'market/24kline',
                        'market/gears_depth',
                        'market/detail',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'entrust/currentList',
                        'entrust/historyList',
                        'entrust/rate',
                        'wallet/list',
                        'entrust/detail',
                    ),
                    'post' => array(
                        'entrust/add',
                        'entrust/cancel',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'maker' => 0.0019,
                    'taker' => 0.002,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    '20001' => '\\ccxt\\ExchangeError', // request failure
                    '20401' => '\\ccxt\\PermissionDenied', // no permission
                    '20500' => '\\ccxt\\ExchangeError', // system error
                    '20501' => '\\ccxt\\BadSymbol', // base symbol error
                    '20502' => '\\ccxt\\ExchangeError', // base currency error
                    '20503' => '\\ccxt\\ExchangeError', // base date error
                    '20504' => '\\ccxt\\InsufficientFunds', // account frozen balance insufficient error
                    '20505' => '\\ccxt\\BadRequest', // bad argument
                    '20506' => '\\ccxt\\AuthenticationError', // api signature not valid
                    '20507' => '\\ccxt\\ExchangeError', // gateway internal error
                    '20508' => '\\ccxt\\InvalidAddress', // ad ethereum addresss
                    '20509' => '\\ccxt\\InsufficientFunds', // order accountbalance error
                    '20510' => '\\ccxt\\InvalidOrder', // order limitorder price error
                    '20511' => '\\ccxt\\InvalidOrder', // order limitorder amount error
                    '20512' => '\\ccxt\\InvalidOrder', // order orderprice precision error
                    '20513' => '\\ccxt\\InvalidOrder', // order orderamount precision error
                    '20514' => '\\ccxt\\InvalidOrder', // order marketorder amount error
                    '20515' => '\\ccxt\\InvalidOrder', // order queryorder invalid
                    '20516' => '\\ccxt\\InvalidOrder', // order orderstate error
                    '20517' => '\\ccxt\\InvalidOrder', // order datelimit error
                    '50518' => '\\ccxt\\InvalidOrder', // order update error
                    '20519' => '\\ccxt\\InvalidNonce', // the nonce has been used
                    '20520' => '\\ccxt\\InvalidNonce', // nonce expires, please verify server time
                    '20521' => '\\ccxt\\BadRequest', // incomplete header parameters
                    '20522' => '\\ccxt\\ExchangeError', // not getting the current user
                    '20523' => '\\ccxt\\AuthenticationError', // please authenticate
                    '20524' => '\\ccxt\\PermissionDenied', // btc account lockout
                    '20525' => '\\ccxt\\AuthenticationError', // get API Key error
                    '20526' => '\\ccxt\\PermissionDenied', // no query permission
                    '20527' => '\\ccxt\\PermissionDenied', // no deal permission
                    '20528' => '\\ccxt\\PermissionDenied', // no withdrawal permission
                    '20529' => '\\ccxt\\AuthenticationError', // API Key expired
                    '20530' => '\\ccxt\\PermissionDenied', // no permission
                ),
                'broad' => array(
                ),
            ),
            'options' => array(
                'fetchBalance' => array(
                    'show_all' => '0', // '1' to show zero balances
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $markets = yield $this->publicGetMarketSymbols ($params);
        //
        //     {
        //         errno => 0,
        //         errmsg => 'success',
        //         $result => array(
        //             array(
        //                 $id => 2,
        //                 $symbol => 'BTC-USDT',
        //                 base_currency => 'BTC',
        //                 quote_currency => 'USDT',
        //                 min_size => 0.00008,
        //                 max_size => 1300,
        //                 min_price => 1000,
        //                 max_price => 110000,
        //                 maker_fee => 1,
        //                 taker_fee => 1,
        //                 isHot => null,
        //                 isNew => null,
        //                 crown => null
        //             ),
        //         )
        //     }
        //
        $precisions = yield $this->publicGetMarketPrecision ();
        //
        //     {
        //         errno => 0,
        //         errmsg => 'success',
        //         $result => {
        //             'MANA-USDT' => array(
        //                 amount => '2',
        //                 minQuantity => '32',
        //                 maxQuantity => '46000000',
        //                 price => '4',
        //                 minPrice => '0.003',
        //                 maxPrice => '0.35'
        //             ),
        //         }
        //     }
        //
        $precisions = $this->safe_value($precisions, 'result', array());
        $markets = $this->safe_value($markets, 'result', array());
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $id = $this->safe_string($market, 'symbol');
            $baseId = $this->safe_string($market, 'base_currency');
            $quoteId = $this->safe_string($market, 'quote_currency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $base . '/' . $quote;
            $numericId = $this->safe_integer($market, 'id');
            $precision = $this->safe_value($precisions, $id, array());
            $makerFee = $this->safe_float($market, 'maker_fee');
            $takerFee = $this->safe_float($market, 'taker_fee');
            $result[] = array(
                'id' => $id,
                'numericId' => $numericId,
                'symbol' => $symbol,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'base' => $base,
                'quote' => $quote,
                'active' => null,
                'maker' => $makerFee / 1000,
                'taker' => $takerFee / 1000,
                'precision' => array(
                    'amount' => $this->safe_integer($precision, 'amount'),
                    'price' => $this->safe_integer($precision, 'price'),
                ),
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($market, 'min_size'),
                        'max' => $this->safe_float($market, 'max_size'),
                    ),
                    'price' => array(
                        'min' => $this->safe_float($market, 'min_price'),
                        'max' => $this->safe_float($market, 'max_price'),
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'info' => $market,
            );
        }
        return $result;
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         id =>  1584950100,
        //         amount => "329.196",
        //         count =>  81,
        //         open => "0.021155",
        //         close => "0.021158",
        //         low => "0.021144",
        //         high => "0.021161",
        //         vol => "6.963557767"
        //     }
        //
        return array(
            $this->safe_timestamp($ohlcv, 'id'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'amount'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        if ($limit === null) {
            $limit = 150; // default 150, max 2000
        }
        $request = array(
            'symbol' => $market['id'],
            'period' => $this->timeframes[$timeframe],
            'size' => $limit, // default 150, max 2000
        );
        $response = yield $this->publicGetMarketKline (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => "success",
        //         $result => {
        //             ts =>  1584950139003,
        //             $symbol => "ETH-BTC",
        //             period => "1min",
        //             $data => array(
        //                 array(
        //                     id =>  1584950100,
        //                     amount => "329.196",
        //                     count =>  81,
        //                     open => "0.021155",
        //                     close => "0.021158",
        //                     low => "0.021144",
        //                     high => "0.021161",
        //                     vol => "6.963557767"
        //                 ),
        //                 array(
        //                     id =>  1584950040,
        //                     amount => "513.265",
        //                     count =>  151,
        //                     open => "0.021165",
        //                     close => "0.021155",
        //                     low => "0.021151",
        //                     high => "0.02118",
        //                     vol => "10.862806573"
        //                 ),
        //             )
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_ohlcvs($data, $market, $since, $limit);
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchBalance', array());
        $showAll = $this->safe_value($options, 'show_all', '0');
        $request = array(
            // 'currency' => 'BTC',
            'show_all' => $showAll, // required to show zero $balances
        );
        $response = yield $this->privateGetWalletList (array_merge($request, $params));
        //
        //     {
        //         "errno" => 0,
        //         "errmsg" => "success",
        //         "$result" => array(
        //             array( "available" => "0", "frozen" => "0", "currency" => "BTC" )
        //         )
        //     }
        //
        $result = array( 'info' => $response );
        $balances = $this->safe_value($response, 'result', 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, 'frozen');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function fetch_trading_fee($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = yield $this->privateGetEntrustRate (array_merge($request, $params));
        //
        //     {
        //         "errno":0,
        //         "errmsg":"success",
        //         "$result" => {
        //             "toFee":"0.002","fromFee":"0.002"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return array(
            'info' => $response,
            'symbol' => $symbol,
            'maker' => $this->safe_float($result, 'fromFee'),
            'taker' => $this->safe_float($result, 'toFee'),
        );
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'symbol' => $this->market_id($symbol),
        );
        $response = yield $this->publicGetMarketDepth (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => "success",
        //         $result => {
        //             buyType => 1,
        //             sellType => 1,
        //             ts => 1584950701050,
        //             $symbol => "ETH-BTC",
        //             asks => [
        //                 ["0.021227", "0.182"],
        //                 ["0.021249", "0.035"],
        //                 ["0.021253", "0.058"],
        //             ],
        //             bids => [
        //                 ["0.021207", "0.039"],
        //                 ["0.021203", "0.051"],
        //                 ["0.02117", "2.326"],
        //             ]
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $timestamp = $this->safe_integer($result, 'ts');
        return $this->parse_order_book($result, $timestamp);
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker
        //
        //     {
        //         id => 1584890087,
        //         amount => '150032.919',
        //         count => 134538,
        //         $open => '0.021394',
        //         close => '0.021177',
        //         low => '0.021053',
        //         high => '0.021595',
        //         vol => '3201.72451442'
        //     }
        //
        $timestamp = $this->safe_timestamp($ticker, 'id');
        $symbol = null;
        if ($market) {
            $symbol = $market['symbol'];
        }
        $open = $this->safe_float($ticker, 'open');
        $last = $this->safe_float($ticker, 'close');
        $change = null;
        if ($symbol !== null) {
            $change = floatval($this->price_to_precision($symbol, $last - $open));
        } else {
            $change = $last - $open;
        }
        $average = $this->sum($last, $open) / 2;
        $percentage = $change / $open * 100;
        $baseVolume = $this->safe_float($ticker, 'amount');
        $quoteVolume = $this->safe_float($ticker, 'vol');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        if ($vwap !== null) {
            $vwap = floatval($this->price_to_precision($symbol, $vwap));
        }
        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_tickers($symbols = null, $params = array ()) {
        yield $this->load_markets();
        $request = array();
        if ($symbols !== null) {
            $ids = $this->market_ids($symbols);
            $request['symbol'] = implode(',', $ids);
        }
        $response = yield $this->publicGetMarket24kline (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => "success",
        //         $result => array(
        //             {
        //                 $symbol => "HB-AQ",
        //                 $data => array(
        //                     id =>  1584893403,
        //                     amount => "4753751.243400354852648809",
        //                     count =>  4724,
        //                     open => "6.3497",
        //                     close => "6.3318",
        //                     low => "6.011",
        //                     high => "6.5",
        //                     vol => "29538384.7873528796542891343493"
        //                 }
        //             ),
        //         )
        //     }
        //
        $tickers = $this->safe_value($response, 'result', array());
        $result = array();
        for ($i = 0; $i < count($tickers); $i++) {
            $marketId = $this->safe_string($tickers[$i], 'symbol');
            $market = $this->safe_market($marketId, null, '-');
            $symbol = $market['symbol'];
            $data = $this->safe_value($tickers[$i], 'data', array());
            $result[$symbol] = $this->parse_ticker($data, $market);
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = yield $this->publicGetMarketDetail (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => 'success',
        //         $result => {
        //             id => 1584890087,
        //             amount => '150032.919',
        //             count => 134538,
        //             open => '0.021394',
        //             close => '0.021177',
        //             low => '0.021053',
        //             high => '0.021595',
        //             vol => '3201.72451442'
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_ticker($result, $market);
    }

    public function parse_trade($trade, $market = null) {
        //
        // fetchTrades (public)
        //
        //     {
        //         $id => 1584948803298490,
        //         $amount => "2.737",
        //         $price => "0.021209",
        //         direction => "sell",
        //         ts => 1584948803
        //     }
        //
        // fetchOrder trades
        //
        //     {
        //         "$id":null,
        //         "$ctime":"2020-03-23 20:07:17",
        //         "$price":"123.9",
        //         "number":"0.010688626311541565",
        //         "total_price":"1.324320799999999903",
        //         "$fee":"0.000021377252623083"
        //     }
        //
        $id = $this->safe_string($trade, 'id');
        $ctime = $this->parse8601($this->safe_string($trade, 'ctime'));
        $timestamp = $this->safe_timestamp($trade, 'ts', $ctime) - 28800000; // 8 hours, adjust to UTC;
        $symbol = null;
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        $side = $this->safe_string($trade, 'direction');
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float_2($trade, 'amount', 'number');
        $cost = $this->safe_float($trade, 'total_price');
        if (($cost === null) && ($price !== null) && ($amount !== null)) {
            $cost = $price * $amount;
        }
        $feeCost = $this->safe_float($trade, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyCode = null;
            if ($market !== null) {
                if ($side === 'buy') {
                    $feeCurrencyCode = $market['base'];
                } else if ($side === 'sell') {
                    $feeCurrencyCode = $market['quote'];
                }
            }
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        return array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => null,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = yield $this->publicGetMarketTrade (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => "success",
        //         $result => {
        //             $symbol => "ETH-BTC",
        //             ts => 1584948805439,
        //             $data => array(
        //                 array(
        //                     id => 1584948803300883,
        //                     amount => "0.583",
        //                     price => "0.021209",
        //                     direction => "buy",
        //                     ts => 1584948803
        //                 ),
        //                 array(
        //                     id => 1584948803298490,
        //                     amount => "2.737",
        //                     price => "0.021209",
        //                     direction => "sell",
        //                     ts => 1584948803
        //                 ),
        //             )
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function parse_order_status($status) {
        $statuses = array(
            '1' => 'open',
            '2' => 'open', // partially filled
            '3' => 'closed',
            '4' => 'canceled', // canceling
            '5' => 'canceled', // partially canceled
            '6' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder
        //
        //     array( order_sn => 'BM7442641584965237751ZMAKJ5' )
        //
        // fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "order_sn" => "BL74426415849672087836G48N1",
        //         "symbol" => "ETH-USDT",
        //         "ctime" => "2020-03-23 20:40:08",
        //         "$type" => 2,
        //         "$side" => "buy",
        //         "$price" => "90", // null for $market orders
        //         "$number" => "0.1",
        //         "total_price" => "9.0", // 0 for $market orders
        //         "deal_number" => null,
        //         "deal_price" => null,
        //         "$status" => 1,
        //     }
        //
        // fetchOrder
        //
        //     {
        //         order_sn => 'BM7442641584965237751ZMAKJ5',
        //         symbol => 'ETH-USDT',
        //         ctime => '2020-03-23 20:07:17',
        //         $type => 1,
        //         $side => 'buy',
        //         $price => '0',
        //         $number => '10',
        //         total_price => '0',
        //         deal_number => '0.080718626311541565',
        //         deal_price => '123.890000000000000000',
        //         $status => 3,
        //         // the $trades field is injected by fetchOrder
        //         $trades => array(
        //             {
        //                 $id => null,
        //                 ctime => '2020-03-23 20:07:17',
        //                 $price => '123.9',
        //                 $number => '0.010688626311541565',
        //                 total_price => '1.324320799999999903',
        //                 $fee => '0.000021377252623083'
        //             }
        //         )
        //     }
        //
        $id = $this->safe_string($order, 'order_sn');
        $orderStatus = $this->safe_string($order, 'status');
        $status = $this->parse_order_status($orderStatus);
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market, '-');
        $timestamp = $this->parse8601($this->safe_string($order, 'ctime'));
        if ($timestamp !== null) {
            $timestamp -= 28800000; // 8 hours, adjust to UTC
        }
        $orderType = $this->safe_string($order, 'type');
        $type = ($orderType === '2') ? 'limit' : 'market';
        $side = $this->safe_string($order, 'side');
        // $amount = $this->safe_float($order, 'number');
        // $price = $this->safe_float($order, 'price');
        $cost = null;
        $price = null;
        $amount = null;
        $average = null;
        $number = $this->safe_float($order, 'number');
        $totalPrice = $this->safe_float($order, 'total_price');
        if ($type === 'limit') {
            $amount = $number;
            $price = $this->safe_float($order, 'price');
        } else {
            $average = $this->safe_float($order, 'deal_price');
            if ($side === 'buy') {
                $amount = $this->safe_float($order, 'deal_number');
            } else {
                $amount = $number;
            }
        }
        $fee = null;
        $trades = null;
        $filled = null;
        $feeCost = null;
        $remaining = null;
        $lastTradeTimestamp = null;
        // all orders except new orders and canceled orders
        if (($orderStatus !== '1') && ($orderStatus !== '6')) {
            $rawTrades = $this->safe_value($order, 'trades');
            if ($rawTrades !== null) {
                for ($i = 0; $i < count($rawTrades); $i++) {
                    $rawTrades[$i]['direction'] = $side;
                }
                $trades = $this->parse_trades($rawTrades, $market, null, null, array(
                    'symbol' => $market['symbol'],
                    'order' => $id,
                    'side' => $side,
                    'type' => $type,
                ));
                $tradesLength = is_array($trades) ? count($trades) : 0;
                if ($tradesLength > 0) {
                    $firstTrade = $trades[0];
                    $feeCost = $firstTrade['fee']['cost'];
                    $lastTradeTimestamp = $firstTrade['timestamp'];
                    $filled = $firstTrade['amount'];
                    $cost = $firstTrade['cost'];
                    for ($i = 1; $i < count($trades); $i++) {
                        $trade = $trades[$i];
                        $feeCost = $this->sum($feeCost, $trade['fee']['cost']);
                        $filled = $this->sum($filled, $trade['amount']);
                        $cost = $this->sum($cost, $trade['cost']);
                        $lastTradeTimestamp = max ($lastTradeTimestamp, $trade['timestamp']);
                    }
                    if ($amount !== null) {
                        $filled = min ($amount, $filled);
                    }
                    if ($filled > 0) {
                        $average = $cost / $filled;
                    }
                }
                if ($feeCost !== null) {
                    $feeCurrencyCode = ($side === 'buy') ? $market['base'] : $market['quote'];
                    $fee = array(
                        'cost' => $feeCost,
                        'currency' => $feeCurrencyCode,
                    );
                }
            }
        } else {
            $filled = 0;
            $cost = 0;
        }
        if ($cost === null) {
            if ($type === 'limit') {
                $cost = $totalPrice;
            } else if ($side === 'buy') {
                $cost = $number;
            }
        }
        if ($filled === null) {
            if (($type === 'limit') && ($orderStatus === '3')) {
                $filled = $amount;
            }
        }
        if ($filled !== null) {
            if ($amount !== null) {
                $remaining = max ($amount - $filled, 0);
            }
        }
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'status' => $status,
            'symbol' => $market['symbol'],
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'cost' => $cost,
            'average' => $average,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'trades' => $trades,
            'fee' => $fee,
        );
    }

    public function fetch_closed_order($id, $symbol = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'order_sn' => $id,
        );
        $response = yield $this->privateGetEntrustDetail (array_merge($request, $params));
        //
        //     {
        //         "errno" => 0,
        //         "errmsg" => "success",
        //         "$result" => {
        //             "$trades" => array(
        //                 array(
        //                     "$id":null,
        //                     "ctime":"2020-03-23 20:07:17",
        //                     "price":"123.9",
        //                     "number":"0.010688626311541565",
        //                     "total_price":"1.324320799999999903",
        //                     "fee":"0.000021377252623083"
        //                 ),
        //             ),
        //             "entrust":{
        //                 "order_sn":"BM7442641584965237751ZMAKJ5",
        //                 "$symbol":"ETH-USDT",
        //                 "ctime":"2020-03-23 20:07:17",
        //                 "type":1,
        //                 "side":"buy",
        //                 "price":"0",
        //                 "number":"10",
        //                 "total_price":"0",
        //                 "deal_number":"0.080718626311541565",
        //                 "deal_price":"123.890000000000000000",
        //                 "status":3
        //             }
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $trades = $this->safe_value($result, 'trades', array());
        $order = $this->safe_value($result, 'entrust', array());
        $order['trades'] = $trades;
        return $this->parse_order($order);
    }

    public function fetch_order_trades($id, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $response = yield $this->fetch_closed_order($id, $symbol, $params);
        return $this->safe_value($response, 'trades', array());
    }

    public function fetch_orders_with_method($method, $symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'from' => 'BM7442641584965237751ZMAKJ5', // query start order_sn
            'direct' => 'prev', // next
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 20, max 100
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        //     {
        //         "errno" => 0,
        //         "errmsg" => "success",
        //         "$result" => array(
        //             {
        //                 "order_sn" => "BL74426415849672087836G48N1",
        //                 "$symbol" => "ETH-USDT",
        //                 "ctime" => "2020-03-23 20:40:08",
        //                 "type" => 2,
        //                 "side" => "buy",
        //                 "price" => "90",
        //                 "number" => "0.1",
        //                 "total_price" => "9.0",
        //                 "deal_number" => null,
        //                 "deal_price" => null,
        //                 "status" => 1,
        //             }
        //         )
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_orders($result, $market, $since, $limit);
    }

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

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

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $orderType = $side . '-' . $type;
        $request = array(
            'symbol' => $market['id'],
            'type' => $orderType,
        );
        if ($type === 'limit') {
            $request['amount'] = $this->amount_to_precision($symbol, $amount);
            $request['price'] = $this->price_to_precision($symbol, $price);
        } else if ($type === 'market') {
            // for $market buy it requires the $amount of quote currency to spend
            if ($side === 'buy') {
                $createMarketBuyOrderRequiresPrice = $this->safe_value($this->options, 'createMarketBuyOrderRequiresPrice', true);
                $cost = $amount;
                if ($createMarketBuyOrderRequiresPrice) {
                    if ($price !== null) {
                        $cost = $amount * $price;
                    } else {
                        throw new InvalidOrder($this->id . " createOrder() requires the $price argument with $market buy orders to calculate total $order $cost ($amount to spend), where $cost = $amount * $price-> Supply a $price argument to createOrder() call if you want the $cost to be calculated for you from $price and $amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total $cost value in the 'amount' argument");
                    }
                }
                $precision = $market['precision']['price'];
                $request['amount'] = $this->decimal_to_precision($cost, TRUNCATE, $precision, $this->precisionMode);
            } else {
                $request['amount'] = $this->amount_to_precision($symbol, $amount);
            }
        }
        $response = yield $this->privatePostEntrustAdd (array_merge($request, $params));
        //
        //     {
        //         errno => 0,
        //         errmsg => 'success',
        //         $result => array( order_sn => 'BM7442641584965237751ZMAKJ5' )
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $order = $this->parse_order($result, $market);
        $timestamp = $this->milliseconds();
        return array_merge($order, array(
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'amount' => $amount,
            'price' => $price,
            'type' => $type,
            'side' => $side,
        ));
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'order_ids' => $id,
        );
        $response = yield $this->privatePostEntrustCancel (array_merge($request, $params));
        //
        //     {
        //         "errno" => 0,
        //         "errmsg" => "$success",
        //         "$result" => {
        //             "$success" => array( "avl12121", "bl3123123" ),
        //             "failed" => array( "sd24564", "sdf6564564" )
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $success = $this->safe_value($result, 'success', array());
        if (!$this->in_array($id, $success)) {
            throw new OrderNotFound($this->id . ' order $id ' . $id . ' not found in successfully canceled orders => ' . $this->json($response));
        }
        $timestamp = null;
        return array(
            'info' => $response,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'status' => 'canceled',
            'symbol' => $symbol,
            'type' => null,
            'side' => null,
            'price' => null,
            'cost' => null,
            'average' => null,
            'amount' => null,
            'filled' => null,
            'remaining' => null,
            'trades' => null,
            'fee' => null,
            'clientOrderId' => null,
        );
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = yield $this->privatePostEntrustCancel (array_merge($request, $params));
        //
        //     {
        //         "errno" => 0,
        //         "errmsg" => "success",
        //         "result" => {
        //             "success" => array( "avl12121", "bl3123123" ),
        //             "failed" => array( "sd24564", "sdf6564564" )
        //         }
        //     }
        //
        return $response;
    }

    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] . '/' . $path;
        $keys = is_array($params) ? array_keys($params) : array();
        $keysLength = is_array($keys) ? count($keys) : 0;
        if ($api === 'public') {
            if ($keysLength > 0) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $nonce = (string) $this->nonce();
            $uuid = $this->uuid();
            $randomString = mb_substr($uuid, 0, 5 - 0);
            $nonceString = $nonce . '_' . $randomString;
            $auth = array();
            $auth[$this->apiKey] = $this->apiKey;
            $auth[$this->secret] = $this->secret;
            $auth[$nonceString] = $nonceString;
            for ($i = 0; $i < $keysLength; $i++) {
                $key = $keys[$i];
                $auth[$key] = $key . '=' . $params[$key];
            }
            $keysorted = $this->keysort($auth);
            $stringToSign = '';
            $keys = is_array($keysorted) ? array_keys($keysorted) : array();
            for ($i = 0; $i < count($keys); $i++) {
                $key = $keys[$i];
                $stringToSign .= $keysorted[$key];
            }
            $signature = $this->hash($this->encode($stringToSign), 'sha1');
            $headers = array(
                'Nonce' => $nonceString,
                'Token' => $this->apiKey,
                'Signature' => $signature,
            );
            if ($method === 'POST') {
                $headers['Content-Type'] = 'application/x-www-form-urlencoded';
                if ($keysLength > 0) {
                    $body = $this->urlencode($params);
                }
            } else {
                if ($keysLength > 0) {
                    $url .= '?' . $this->urlencode($params);
                }
            }
        }
        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("errno":20501,"errmsg":"base symbol $error")
        //
        $error = $this->safe_string($response, 'errno');
        if (($error !== null) && ($error !== '0')) {
            $message = $this->safe_string($response, 'errmsg');
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $error, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
    }
}
