<?php

namespace ccxt;

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

use Exception; // a common import
use ccxt\abstract\oceanex as Exchange;

class oceanex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'oceanex',
            'name' => 'OceanEx',
            'countries' => array( 'BS' ), // Bahamas
            'version' => 'v1',
            'rateLimit' => 3000,
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/58385970-794e2d80-8001-11e9-889c-0567cd79b78e.jpg',
                'api' => array(
                    'rest' => 'https://api.oceanex.pro',
                ),
                'www' => 'https://www.oceanex.pro.com',
                'doc' => 'https://api.oceanex.pro/doc/v1',
                'referral' => 'https://oceanex.pro/signup?referral=VE24QX',
            ),
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => null, // has but unimplemented
                'future' => null,
                'option' => null,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'createMarketOrder' => true,
                'createOrder' => true,
                'fetchBalance' => true,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => 'emulated',
                'fetchDepositAddresses' => null,
                'fetchDepositAddressesByNetwork' => true,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchMarkets' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => true,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => true,
                'fetchTransactionFees' => null,
            ),
            'timeframes' => array(
                '1m' => '1',
                '5m' => '5',
                '15m' => '15',
                '30m' => '30',
                '1h' => '60',
                '2h' => '120',
                '4h' => '240',
                '6h' => '360',
                '12h' => '720',
                '1d' => '1440',
                '3d' => '4320',
                '1w' => '10080',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'markets',
                        'tickers/{pair}',
                        'tickers_multi',
                        'order_book',
                        'order_book/multi',
                        'fees/trading',
                        'trades',
                        'timestamp',
                    ),
                    'post' => array(
                        'k',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'key',
                        'members/me',
                        'orders',
                        'orders/filter',
                    ),
                    'post' => array(
                        'orders',
                        'orders/multi',
                        'order/delete',
                        'order/delete/multi',
                        'orders/clear',
                        '/withdraws/special/new',
                        '/deposit_address',
                        '/deposit_addresses',
                        '/deposit_history',
                        '/withdraw_history',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.001'),
                    'taker' => $this->parse_number('0.001'),
                ),
            ),
            'commonCurrencies' => array(
                'PLA' => 'Plair',
            ),
            'precisionMode' => TICK_SIZE,
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true, // todo
                        'triggerDirection' => true, // todo
                        'triggerPriceType' => null,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => false,
                            'FOK' => false,
                            'PO' => false,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => null,
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo => max unknown
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 100000, // todo
                        'daysBackCanceled' => 1, // todo
                        'untilDays' => 100000, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 100,
                    ),
                ),
                // todo implement swap
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'exceptions' => array(
                'codes' => array(
                    '-1' => '\\ccxt\\BadRequest',
                    '-2' => '\\ccxt\\BadRequest',
                    '1001' => '\\ccxt\\BadRequest',
                    '1004' => '\\ccxt\\ArgumentsRequired',
                    '1006' => '\\ccxt\\AuthenticationError',
                    '1008' => '\\ccxt\\AuthenticationError',
                    '1010' => '\\ccxt\\AuthenticationError',
                    '1011' => '\\ccxt\\PermissionDenied',
                    '2001' => '\\ccxt\\AuthenticationError',
                    '2002' => '\\ccxt\\InvalidOrder',
                    '2004' => '\\ccxt\\OrderNotFound',
                    '9003' => '\\ccxt\\PermissionDenied',
                ),
                'exact' => array(
                    'market does not have a valid value' => '\\ccxt\\BadRequest',
                    'side does not have a valid value' => '\\ccxt\\BadRequest',
                    'Account::AccountError => Cannot lock funds' => '\\ccxt\\InsufficientFunds',
                    'The account does not exist' => '\\ccxt\\AuthenticationError',
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all $markets for oceanex
         *
         * @see https://api.oceanex.pro/doc/v1/#$markets-post
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $request = array( 'show_details' => true );
        $response = $this->publicGetMarkets ($this->extend($request, $params));
        //
        //    array(
        //        "id" => "xtzusdt",
        //        "name" => "XTZ/USDT",
        //        "ask_precision" => "8",
        //        "bid_precision" => "8",
        //        "enabled" => true,
        //        "price_precision" => "4",
        //        "amount_precision" => "3",
        //        "usd_precision" => "4",
        //        "minimum_trading_amount" => "1.0"
        //    ),
        //
        $markets = $this->safe_value($response, 'data', array());
        return $this->parse_markets($markets);
    }

    public function parse_market(array $market): array {
        $id = $this->safe_value($market, 'id');
        $name = $this->safe_value($market, 'name');
        list($baseId, $quoteId) = explode('/', $name);
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $baseId = strtolower($baseId);
        $quoteId = strtolower($quoteId);
        $symbol = $base . '/' . $quote;
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => null,
            'type' => 'spot',
            'spot' => true,
            'margin' => false,
            'swap' => false,
            'future' => false,
            'option' => false,
            'active' => null,
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->parse_number($this->parse_precision($this->safe_string($market, 'amount_precision'))),
                'price' => $this->parse_number($this->parse_precision($this->safe_string($market, 'price_precision'))),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'minimum_trading_amount'),
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        );
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://api.oceanex.pro/doc/v1/#ticker-post
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        $response = $this->publicGetTickersPair ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "message":"Operation successful",
        //         "data" => {
        //             "at":1559431729,
        //             "ticker" => {
        //                 "buy":"0.0065",
        //                 "sell":"0.00677",
        //                 "low":"0.00677",
        //                 "high":"0.00677",
        //                 "last":"0.00677",
        //                 "vol":"2000.0"
        //             }
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_ticker($data, $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://api.oceanex.pro/doc/v1/#multiple-tickers-post
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the $ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structures~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        if ($symbols === null) {
            $symbols = $this->symbols;
        }
        $marketIds = $this->market_ids($symbols);
        $request = array( 'markets' => $marketIds );
        $response = $this->publicGetTickersMulti ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "message":"Operation successful",
        //         "data" => {
        //             "at":1559431729,
        //             "ticker" => {
        //                 "buy":"0.0065",
        //                 "sell":"0.00677",
        //                 "low":"0.00677",
        //                 "high":"0.00677",
        //                 "last":"0.00677",
        //                 "vol":"2000.0"
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $ticker = $data[$i];
            $marketId = $this->safe_string($ticker, 'market');
            $market = $this->safe_market($marketId);
            $symbol = $market['symbol'];
            $result[$symbol] = $this->parse_ticker($ticker, $market);
        }
        return $this->filter_by_array_tickers($result, 'symbol', $symbols);
    }

    public function parse_ticker($data, ?array $market = null) {
        //
        //         {
        //             "at":1559431729,
        //             "ticker" => {
        //                 "buy":"0.0065",
        //                 "sell":"0.00677",
        //                 "low":"0.00677",
        //                 "high":"0.00677",
        //                 "last":"0.00677",
        //                 "vol":"2000.0"
        //             }
        //         }
        //
        $ticker = $this->safe_value($data, 'ticker', array());
        $timestamp = $this->safe_timestamp($data, 'at');
        $symbol = $this->safe_symbol(null, $market);
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => $this->safe_string($ticker, 'buy'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'sell'),
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $this->safe_string($ticker, 'last'),
            'last' => $this->safe_string($ticker, 'last'),
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'volume'),
            'quoteVolume' => null,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://api.oceanex.pro/doc/v1/#order-book-post
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the order book for
         * @param {int} [$limit] the maximum amount of order book entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetOrderBook ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "message":"Operation successful",
        //         "data" => {
        //             "timestamp":1559433057,
        //             "asks" => [
        //                 ["100.0","20.0"],
        //                 ["4.74","2000.0"],
        //                 ["1.74","4000.0"],
        //             ],
        //             "bids":[
        //                 ["0.0065","5482873.4"],
        //                 ["0.00649","4781956.2"],
        //                 ["0.00648","2876006.8"],
        //             ],
        //         }
        //     }
        //
        $orderbook = $this->safe_value($response, 'data', array());
        $timestamp = $this->safe_timestamp($orderbook, 'timestamp');
        return $this->parse_order_book($orderbook, $symbol, $timestamp);
    }

    public function fetch_order_books(?array $symbols = null, ?int $limit = null, $params = array ()): OrderBooks {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data for multiple markets
         *
         * @see https://api.oceanex.pro/doc/v1/#multiple-order-books-post
         *
         * @param {string[]|null} $symbols list of unified market $symbols, all $symbols fetched if null, default is null
         * @param {int} [$limit] max number of entries per $orderbook to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by market $symbol
         */
        $this->load_markets();
        if ($symbols === null) {
            $symbols = $this->symbols;
        }
        $marketIds = $this->market_ids($symbols);
        $request = array(
            'markets' => $marketIds,
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetOrderBookMulti ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "message":"Operation successful",
        //         "data" => [
        //             array(
        //                 "timestamp":1559433057,
        //                 "market" => "bagvet",
        //                 "asks" => [
        //                     ["100.0","20.0"],
        //                     ["4.74","2000.0"],
        //                     ["1.74","4000.0"],
        //                 ],
        //                 "bids":[
        //                     ["0.0065","5482873.4"],
        //                     ["0.00649","4781956.2"],
        //                     ["0.00648","2876006.8"],
        //                 ],
        //             ),
        //             ...,
        //         ],
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $orderbook = $data[$i];
            $marketId = $this->safe_string($orderbook, 'market');
            $symbol = $this->safe_symbol($marketId);
            $timestamp = $this->safe_timestamp($orderbook, 'timestamp');
            $result[$symbol] = $this->parse_order_book($orderbook, $symbol, $timestamp);
        }
        return $result;
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent trades for a particular $symbol
         *
         * @see https://api.oceanex.pro/doc/v1/#trades-post
         *
         * @param {string} $symbol unified $symbol of the $market to fetch trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = min ($limit, 1000);
        }
        $response = $this->publicGetTrades ($this->extend($request, $params));
        //
        //      {
        //          "code":0,
        //          "message":"Operation successful",
        //          "data" => array(
        //              array(
        //                  "id":220247666,
        //                  "price":"3098.62",
        //                  "volume":"0.00196",
        //                  "funds":"6.0732952",
        //                  "market":"ethusdt",
        //                  "created_at":"2022-04-19T19:03:15Z",
        //                  "created_on":1650394994,
        //                  "side":"bid"
        //              ),
        //          )
        //      }
        //
        $data = $this->safe_list($response, 'data');
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //      {
        //          "id":220247666,
        //          "price":"3098.62",
        //          "volume":"0.00196",
        //          "funds":"6.0732952",
        //          "market":"ethusdt",
        //          "created_at":"2022-04-19T19:03:15Z",
        //          "created_on":1650394995,
        //          "side":"bid"
        //      }
        //
        $side = $this->safe_value($trade, 'side');
        if ($side === 'bid') {
            $side = 'buy';
        } elseif ($side === 'ask') {
            $side = 'sell';
        }
        $marketId = $this->safe_value($trade, 'market');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_timestamp($trade, 'created_on');
        if ($timestamp === null) {
            $timestamp = $this->parse8601($this->safe_string($trade, 'created_at'));
        }
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'volume');
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $this->safe_string($trade, 'id'),
            'order' => null,
            'type' => 'limit',
            'takerOrMaker' => null,
            'side' => $side,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => null,
        ), $market);
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://api.oceanex.pro/doc/v1/#api-server-time-post
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->publicGetTimestamp ($params);
        //
        //     array("code":0,"message":"Operation successful","data":1559433420)
        //
        return $this->safe_timestamp($response, 'data');
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading fees for multiple markets
         *
         * @see https://api.oceanex.pro/doc/v1/#trading-fees-post
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by market symbols
         */
        $response = $this->publicGetFeesTrading ($params);
        $data = $this->safe_value($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $group = $data[$i];
            $maker = $this->safe_value($group, 'ask_fee', array());
            $taker = $this->safe_value($group, 'bid_fee', array());
            $marketId = $this->safe_string($group, 'market');
            $symbol = $this->safe_symbol($marketId);
            $result[$symbol] = array(
                'info' => $group,
                'symbol' => $symbol,
                'maker' => $this->safe_number($maker, 'value'),
                'taker' => $this->safe_number($taker, 'value'),
                'percentage' => true,
            );
        }
        return $result;
    }

    public function fetch_key($params = array ()) {
        $response = $this->privateGetKey ($params);
        return $this->safe_value($response, 'data');
    }

    public function parse_balance($response): array {
        $data = $this->safe_value($response, 'data');
        $balances = $this->safe_value($data, 'accounts', array());
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_value($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_string($balance, 'balance');
            $account['used'] = $this->safe_string($balance, 'locked');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://api.oceanex.pro/doc/v1/#account-info-post
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $response = $this->privateGetMembersMe ($params);
        return $this->parse_balance($response);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://api.oceanex.pro/doc/v1/#new-order-post
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
            'side' => $side,
            'ord_type' => $type,
            'volume' => $this->amount_to_precision($symbol, $amount),
        );
        if ($type === 'limit') {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $response = $this->privatePostOrders ($this->extend($request, $params));
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://api.oceanex.pro/doc/v1/#order-status-get
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $ids = array( $id );
        $request = array( 'ids' => $ids );
        $response = $this->privateGetOrders ($this->extend($request, $params));
        $data = $this->safe_value($response, 'data');
        $dataLength = count($data);
        if ($data === null) {
            throw new OrderNotFound($this->id . ' could not found matching order');
        }
        if (gettype($id) === 'array' && array_keys($id) === array_keys(array_keys($id))) {
            $orders = $this->parse_orders($data, $market);
            return $orders[0];
        }
        if ($dataLength === 0) {
            throw new OrderNotFound($this->id . ' could not found matching order');
        }
        return $this->parse_order($data[0], $market);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open orders
         *
         * @see https://api.oceanex.pro/doc/v1/#order-status-get
         *
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of  open orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $request = array(
            'states' => array( 'wait' ),
        );
        return $this->fetch_orders($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple closed orders made by the user
         *
         * @see https://api.oceanex.pro/doc/v1/#order-status-get
         *
         * @param {string} $symbol unified market $symbol of the market orders were made in
         * @param {int} [$since] the earliest time in ms to fetch orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $request = array(
            'states' => array( 'done', 'cancel' ),
        );
        return $this->fetch_orders($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple $orders made by the user
         *
         * @see https://api.oceanex.pro/doc/v1/#order-$status-with-filters-post
         *
         * @param {string} $symbol unified $market $symbol of the $market $orders were made in
         * @param {int} [$since] the earliest time in ms to fetch $orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $states = $this->safe_value($params, 'states', array( 'wait', 'done', 'cancel' ));
        $query = $this->omit($params, 'states');
        $request = array(
            'market' => $market['id'],
            'states' => $states,
            'need_price' => 'True',
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOrdersFilter ($this->extend($request, $query));
        $data = $this->safe_value($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $orders = $this->safe_value($data[$i], 'orders', array());
            $status = $this->parse_order_status($this->safe_value($data[$i], 'state'));
            $parsedOrders = $this->parse_orders($orders, $market, $since, $limit, array( 'status' => $status ));
            $result = $this->array_concat($result, $parsedOrders);
        }
        return $result;
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        // array(
        //    1559232000,
        //    8889.22,
        //    9028.52,
        //    8889.22,
        //    9028.52
        //    0.3121
        // )
        return array(
            $this->safe_timestamp($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
            $this->safe_number($ohlcv, 5),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://api.oceanex.pro/doc/v1/#k-line-post
         *
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
         * @param {string} $timeframe the length of time each candle represents
         * @param {int} [$since] timestamp in ms of the earliest candle to fetch
         * @param {int} [$limit] the maximum amount of candles to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
            'period' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        if ($since !== null) {
            $request['timestamp'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = min ($limit, 10000);
        }
        $response = $this->publicPostK ($this->extend($request, $params));
        $ohlcvs = $this->safe_list($response, 'data', array());
        return $this->parse_ohlcvs($ohlcvs, $market, $timeframe, $since, $limit);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        //     {
        //         "created_at" => "2019-01-18T00:38:18Z",
        //         "trades_count" => 0,
        //         "remaining_volume" => "0.2",
        //         "price" => "1001.0",
        //         "created_on" => "1547771898",
        //         "side" => "buy",
        //         "volume" => "0.2",
        //         "state" => "wait",
        //         "ord_type" => "limit",
        //         "avg_price" => "0.0",
        //         "executed_volume" => "0.0",
        //         "id" => 473797,
        //         "market" => "veteth"
        //     }
        //
        $status = $this->parse_order_status($this->safe_value($order, 'state'));
        $marketId = $this->safe_string_2($order, 'market', 'market_id');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_timestamp($order, 'created_on');
        if ($timestamp === null) {
            $timestamp = $this->parse8601($this->safe_string($order, 'created_at'));
        }
        $price = $this->safe_string($order, 'price');
        $average = $this->safe_string($order, 'avg_price');
        $amount = $this->safe_string($order, 'volume');
        $remaining = $this->safe_string($order, 'remaining_volume');
        $filled = $this->safe_string($order, 'executed_volume');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($order, 'id'),
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $this->safe_value($order, 'ord_type'),
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $this->safe_value($order, 'side'),
            'price' => $price,
            'triggerPrice' => null,
            'average' => $average,
            'amount' => $amount,
            'remaining' => $remaining,
            'filled' => $filled,
            'status' => $status,
            'cost' => null,
            'trades' => null,
            'fee' => null,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'wait' => 'open',
            'done' => 'closed',
            'cancel' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://api.oceanex.pro/doc/v1/#cancel-order-post
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by oceanex cancelOrder ()
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $response = $this->privatePostOrderDelete ($this->extend(array( 'id' => $id ), $params));
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data);
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple orders
         *
         * @see https://api.oceanex.pro/doc/v1/#cancel-multiple-orders-post
         *
         * @param {string[]} $ids order $ids
         * @param {string} $symbol not used by oceanex cancelOrders ()
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $response = $this->privatePostOrderDeleteMulti ($this->extend(array( 'ids' => $ids ), $params));
        $data = $this->safe_list($response, 'data');
        return $this->parse_orders($data);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders
         *
         * @see https://api.oceanex.pro/doc/v1/#cancel-all-orders-post
         *
         * @param {string} $symbol unified market $symbol, only orders in the market of this $symbol are cancelled when $symbol is not null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $response = $this->privatePostOrdersClear ($params);
        $data = $this->safe_list($response, 'data');
        return $this->parse_orders($data);
    }

    public function fetch_deposit_addresses_by_network(string $code, $params = array ()): array {
        /**
         * fetch the deposit addresses for a $currency associated with this account
         *
         * @see https://api.oceanex.pro/doc/v1/#deposit-addresses-post
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary ~@link https://docs.ccxt.com/#/?id=address-structure address structures~, indexed by the network
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $response = $this->privatePostDepositAddresses ($this->extend($request, $params));
        //
        //    {
        //        $code => '0',
        //        message => 'Operation successful',
        //        $data => {
        //          $data => {
        //            currency_id => 'usdt',
        //            display_name => 'USDT',
        //            num_of_resources => '3',
        //            $resources => array(
        //              array(
        //                chain_name => 'TRC20',
        //                currency_id => 'usdt',
        //                address => 'TPcS7VgKMFmpRrWY82GbJzDeMnemWxEbpg',
        //                memo => '',
        //                deposit_status => 'enabled'
        //              ),
        //              ...
        //            )
        //          }
        //        }
        //    }
        //
        $data = $this->safe_dict($response, 'data', array());
        $data2 = $this->safe_dict($data, 'data', array());
        $resources = $this->safe_list($data2, 'resources', array());
        $result = array();
        for ($i = 0; $i < count($resources); $i++) {
            $resource = $resources[$i];
            $enabled = $this->safe_string($resource, 'deposit_status');
            if ($enabled === 'enabled') {
                $parsedAddress = $this->parse_deposit_address($resource, $currency);
                $result[$parsedAddress['currency']] = $parsedAddress;
            }
        }
        return $result;
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //    {
        //        chain_name => 'TRC20',
        //        currency_id => 'usdt',
        //        $address => 'TPcS7VgKMFmpRrWY82GbJzDeMnemWxEbpg',
        //        memo => '',
        //        deposit_status => 'enabled'
        //    }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $this->check_address($address);
        $currencyId = $this->safe_string($depositAddress, 'currency_id');
        $networkId = $this->safe_string($depositAddress, 'chain_name');
        return array(
            'info' => $depositAddress,
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'network' => $this->network_id_to_code($networkId),
            'address' => $address,
            'tag' => $this->safe_string($depositAddress, 'memo'),
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api']['rest'] . '/' . $this->version . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'public') {
            if ($path === 'tickers_multi' || $path === 'order_book/multi') {
                $request = '?';
                $markets = $this->safe_value($params, 'markets');
                for ($i = 0; $i < count($markets); $i++) {
                    $request .= 'marketsarray()=' . $markets[$i] . '&';
                }
                $limit = $this->safe_value($params, 'limit');
                if ($limit !== null) {
                    $request .= 'limit=' . $limit;
                }
                $url .= $request;
            } elseif ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } elseif ($api === 'private') {
            $this->check_required_credentials();
            $request = array(
                'uid' => $this->apiKey,
                'data' => $query,
            );
            // to set the private key:
            // $fs = require ('fs')
            // exchange.secret = $fs->readFileSync ('oceanex.pem', 'utf8')
            $jwt_token = $this->jwt($request, $this->encode($this->secret), 'sha256', true);
            $url .= '?user_jwt=' . $jwt_token;
        }
        $headers = array( 'Content-Type' => 'application/json' );
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        //
        //     array("code":1011,"message":"This IP 'x.x.x.x' is not allowed","data":array())
        //
        if ($response === null) {
            return null;
        }
        $errorCode = $this->safe_string($response, 'code');
        $message = $this->safe_string($response, 'message');
        if (($errorCode !== null) && ($errorCode !== '0')) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['codes'], $errorCode, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
