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

class bw extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'bw',
            'name' => 'BW',
            'countries' => array( 'CN' ),
            'rateLimit' => 1500,
            'version' => 'v1',
            'has' => array(
                'cancelAllOrders' => false,
                'cancelOrder' => true,
                'cancelOrders' => false,
                'CORS' => false,
                'createDepositAddress' => false,
                'createLimitOrder' => true,
                'createMarketOrder' => false,
                'createOrder' => true,
                'deposit' => false,
                'editOrder' => false,
                'fetchBalance' => true,
                'fetchBidsAsks' => false,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchFundingFees' => false,
                'fetchL2OrderBook' => false,
                'fetchLedger' => false,
                'fetchMarkets' => true,
                'fetchMyTrades' => false,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => false,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTradingLimits' => false,
                'fetchTransactions' => false,
                'fetchWithdrawals' => true,
                'privateAPI' => false,
                'publicAPI' => false,
                'withdraw' => false,
            ),
            'timeframes' => array(
                '1m' => '1M',
                '5m' => '5M',
                '15m' => '15M',
                '30m' => '30M',
                '1h' => '1H',
                '1w' => '1W',
            ),
            'hostname' => 'bw.com', // set to 'bw.io' for China mainland
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/69436317-31128c80-0d52-11ea-91d1-eb7bb5818812.jpg',
                'api' => 'https://www.{hostname}',
                'www' => 'https://www.bw.com',
                'doc' => 'https://github.com/bw-exchange/api_docs_en/wiki',
                'fees' => 'https://www.bw.com/feesRate',
                'referral' => 'https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0',
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'taker' => 0.2 / 100,
                    'maker' => 0.2 / 100,
                ),
                'funding' => array(
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    '999' => '\\ccxt\\AuthenticationError',
                    '1000' => '\\ccxt\\ExchangeNotAvailable', // array("datas":null,"resMsg":array("message":"getKlines error:data not exitsts\uff0cplease wait ,dataType=4002_KLINE_1M","method":null,"code":"1000"))
                    '2012' => '\\ccxt\\OrderNotFound', // array("datas":null,"resMsg":array("message":"entrust not exists or on dealing with system","method":null,"code":"2012"))
                    '5017' => '\\ccxt\\BadSymbol', // array("datas":null,"resMsg":array("message":"market not exist","method":null,"code":"5017"))
                    '10001' => '\\ccxt\\RateLimitExceeded', // array("resMsg":array("code":"10001","message":"API frequency limit"))
                ),
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'api/data/v1/klines',
                        'api/data/v1/ticker',
                        'api/data/v1/tickers',
                        'api/data/v1/trades',
                        'api/data/v1/entrusts',
                        'exchange/config/controller/website/marketcontroller/getByWebId',
                        'exchange/config/controller/website/currencycontroller/getCurrencyList',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'exchange/entrust/controller/website/EntrustController/getEntrustById',
                        'exchange/entrust/controller/website/EntrustController/getUserEntrustRecordFromCacheWithPage',
                        'exchange/entrust/controller/website/EntrustController/getUserEntrustList',
                        'exchange/fund/controller/website/fundwebsitecontroller/getwithdrawaddress',
                        'exchange/fund/controller/website/fundwebsitecontroller/getpayoutcoinrecord',
                        'exchange/entrust/controller/website/EntrustController/getUserEntrustList',
                        // the docs say that the following URLs are HTTP POST
                        // in the docs header and HTTP GET in the docs body
                        // the docs contradict themselves, a typo most likely
                        // the actual HTTP method is POST for this endpoint
                        // 'exchange/fund/controller/website/fundcontroller/getPayinAddress',
                        // 'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord',
                    ),
                    'post' => array(
                        'exchange/fund/controller/website/fundcontroller/getPayinAddress', // see the comment above
                        'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord', // see the comment above
                        'exchange/fund/controller/website/fundcontroller/findbypage',
                        'exchange/entrust/controller/website/EntrustController/addEntrust',
                        'exchange/entrust/controller/website/EntrustController/cancelEntrust',
                    ),
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $response = yield $this->publicGetExchangeConfigControllerWebsiteMarketcontrollerGetByWebId ($params);
        //
        //     {
        //         "datas" => array(
        //             {
        //                 "orderNum":null,
        //                 "leverEnable":true,
        //                 "leverMultiple":10,
        //                 "marketId":"291",
        //                 "webId":"102",
        //                 "serverId":"entrust_bw_23",
        //                 "$name":"eos_usdt",
        //                 "leverType":"2",
        //                 "buyerCurrencyId":"11",
        //                 "sellerCurrencyId":"7",
        //                 "amountDecimal":4,
        //                 "priceDecimal":3,
        //                 "minAmount":"0.0100000000",
        //                 "$state":1,
        //                 "openTime":1572537600000,
        //                 "defaultFee":"0.00200000",
        //                 "createUid":null,
        //                 "createTime":0,
        //                 "modifyUid":null,
        //                 "modifyTime":1574160113735,
        //                 "combineMarketId":"",
        //                 "isCombine":0,
        //                 "isMining":0
        //             }
        //         ),
        //         "resMsg" => array( "message":"success !", "method":null, "code":"1" )
        //     }
        //
        $markets = $this->safe_value($response, 'datas', array());
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $id = $this->safe_string($market, 'marketId');
            $numericId = intval($id);
            $name = $this->safe_string_upper($market, 'name');
            list($base, $quote) = explode('_', $name);
            $base = $this->safe_currency_code($base);
            $quote = $this->safe_currency_code($quote);
            $baseId = $this->safe_string($market, 'sellerCurrencyId');
            $quoteId = $this->safe_string($market, 'buyerCurrencyId');
            $baseNumericId = intval($baseId);
            $quoteNumericId = intval($quoteId);
            $symbol = $base . '/' . $quote;
            $state = $this->safe_integer($market, 'state');
            $active = ($state === 1);
            $fee = $this->safe_float($market, 'defaultFee');
            $result[] = array(
                'id' => $id,
                'active' => $active,
                'numericId' => $numericId,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'baseNumericId' => $baseNumericId,
                'quoteNumericId' => $quoteNumericId,
                'maker' => $fee,
                'taker' => $fee,
                'info' => $market,
                'precision' => array(
                    'amount' => $this->safe_integer($market, 'amountDecimal'),
                    'price' => $this->safe_integer($market, 'priceDecimal'),
                ),
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($market, 'minAmount'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => 0,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => 0,
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = yield $this->publicGetExchangeConfigControllerWebsiteCurrencycontrollerGetCurrencyList ($params);
        //
        //     {
        //         "datas":array(
        //             array(
        //                 "currencyId":"456",
        //                 "name":"pan",
        //                 "alias":"pan",
        //                 "logo":"pan.svg",
        //                 "description":"pan",
        //                 "descriptionEnglish":"pan",
        //                 "defaultDecimal":2,
        //                 "createUid":null,
        //                 "createTime":1574068133762,
        //                 "modifyUid":null,
        //                 "modifyTime":0,
        //                 "$state":1,
        //                 "mark":"pan",
        //                 "totalNumber":"0",
        //                 "publishNumber":"0",
        //                 "marketValue":"0",
        //                 "isLegalCoin":0,
        //                 "needBlockUrl":1,
        //                 "blockChainUrl":"https://etherscan.io/tx/",
        //                 "tradeSearchUrl":null,
        //                 "tokenCoinsId":0,
        //                 "isMining":"0",
        //                 "arithmetic":null,
        //                 "founder":"bw_nxwal",
        //                 "teamAddress":null,
        //                 "remark":null,
        //                 "tokenName":"ethw2",
        //                 "isMemo":0,
        //                 "websiteCurrencyId":"7rhqoHLohkG",
        //                 "drawFlag":0,
        //                 "rechargeFlag":1,
        //                 "drawFee":"0.03000000",
        //                 "onceDrawLimit":100,
        //                 "dailyDrawLimit":500,
        //                 "timesFreetrial":"0",
        //                 "hourFreetrial":"0",
        //                 "dayFreetrial":"0",
        //                 "minFee":"0",
        //                 "inConfigTimes":7,
        //                 "outConfigTimes":7,
        //                 "minCash":"0.06000000",
        //                 "limitAmount":"0",
        //                 "zbExist":false,
        //                 "zone":1
        //             ),
        //         ),
        //         "resMsg" => array( "message":"success !", "method":null, "$code":"1" )
        //     }
        //
        $currencies = $this->safe_value($response, 'datas', array());
        $result = array();
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $id = $this->safe_string($currency, 'currencyId');
            $code = $this->safe_currency_code($this->safe_string_upper($currency, 'name'));
            $state = $this->safe_integer($currency, 'state');
            $active = $state === 1;
            $result[$code] = array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'name' => $code,
                'active' => $active,
                'fee' => $this->safe_float($currency, 'drawFee'),
                'precision' => null,
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($currency, 'limitAmount', 0),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => $this->safe_float($currency, 'onceDrawLimit'),
                    ),
                ),
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     [
        //         "281",            // $market id
        //         "9754.4",         // last
        //         "9968.8",         // high
        //         "9631.5",         // low
        //         "47865.6432",     // base volume
        //         "-2.28",          // change
        //         // closing price for last 6 hours
        //         "[[1, 9750.1], [2, 9737.1], [3, 9727.5], [4, 9722], [5, 9722.1], [6, 9754.4]]",
        //         "9752.12",        // $bid
        //         "9756.69",        // $ask
        //         "469849357.2364"  // quote volume
        //     ]
        //
        $marketId = $this->safe_string($ticker, 0);
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->milliseconds();
        $close = floatval($this->safe_value($ticker, 1));
        $bid = $this->safe_value($ticker, 'bid', array());
        $ask = $this->safe_value($ticker, 'ask', array());
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => floatval($this->safe_value($ticker, 2)),
            'low' => floatval($this->safe_value($ticker, 3)),
            'bid' => floatval($this->safe_value($ticker, 7)),
            'bidVolume' => $this->safe_float($bid, 'quantity'),
            'ask' => floatval($this->safe_value($ticker, 8)),
            'askVolume' => $this->safe_float($ask, 'quantity'),
            'vwap' => null,
            'open' => null,
            'close' => $close,
            'last' => $close,
            'previousClose' => null,
            'change' => floatval($this->safe_value($ticker, 5)),
            'percentage' => null,
            'average' => null,
            'baseVolume' => floatval($this->safe_value($ticker, 4)),
            'quoteVolume' => floatval($this->safe_value($ticker, 9)),
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
        );
        $response = yield $this->publicGetApiDataV1Ticker (array_merge($request, $params));
        //
        //     {
        //         "datas" => [
        //             "281",
        //             "7601.99",
        //             "8126.5",
        //             "7474.68",
        //             "47004.8708",
        //             "-6.18",
        //             "[[1, 7800.34], [2, 7626.41], [3, 7609.97], [4, 7569.04], [5, 7577.93], [6, 7601.99]]",
        //             "7600.24",
        //             "7603.69",
        //             "371968300.0119",
        //         ],
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" )
        //     }
        //
        $ticker = $this->safe_value($response, 'datas', array());
        return $this->parse_ticker($ticker, $market);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        yield $this->load_markets();
        $response = yield $this->publicGetApiDataV1Tickers ($params);
        //
        //     {
        //         "$datas" => [
        //             [
        //                 "4051",
        //                 "0.00194",
        //                 "0.00863",
        //                 "0.0012",
        //                 "1519020",
        //                 "-38.22",
        //                 "[[1, 0.0023], [2, 0.00198], [3, 0.00199], [4, 0.00195], [5, 0.00199], [6, 0.00194]]",
        //                 "0.00123",
        //                 "0.0045",
        //                 "4466.8104",
        //             ],
        //         ],
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" ),
        //     }
        //
        $datas = $this->safe_value($response, 'datas', array());
        $result = array();
        for ($i = 0; $i < count($datas); $i++) {
            $ticker = $this->parse_ticker($datas[$i]);
            $symbol = $ticker['symbol'];
            if (($symbols === null) || $this->in_array($symbol, $symbols)) {
                $result[$symbol] = $ticker;
            }
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
        );
        if ($limit !== null) {
            $request['dataSize'] = $limit;
        }
        $response = yield $this->publicGetApiDataV1Entrusts (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "asks" => array(
        //                 array( "9740.43", "0.0083" ),
        //             ),
        //             "bids" => array(
        //                 array( "9734.33", "0.0133" ),
        //             ),
        //             "$timestamp" => "1569303520",
        //         ),
        //         "resMsg" => array(
        //             "message" => "success !",
        //             "method" => null,
        //             "code" => "1",
        //         ),
        //     }
        //
        $orderbook = $this->safe_value($response, 'datas', array());
        $timestamp = $this->safe_timestamp($orderbook, 'timestamp');
        return $this->parse_order_book($orderbook, $timestamp);
    }

    public function parse_trade($trade, $market = null) {
        //
        // fetchTrades (public)
        //
        //     array(
        //         "T",          // $trade
        //         "281",        // $market id
        //         "1569303302", // $timestamp
        //         "BTC_USDT",   // $market name
        //         "ask",        // $side
        //         "9745.08",    // $price
        //         "0.0026"      // $amount
        //     )
        //
        // fetchMyTrades (private)
        //
        //     ...
        //
        $timestamp = $this->safe_timestamp($trade, 2);
        $price = $this->safe_float($trade, 5);
        $amount = $this->safe_float($trade, 6);
        $marketId = $this->safe_string($trade, 1);
        $symbol = null;
        if ($marketId !== null) {
            if (is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id)) {
                $market = $this->markets_by_id[$marketId];
            } else {
                $marketName = $this->safe_string($trade, 3);
                list($baseId, $quoteId) = explode('_', $marketName);
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $symbol = $base . '/' . $quote;
            }
        }
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        $cost = null;
        if ($amount !== null) {
            if ($price !== null) {
                $cost = $this->cost_to_precision($symbol, $price * $amount);
            }
        }
        $sideString = $this->safe_string($trade, 4);
        $side = ($sideString === 'ask') ? 'sell' : 'buy';
        return array(
            'id' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => null,
            'type' => 'limit',
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => floatval($cost),
            'fee' => null,
            'info' => $trade,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
        );
        if ($limit !== null) {
            $request['dataSize'] = $limit; // max 20
        }
        $response = yield $this->publicGetApiDataV1Trades (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             array(
        //                 "T",          // trade
        //                 "281",        // $market id
        //                 "1569303302", // timestamp
        //                 "BTC_USDT",   // $market name
        //                 "ask",        // side
        //                 "9745.08",    // price
        //                 "0.0026"      // amount
        //             ),
        //         ),
        //         "resMsg" => array( "code" => "1", "method" => null, "message" => "success !" ),
        //     }
        //
        $trades = $this->safe_value($response, 'datas', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     array(
        //         "K",
        //         "305",
        //         "eth_btc",
        //         "1591511280",
        //         "0.02504",
        //         "0.02504",
        //         "0.02504",
        //         "0.02504",
        //         "0.0123",
        //         "0",
        //         "285740.17",
        //         "1M",
        //         "false",
        //         "0.000308"
        //     )
        //
        return array(
            $this->safe_timestamp($ohlcv, 3),
            $this->safe_float($ohlcv, 4),
            $this->safe_float($ohlcv, 5),
            $this->safe_float($ohlcv, 6),
            $this->safe_float($ohlcv, 7),
            $this->safe_float($ohlcv, 8),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
            'type' => $this->timeframes[$timeframe],
            'dataSize' => 500,
        );
        if ($limit !== null) {
            $request['dataSize'] = $limit;
        }
        $response = yield $this->publicGetApiDataV1Klines (array_merge($request, $params));
        //
        //     {
        //         "datas":[
        //             ["K","305","eth_btc","1591511280","0.02504","0.02504","0.02504","0.02504","0.0123","0","285740.17","1M","false","0.000308"],
        //             ["K","305","eth_btc","1591511220","0.02504","0.02504","0.02504","0.02504","0.0006","0","285740.17","1M","false","0.00001502"],
        //             ["K","305","eth_btc","1591511100","0.02505","0.02505","0.02504","0.02504","0.0012","-0.0399","285740.17","1M","false","0.00003005"],
        //         ],
        //         "resMsg":array("code":"1","method":null,"message":"success !")
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        $response = yield $this->privatePostExchangeFundControllerWebsiteFundcontrollerFindbypage ($params);
        //
        //     {
        //         "datas" => array(
        //             "totalRow" => 6,
        //             "pageSize" => 99,
        //             "list" => array(
        //                 array(
        //                     "amount" => "0.000090000000000000", // The current number of tokens available
        //                     "currencyTypeId" => 2,              // Token ID
        //                     "freeze" => "0.009900000000000000", // Current token freezing quantity
        //                 ),
        //             ),
        //             "pageNum" => 1,
        //         ),
        //         "resMsg" => array( "$code" => "1", "message" => "success !" )
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $balances = $this->safe_value($data, 'list', array());
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'currencyTypeId');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'amount');
            $account['used'] = $this->safe_float($balance, 'freeze');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        if ($price === null) {
            throw new ExchangeError($this->id . ' allows limit orders only');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'amount' => $this->amount_to_precision($symbol, $amount),
            'price' => $this->price_to_precision($symbol, $price),
            'type' => ($side === 'buy') ? 1 : 0,
            'rangeType' => 0, // limit order
            'marketId' => $market['id'],
        );
        $response = yield $this->privatePostExchangeEntrustControllerWebsiteEntrustControllerAddEntrust (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "entrustId" => "E6581105708337483776",
        //         ),
        //         "resMsg" => {
        //             "message" => "success !",
        //             "method" => null,
        //             "code" => "1"
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'datas');
        $id = $this->safe_string($data, 'entrustId');
        return array(
            'id' => $id,
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => null,
            'average' => null,
            'filled' => null,
            'remaining' => null,
            'status' => 'open',
            'fee' => null,
            'trades' => null,
            'clientOrderId' => null,
        );
    }

    public function parse_order_status($status) {
        $statuses = array(
            '-3' => 'canceled',
            '-2' => 'canceled',
            '-1' => 'canceled',
            '0' => 'open',
            '1' => 'canceled',
            '2' => 'closed',
            '3' => 'open',
            '4' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // fetchOrder, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "entrustId" => "E6581108027628212224", // Order id
        //         "$price" => "1450",                     // $price
        //         "rangeType" => 0,                      // Commission type 0 => limit $price commission 1 => interval commission
        //         "$amount" => "14.05",                   // Order quantity
        //         "totalMoney" => "20372.50",            // Total $order $amount
        //         "completeAmount" => "0",               // Quantity sold
        //         "completeTotalMoney" => "0",           // Total dealt $amount
        //         "type" => 1,                           // 0 = sell, 1 = buy, -1 = cancel
        //         "entrustType" => 0,                    // 0 = ordinary current $price commission, 1 = lever commission
        //         "$status" => 0,                         //
        //         "$marketId" => "318",                   // The $market id
        //         "createTime" => 1569058424861,         // Create time
        //         "availabelAmount" => "14.05"           // Outstanding quantity, typo in the docs or in the API, availabel vs available
        //     }
        //
        $marketId = $this->safe_string($order, 'marketId');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_integer($order, 'createTime');
        $side = $this->safe_string($order, 'type');
        if ($side === '0') {
            $side = 'sell';
        } else if ($side === '1') {
            $side = 'buy';
        }
        $amount = $this->safe_float($order, 'amount');
        $price = $this->safe_float($order, 'price');
        $filled = $this->safe_float($order, 'completeAmount');
        $remaining = $this->safe_float_2($order, 'availabelAmount', 'availableAmount'); // typo in the docs or in the API, availabel vs available
        $cost = $this->safe_float($order, 'totalMoney');
        if ($filled !== null) {
            if ($amount !== null) {
                if ($remaining === null) {
                    $remaining = $amount - $filled;
                }
            }
            if ($cost === null) {
                if ($price !== null) {
                    $cost = $filled * $cost;
                }
            }
        }
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        return array(
            'info' => $order,
            'id' => $this->safe_string($order, 'entrustId'),
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => 'limit',
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'amount' => $amount,
            'cost' => $cost,
            'average' => null,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        );
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
            'entrustId' => $id,
        );
        $response = yield $this->privateGetExchangeEntrustControllerWebsiteEntrustControllerGetEntrustById (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "entrustId" => "E6581108027628212224", // Order $id
        //             "price" => "1450",                     // price
        //             "rangeType" => 0,                      // Commission type 0 => limit price commission 1 => interval commission
        //             "amount" => "14.05",                   // Order quantity
        //             "totalMoney" => "20372.50",            // Total $order amount
        //             "completeAmount" => "0",               // Quantity sold
        //             "completeTotalMoney" => "0",           // Total dealt amount
        //             "type" => 1,                           // Trade direction, 0 => sell, 1 => buy, -1 => cancel
        //             "entrustType" => 0,                    // Commission type, 0 => ordinary current price commission, 1 => lever commission
        //             "status" => 0,                         // Order status,-3:fund Freeze exception,Order status to be confirmed  -2 => fund freeze failure, $order failure, -1 => insufficient funds, $order failure, 0 => pending $order, 1 => cancelled, 2 => dealt, 3 => partially dealt
        //             "marketId" => "318",                   // The $market $id
        //             "createTime" => 1569058424861,         // Create time
        //             "availabelAmount" => "14.05"           // Outstanding quantity
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" )
        //     }
        //
        $order = $this->safe_value($response, 'datas', array());
        return $this->parse_order($order, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
            'entrustId' => $id,
        );
        $response = yield $this->privatePostExchangeEntrustControllerWebsiteEntrustControllerCancelEntrust (array_merge($request, $params));
        //
        //     {
        //         "datas" => null,
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" )
        //     }
        //
        return array(
            'info' => $response,
            'id' => $id,
        );
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
            // 'pageSize' => $limit, // documented as required, but it works without it
            // 'pageIndex' => 0, // also works without it, most likely a typo in the docs
        );
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default $limit is 20
        }
        $response = yield $this->privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustRecordFromCacheWithPage (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "pageNum" => 1,
        //             "pageSize" => 2,
        //             "totalPage" => 20,
        //             "totalRow" => 40,
        //             "entrustList" => array(
        //                 array(
        //                     "amount" => "14.050000000000000000",        // Order quantity
        //                     "rangeType" => 0,                           // Commission type 0 => $limit price commission 1 => interval commission
        //                     "totalMoney" => "20372.500000000000000000", // Total order amount
        //                     "entrustId" => "E6581108027628212224",      // Order id
        //                     "type" => 1,                                // Trade direction, 0 => sell, 1 => buy, -1 => cancel
        //                     "completeAmount" => "0",                    // Quantity sold
        //                     "marketId" => "318",                        // The $market id
        //                     "createTime" => 1569058424861,              // Create time
        //                     "price" => "1450.000000000",                // price
        //                     "completeTotalMoney" => "0",                // Quantity sold
        //                     "entrustType" => 0,                         // Commission type, 0 => ordinary current price commission, 1 => lever commission
        //                     "status" => 0                               // Order status,-3:fund Freeze exception,Order status to be confirmed  -2 => fund freeze failure, order failure, -1 => insufficient funds, order failure, 0 => pending order, 1 => cancelled, 2 => dealt, 3 => partially dealt
        //                 ),
        //             ),
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" ),
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $orders = $this->safe_value($data, 'entrustList', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchClosedOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
        );
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default $limit is 20
        }
        if ($since !== null) {
            $request['startDateTime'] = $since;
        }
        $response = yield $this->privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (array_merge($request, $params));
        $data = $this->safe_value($response, 'datas', array());
        $orders = $this->safe_value($data, 'entrustList', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'marketId' => $market['id'],
            // 'pageSize' => $limit, // documented as required, but it works without it
            // 'pageIndex' => 0, // also works without it, most likely a typo in the docs
            // 'type' => 0, // 0 = sell, 1 = buy, -1 = cancel
            // 'status' => -1, // -1 = insufficient funds, failed $orders, 0 = pending $orders, 1 = canceled, 2 = closed, 3 = partial
            // 'startDateTime' => $since,
            // 'endDateTime' => $this->milliseconds(),
        );
        if ($since !== null) {
            $request['startDateTime'] = $since;
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default $limit is 20
        }
        $response = yield $this->privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "pageNum" => 1,
        //             "pageSize" => 2,
        //             "totalPage" => 20,
        //             "totalRow" => 40,
        //             "entrustList" => array(
        //                 array(
        //                     "amount" => "14.050000000000000000",        // Order quantity
        //                     "rangeType" => 0,                           // Commission type 0 => $limit price commission 1 => interval commission
        //                     "totalMoney" => "20372.500000000000000000", // Total order amount
        //                     "entrustId" => "E6581108027628212224",      // Order id
        //                     "type" => 1,                                // Trade direction, 0 => sell, 1 => buy, -1 => cancel
        //                     "completeAmount" => "0",                    // Quantity sold
        //                     "marketId" => "318",                        // The $market id
        //                     "createTime" => 1569058424861,              // Create time
        //                     "price" => "1450.000000000",                // price
        //                     "completeTotalMoney" => "0",                // Quantity sold
        //                     "entrustType" => 0,                         // Commission type, 0 => ordinary current price commission, 1 => lever commission
        //                     "status" => 0                               // Order status,-3:fund Freeze exception,Order status to be confirmed  -2 => fund freeze failure, order failure, -1 => insufficient funds, order failure, 0 => pending order, 1 => cancelled, 2 => dealt, 3 => partially dealt
        //                 ),
        //             ),
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "code" => "1" ),
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $orders = $this->safe_value($data, 'entrustList', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->implode_params($this->urls['api'], array( 'hostname' => $this->hostname )) . '/' . $path;
        if ($method === 'GET') {
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $body = $this->json($params);
        }
        if ($api === 'private') {
            $ms = (string) $this->milliseconds();
            $content = '';
            if ($method === 'GET') {
                $sortedParams = $this->keysort($params);
                $keys = is_array($sortedParams) ? array_keys($sortedParams) : array();
                for ($i = 0; $i < count($keys); $i++) {
                    $key = $keys[$i];
                    $content .= $key . (string) $sortedParams[$key];
                }
            } else {
                $content = $body;
            }
            $signature = $this->apiKey . $ms . $content . $this->secret;
            $hash = $this->hash($this->encode($signature), 'md5');
            if (!$headers) {
                $headers = array();
            }
            $headers['Apiid'] = $this->apiKey;
            $headers['Timestamp'] = $ms;
            $headers['Sign'] = $hash;
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currencyTypeName' => $currency['name'],
        );
        $response = yield $this->privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinAddress (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "isMemo" => true,                                // 是否为memo 格式，false：否，true ：是
        //             "$address" => "bweosdeposit_787928102918558272",  // 充币地址
        //             "memo" => "787928102918558272",                  // 币种memo
        //             "account" => "bweosdeposit"                      // 币种账户
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "$code" => "1" )
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $address = $this->safe_string($data, 'address');
        $tag = $this->safe_string($data, 'memo');
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $this->check_address($address),
            'tag' => $tag,
            'info' => $response,
        );
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            '-1' => 'canceled', // or auditing failed
            '0' => 'pending',
            '1' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchDeposits
        //
        //     {
        //         "depositId" => "D6574268549744189441",                  // Deposit ID
        //         "$amount" => "54.753589700000000000",                    // Deposit $amount
        //         "txId" => "INNER_SYSTEM_TRANSFER_1198941",              // Trading ID
        //         "confirmTimes" => 0,                                    // Confirmation number
        //         "depositAddress" => "bweosdeposit_787928102918558272",  // Deposit $address
        //         "createTime" => "2019-09-02 20:36:08.0",                // Deposit time
        //         "$status" => 1,                                          // Deposit $status, 0 => not received, 1 => received
        //         "currencyTypeId" => 7,                                  // Token ID
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "withdrawalId" => "W6527498439872634880",      // Withdrawal ID
        //         "fees" => "0.500000000000000000",              // Withdrawal $fee
        //         "withdrawalAddress" => "okbtothemoon_941657",  // Withdrawal $address
        //         "$currencyId" => "7",                           // Token ID
        //         "$amount" => "10.000000000000000000",           // Withdrawal $amount
        //         "state" => 1,                                  // Status, 1 => normal, -1 => delete
        //         "verifyStatus" => 1,                           // Audit $status, 0 => to be audited, 1 => auditing passed, -1 => auditing failed
        //         "createTime" => 1556276903656,                 // WIthdrawal time
        //         "actuallyAmount" => "9.500000000000000000",    // Actual $amount received
        //     }
        //
        $id = $this->safe_string($transaction, 'depositId', 'withdrawalId');
        $address = $this->safe_string_2($transaction, 'depositAddress', 'withdrawalAddress');
        $currencyId = $this->safe_string_2($transaction, 'currencyId', 'currencyTypeId');
        $code = null;
        if (is_array($this->currencies_by_id) && array_key_exists($currencyId, $this->currencies_by_id)) {
            $currency = $this->currencies_by_id[$currencyId];
        }
        if (($code === null) && ($currency !== null)) {
            $code = $currency['code'];
        }
        $type = (is_array($transaction) && array_key_exists('depositId', $transaction)) ? 'deposit' : 'withdrawal';
        $amount = $this->safe_float_2($transaction, 'actuallyAmount', 'amount');
        $status = $this->parse_transaction_status($this->safe_string_2($transaction, 'verifyStatus', 'state'));
        $timestamp = $this->safe_integer($transaction, 'createTime');
        $txid = $this->safe_string($transaction, 'txId');
        $fee = null;
        $feeCost = $this->safe_float($transaction, 'fees');
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => null,
            'address' => $address,
            'addressTo' => null,
            'tagFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'fee' => $fee,
        );
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        if ($code === null) {
            throw new ArgumentsRequired($this->id . ' fetchDeposits() requires a $currency $code argument');
        }
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currencyTypeName' => $currency['name'],
            // 'pageSize' => $limit, // documented as required, but it works without it
            // 'pageNum' => 0, // also works without it, most likely a typo in the docs
            // 'sort' => 1, // 1 = asc, 0 = desc
        );
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default 50
        }
        $response = yield $this->privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinCoinRecord (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "totalRow":2,
        //             "totalPage" => 1,
        //             "pageSize" => 2,
        //             "pageNum" => 1,
        //             "list" => array(
        //                 array(
        //                     "depositId" => "D6574268549744189441",                  // Deposit ID
        //                     "amount" => "54.753589700000000000",                    // Deposit amount
        //                     "txId" => "INNER_SYSTEM_TRANSFER_1198941",              // Trading ID
        //                     "confirmTimes" => 0,                                    // Confirmation number
        //                     "depositAddress" => "bweosdeposit_787928102918558272",  // Deposit address
        //                     "createTime" => "2019-09-02 20:36:08.0",                // Deposit time
        //                     "status" => 1,                                          // Deposit status, 0 => not received, 1 => received
        //                     "currencyTypeId" => 7,                                  // Token ID
        //                 ),
        //             )
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "$code" => "1" ),
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $deposits = $this->safe_value($data, 'list', array());
        return $this->parse_transactions($deposits, $code, $since, $limit);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        if ($code === null) {
            throw new ArgumentsRequired($this->id . ' fetchWithdrawals() requires a $currency $code argument');
        }
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currencyId' => $currency['id'],
            // 'pageSize' => $limit, // documented as required, but it works without it
            // 'pageIndex' => 0, // also works without it, most likely a typo in the docs
            // 'tab' => 'all', // all, wait (submitted, not audited), success (auditing passed), fail (auditing failed), cancel (canceled by user)
        );
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default 50
        }
        $response = yield $this->privateGetExchangeFundControllerWebsiteFundwebsitecontrollerGetpayoutcoinrecord (array_merge($request, $params));
        //
        //     {
        //         "datas" => array(
        //             "totalRow" => 1,
        //             "totalPage" => 1,
        //             "pageSize" => 2,
        //             "pageNum" => 1,
        //             "list" => array(
        //                 array(
        //                     "withdrawalId" => "W6527498439872634880",      // Withdrawal ID
        //                     "fees" => "0.500000000000000000",              // Withdrawal fee
        //                     "withdrawalAddress" => "okbtothemoon_941657",  // Withdrawal address
        //                     "currencyId" => "7",                           // Token ID
        //                     "amount" => "10.000000000000000000",           // Withdrawal amount
        //                     "state" => 1,                                  // Status, 1 => normal, -1 => delete
        //                     "verifyStatus" => 1,                           // Audit status, 0 => to be audited, 1 => auditing passed, -1 => auditing failed
        //                     "createTime" => 1556276903656,                 // WIthdrawal time
        //                     "actuallyAmount" => "9.500000000000000000",    // Actual amount received
        //                 ),
        //             ),
        //         ),
        //         "resMsg" => array( "message" => "success !", "method" => null, "$code" => "1" ),
        //     }
        //
        $data = $this->safe_value($response, 'datas', array());
        $withdrawals = $this->safe_value($data, 'list', array());
        return $this->parse_transactions($withdrawals, $code, $since, $limit);
    }

    public function handle_errors($httpCode, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return; // default error handler
        }
        $resMsg = $this->safe_value($response, 'resMsg');
        $errorCode = $this->safe_string($resMsg, 'code');
        if ($errorCode !== '1') {
            $feedback = $this->id . ' ' . $this->json($response);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            throw new ExchangeError($feedback); // unknown error
        }
    }
}
