<?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\coinsph as Exchange;

class coinsph extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'coinsph',
            'name' => 'Coins.ph',
            'countries' => array( 'PH' ), // Philippines
            'version' => 'v1',
            'rateLimit' => 50, // 1200 per minute
            'certified' => false,
            'pro' => false,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => false,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => false,
                'createOrderWithTakeProfitAndStopLossWs' => false,
                'createPostOnlyOrder' => false,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'deposit' => true,
                'editOrder' => false,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchBidsAsks' => false,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposit' => null,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositWithdrawFee' => false,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => false,
                'fetchFundingInterval' => false,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchL3OrderBook' => false,
                'fetchLedger' => false,
                'fetchLeverage' => false,
                'fetchLeverages' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarginModes' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMarkPrices' => false,
                'fetchMyLiquidations' => false,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrder' => null,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => false,
                'fetchOrders' => false,
                'fetchOrderTrades' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => false,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => true,
                'fetchTradingLimits' => false,
                'fetchTransactionFee' => false,
                'fetchTransactionFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawal' => null,
                'fetchWithdrawals' => true,
                'fetchWithdrawalWhitelist' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'repayMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'signIn' => false,
                'transfer' => false,
                'withdraw' => true,
                'ws' => false,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '6h' => '6h',
                '8h' => '8h',
                '12h' => '12h',
                '1d' => '1d',
                '3d' => '3d',
                '1w' => '1w',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/225719995-48ab2026-4ddb-496c-9da7-0d7566617c9b.jpg',
                'api' => array(
                    'public' => 'https://api.pro.coins.ph',
                    'private' => 'https://api.pro.coins.ph',
                ),
                'www' => 'https://coins.ph/',
                'doc' => array(
                    'https://coins-docs.github.io/rest-api',
                ),
                'fees' => 'https://support.coins.ph/hc/en-us/sections/4407198694681-Limits-Fees',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'openapi/v1/ping' => 1,
                        'openapi/v1/time' => 1,
                        // cost 1 if 'symbol' param defined (one market symbol) or if 'symbols' param is a list of 1-20 market symbols
                        // cost 20 if 'symbols' param is a list of 21-100 market symbols
                        // cost 40 if 'symbols' param is a list of 101 or more market symbols or if both 'symbol' and 'symbols' params are omited
                        'openapi/quote/v1/ticker/24hr' => array( 'cost' => 1, 'noSymbolAndNoSymbols' => 40, 'byNumberOfSymbols' => array( array( 101, 40 ), array( 21, 20 ), array( 0, 1 ) ) ),
                        // cost 1 if 'symbol' param defined (one market symbol)
                        // cost 2 if 'symbols' param is a list of 1 or more market symbols or if both 'symbol' and 'symbols' params are omited
                        'openapi/quote/v1/ticker/price' => array( 'cost' => 1, 'noSymbol' => 2 ),
                        // cost 1 if 'symbol' param defined (one market symbol)
                        // cost 2 if 'symbols' param is a list of 1 or more market symbols or if both 'symbol' and 'symbols' params are omited
                        'openapi/quote/v1/ticker/bookTicker' => array( 'cost' => 1, 'noSymbol' => 2 ),
                        'openapi/v1/exchangeInfo' => 10,
                        // cost 1 if limit <= 100; 5 if limit > 100.
                        'openapi/quote/v1/depth' => array( 'cost' => 1, 'byLimit' => array( array( 101, 5 ), array( 0, 1 ) ) ),
                        'openapi/quote/v1/klines' => 1, // default limit 500; max 1000.
                        'openapi/quote/v1/trades' => 1, // default limit 500; max 1000. if limit <=0 or > 1000 then return 1000
                        'openapi/v1/pairs' => 1,
                        'openapi/quote/v1/avgPrice' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'openapi/wallet/v1/config/getall' => 10,
                        'openapi/wallet/v1/deposit/address' => 10,
                        'openapi/wallet/v1/deposit/history' => 1,
                        'openapi/wallet/v1/withdraw/history' => 1,
                        'openapi/v1/account' => 10,
                        // cost 3 for a single symbol; 40 when the symbol parameter is omitted
                        'openapi/v1/openOrders' => array( 'cost' => 3, 'noSymbol' => 40 ),
                        'openapi/v1/asset/tradeFee' => 1,
                        'openapi/v1/order' => 2,
                        // cost 10 with symbol, 40 when the symbol parameter is omitted;
                        'openapi/v1/historyOrders' => array( 'cost' => 10, 'noSymbol' => 40 ),
                        'openapi/v1/myTrades' => 10,
                        'openapi/v1/capital/deposit/history' => 1,
                        'openapi/v1/capital/withdraw/history' => 1,
                        'openapi/v3/payment-request/get-payment-request' => 1,
                        'merchant-api/v1/get-invoices' => 1,
                        'openapi/account/v3/crypto-accounts' => 1,
                        'openapi/transfer/v3/transfers/{id}' => 1,
                    ),
                    'post' => array(
                        'openapi/wallet/v1/withdraw/apply' => 600,
                        'openapi/v1/order/test' => 1,
                        'openapi/v1/order' => 1,
                        'openapi/v1/capital/withdraw/apply' => 1,
                        'openapi/v1/capital/deposit/apply' => 1,
                        'openapi/v3/payment-request/payment-requests' => 1,
                        'openapi/v3/payment-request/delete-payment-request' => 1,
                        'openapi/v3/payment-request/payment-request-reminder' => 1,
                        'openapi/v1/userDataStream' => 1,
                        'merchant-api/v1/invoices' => 1,
                        'merchant-api/v1/invoices-cancel' => 1,
                        'openapi/convert/v1/get-supported-trading-pairs' => 1,
                        'openapi/convert/v1/get-quote' => 1,
                        'openapi/convert/v1/accpet-quote' => 1,
                        'openapi/fiat/v1/support-channel' => 1,
                        'openapi/fiat/v1/cash-out' => 1,
                        'openapi/fiat/v1/history' => 1,
                        'openapi/migration/v4/sellorder' => 1,
                        'openapi/migration/v4/validate-field' => 1,
                        'openapi/transfer/v3/transfers' => 1,
                    ),
                    'delete' => array(
                        'openapi/v1/order' => 1,
                        'openapi/v1/openOrders' => 1,
                        'openapi/v1/userDataStream' => 1,
                    ),
                ),
            ),
            'fees' => array(
                // todo => zero fees for USDT, ETH and BTC markets till 2023-04-02
                'trading' => array(
                    'feeSide' => 'get',
                    'tierBased' => true,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.0025'),
                    'taker' => $this->parse_number('0.003'),
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.003') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0027') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0024') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.002') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0018') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('500000000'), $this->parse_number('0.0009') ),
                            array( $this->parse_number('1000000000'), $this->parse_number('0.0007') ),
                            array( $this->parse_number('2500000000'), $this->parse_number('0.0005') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0025') ),
                            array( $this->parse_number('500000'), $this->parse_number('0.0022') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0018') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('5000000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('10000000'), $this->parse_number('0.001') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('500000000'), $this->parse_number('0.0007') ),
                            array( $this->parse_number('1000000000'), $this->parse_number('0.0006') ),
                            array( $this->parse_number('2500000000'), $this->parse_number('0.0005') ),
                        ),
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            // exchange-specific options
            'options' => array(
                'createMarketBuyOrderRequiresPrice' => true, // true or false
                'withdraw' => array(
                    'warning' => false,
                ),
                'deposit' => array(
                    'warning' => false,
                ),
                'createOrder' => array(
                    'timeInForce' => 'GTC', // FOK, IOC
                    'newOrderRespType' => array(
                        'market' => 'FULL', // FULL, RESULT. ACK
                        'limit' => 'FULL', // we change it from 'ACK' by default to 'FULL'
                    ),
                ),
                'fetchTicker' => array(
                    'method' => 'publicGetOpenapiQuoteV1Ticker24hr', // publicGetOpenapiQuoteV1TickerPrice, publicGetOpenapiQuoteV1TickerBookTicker
                ),
                'fetchTickers' => array(
                    'method' => 'publicGetOpenapiQuoteV1Ticker24hr', // publicGetOpenapiQuoteV1TickerPrice, publicGetOpenapiQuoteV1TickerBookTicker
                ),
                'networks' => array(
                    // all networks => 'ETH', 'TRX', 'BSC', 'ARBITRUM', 'RON', 'BTC', 'XRP'
                    // you can call api privateGetOpenapiWalletV1ConfigGetall to check which network is supported for the currency
                    'TRC20' => 'TRX',
                    'ERC20' => 'ETH',
                    'BEP20' => 'BSC',
                    'ARB' => 'ARBITRUM',
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'fetchCurrencies' => array(
                        'private' => true,
                    ),
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => false,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => true, // todo implement
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'untilDays' => 100000, // todo implement
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'daysBackCanceled' => 1,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            // https://coins-docs.github.io/errors/
            'exceptions' => array(
                'exact' => array(
                    '-1000' => '\\ccxt\\BadRequest', // An unknown error occured while processing the request.
                    '-1001' => '\\ccxt\\BadRequest', // array("code":-1001,"msg":"Internal error.")
                    '-1002' => '\\ccxt\\AuthenticationError', // You are not authorized to execute this request. Request need API Key included in . We suggest that API Key be included in any request.
                    '-1003' => '\\ccxt\\RateLimitExceeded', // Too many requests; please use the websocket for live updates. Too many requests; current limit is %s requests per minute. Please use the websocket for live updates to avoid polling the API. Way too many requests; IP banned until %s. Please use the websocket for live updates to avoid bans.
                    '-1004' => '\\ccxt\\InvalidOrder', // array("code":-1004,"msg":"Missing required parameter \u0027symbol\u0027")
                    '-1006' => '\\ccxt\\BadResponse', // An unexpected response was received from the message bus. Execution status unknown. OPEN API server find some exception in execute request .Please report to Customer service.
                    '-1007' => '\\ccxt\\BadResponse', // Timeout waiting for response from backend server. Send status unknown; execution status unknown.
                    '-1014' => '\\ccxt\\InvalidOrder', // Unsupported order combination.
                    '-1015' => '\\ccxt\\RateLimitExceeded', // Reach the rate limit .Please slow down your request speed. Too many new orders. Too many new orders; current limit is %s orders per %s.
                    '-1016' => '\\ccxt\\NotSupported', // This service is no longer available.
                    '-1020' => '\\ccxt\\NotSupported', // This operation is not supported.
                    '-1021' => '\\ccxt\\BadRequest', // array("code":-1021,"msg":"Timestamp for this request is outside of the recvWindow.")
                    '-1022' => '\\ccxt\\BadRequest', // array("code":-1022,"msg":"Signature for this request is not valid.")
                    '-1023' => '\\ccxt\\AuthenticationError', // Please set IP whitelist before using API.
                    '-1024' => '\\ccxt\\BadRequest', // array("code":-1024,"msg":"recvWindow is not valid.")
                    '-1025' => '\\ccxt\\BadRequest', // array("code":-1025,"msg":"recvWindow cannot be greater than 60000")
                    '-1030' => '\\ccxt\\ExchangeError', // Business error.
                    '-1100' => '\\ccxt\\BadRequest', // Illegal characters found in a parameter. Illegal characters found in parameter ‘%s’; legal range is ‘%s’.
                    '-1101' => '\\ccxt\\BadRequest', // Too many parameters sent for this endpoint. Too many parameters; expected ‘%s’ and received ‘%s’. Duplicate values for a parameter detected.
                    '-1102' => '\\ccxt\\BadRequest', // A mandatory parameter was not sent, was empty/null, or malformed. Mandatory parameter ‘%s’ was not sent, was empty/null, or malformed. Param ‘%s’ or ‘%s’ must be sent, but both were empty/null!
                    '-1103' => '\\ccxt\\BadRequest', // An unknown parameter was sent. In BHEx Open Api , each request requires at least one parameter. {Timestamp}.
                    '-1104' => '\\ccxt\\BadRequest', // Not all sent parameters were read. Not all sent parameters were read; read ‘%s’ parameter(s) but was sent ‘%s’.
                    '-1105' => '\\ccxt\\BadRequest', // array("code":-1105,"msg":"Parameter \u0027orderId and origClientOrderId\u0027 is empty.")
                    '-1106' => '\\ccxt\\BadRequest', // A parameter was sent when not required. Parameter ‘%s’ sent when not required.
                    '-1111' => '\\ccxt\\BadRequest', // Precision is over the maximum defined for this asset.
                    '-1112' => '\\ccxt\\BadResponse', // No orders on book for symbol.
                    '-1114' => '\\ccxt\\BadRequest', // TimeInForce parameter sent when not required.
                    '-1115' => '\\ccxt\\InvalidOrder', // array("code":-1115,"msg":"Invalid timeInForce.")
                    '-1116' => '\\ccxt\\InvalidOrder', // array("code":-1116,"msg":"Invalid orderType.")
                    '-1117' => '\\ccxt\\InvalidOrder', // array("code":-1117,"msg":"Invalid side.")
                    '-1118' => '\\ccxt\\InvalidOrder', // New client order ID was empty.
                    '-1119' => '\\ccxt\\InvalidOrder', // Original client order ID was empty.
                    '-1120' => '\\ccxt\\BadRequest', // Invalid interval.
                    '-1121' => '\\ccxt\\BadSymbol', // Invalid symbol.
                    '-1122' => '\\ccxt\\InvalidOrder', // Invalid newOrderRespType.
                    '-1125' => '\\ccxt\\BadRequest', // This listenKey does not exist.
                    '-1127' => '\\ccxt\\BadRequest', // Lookup interval is too big. More than %s hours between startTime and endTime.
                    '-1128' => '\\ccxt\\BadRequest', // Combination of optional parameters invalid.
                    '-1130' => '\\ccxt\\BadRequest', // Invalid data sent for a parameter. Data sent for paramter ‘%s’ is not valid.
                    '-1131' => '\\ccxt\\InsufficientFunds', // array("code":-1131,"msg":"Balance insufficient ")
                    '-1132' => '\\ccxt\\InvalidOrder', // Order price too high.
                    '-1133' => '\\ccxt\\InvalidOrder', // Order price lower than the minimum,please check general broker info.
                    '-1134' => '\\ccxt\\InvalidOrder', // Order price decimal too long,please check general broker info.
                    '-1135' => '\\ccxt\\InvalidOrder', // Order quantity too large.
                    '-1136' => '\\ccxt\\InvalidOrder', // Order quantity lower than the minimum.
                    '-1137' => '\\ccxt\\InvalidOrder', // Order quantity decimal too long.
                    '-1138' => '\\ccxt\\InvalidOrder', // Order price exceeds permissible range.
                    '-1139' => '\\ccxt\\InvalidOrder', // Order has been filled.
                    '-1140' => '\\ccxt\\InvalidOrder', // array("code":-1140,"msg":"Transaction amount lower than the minimum.")
                    '-1141' => '\\ccxt\\DuplicateOrderId', // array("code":-1141,"msg":"Duplicate clientOrderId")
                    '-1142' => '\\ccxt\\InvalidOrder', // array("code":-1142,"msg":"Order has been canceled")
                    '-1143' => '\\ccxt\\OrderNotFound', // Cannot be found on order book
                    '-1144' => '\\ccxt\\InvalidOrder', // Order has been locked
                    '-1145' => '\\ccxt\\InvalidOrder', // This order type does not support cancellation
                    '-1146' => '\\ccxt\\InvalidOrder', // Order creation timeout
                    '-1147' => '\\ccxt\\InvalidOrder', // Order cancellation timeout
                    '-1148' => '\\ccxt\\InvalidOrder', // Market order amount decimal too long
                    '-1149' => '\\ccxt\\InvalidOrder', // Create order failed
                    '-1150' => '\\ccxt\\InvalidOrder', // Cancel order failed
                    '-1151' => '\\ccxt\\BadSymbol', // The trading pair is not open yet
                    '-1152' => '\\ccxt\\NotSupported', // Coming soon
                    '-1153' => '\\ccxt\\AuthenticationError', // User not exist
                    '-1154' => '\\ccxt\\BadRequest', // Invalid price type
                    '-1155' => '\\ccxt\\BadRequest', // Invalid position side
                    '-1156' => '\\ccxt\\InvalidOrder', // Order quantity invalid
                    '-1157' => '\\ccxt\\BadSymbol', // The trading pair is not available for api trading
                    '-1158' => '\\ccxt\\InvalidOrder', // create limit maker order failed
                    '-1159' => '\\ccxt\\InvalidOrder', // array("code":-1159,"msg":"STOP_LOSS/TAKE_PROFIT order is not allowed to trade immediately")
                    '-1160' => '\\ccxt\\BadRequest', // Modify futures margin error
                    '-1161' => '\\ccxt\\BadRequest', // Reduce margin forbidden
                    '-2010' => '\\ccxt\\InvalidOrder', // array("code":-2010,"msg":"New order rejected.")
                    '-2013' => '\\ccxt\\OrderNotFound', // array("code":-2013,"msg":"Order does not exist.")
                    '-2011' => '\\ccxt\\BadRequest', // CANCEL_REJECTED
                    '-2014' => '\\ccxt\\BadRequest', // API-key format invalid.
                    '-2015' => '\\ccxt\\AuthenticationError', // array("code":-2015,"msg":"Invalid API-key, IP, or permissions for action.")
                    '-2016' => '\\ccxt\\BadResponse', // No trading window could be found for the symbol. Try ticker/24hrs instead
                    '-3126' => '\\ccxt\\InvalidOrder', // array("code":-3126,"msg":"Order price lower than 72005.93415")
                    '-3127' => '\\ccxt\\InvalidOrder', // array("code":-3127,"msg":"Order price higher than 1523.192")
                    '-4001' => '\\ccxt\\BadRequest', // array("code":-4001,"msg":"start time must less than end time")
                    '-100011' => '\\ccxt\\BadSymbol', // array("code":-100011,"msg":"Not supported symbols")
                    '-100012' => '\\ccxt\\BadSymbol', // array("code":-100012,"msg":"Parameter symbol ['strval'] missing!")
                    '-30008' => '\\ccxt\\InsufficientFunds', // array("code":-30008,"msg":"withdraw balance insufficient")
                    '-30036' => '\\ccxt\\InsufficientFunds', // array("code":-30036,"msg":"Available balance not enough!")
                    '403' => '\\ccxt\\ExchangeNotAvailable',
                ),
                'broad' => array(
                    'Unknown order sent' => '\\ccxt\\OrderNotFound', // The order (by either orderId, clOrdId, origClOrdId) could not be found
                    'Duplicate order sent' => '\\ccxt\\DuplicateOrderId', // The clOrdId is already in use
                    'Market is closed' => '\\ccxt\\BadSymbol', // The symbol is not trading
                    'Account has insufficient balance for requested action' => '\\ccxt\\InsufficientFunds', // Not enough funds to complete the action
                    'Market orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // MARKET is not enabled on the symbol
                    'Iceberg orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // icebergQty is not enabled on the symbol
                    'Stop loss orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // STOP_LOSS is not enabled on the symbol
                    'Stop loss limit orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // STOP_LOSS_LIMIT is not enabled on the symbol
                    'Take profit orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // TAKE_PROFIT is not enabled on the symbol
                    'Take profit limit orders are not supported for this symbol' => '\\ccxt\\BadSymbol', // TAKE_PROFIT_LIMIT is not enabled on the symbol
                    'Price* QTY is zero or less' => '\\ccxt\\BadRequest', // price* quantity is too low
                    'IcebergQty exceeds QTY' => '\\ccxt\\BadRequest', // icebergQty must be less than the order quantity
                    'This action disabled is on this account' => '\\ccxt\\PermissionDenied', // Contact customer support; some actions have been disabled on the account.
                    'Unsupported order combination' => '\\ccxt\\InvalidOrder', // The orderType, timeInForce, stopPrice, and or icebergQty combination isn’t allowed.
                    'Order would trigger immediately' => '\\ccxt\\InvalidOrder', // The order’s stop price is not valid when compared to the last traded price.
                    'Cancel order is invalid. Check origClOrdId and orderId' => '\\ccxt\\InvalidOrder', // No origClOrdId or orderId was sent in.
                    'Order would immediately match and take' => '\\ccxt\\OrderImmediatelyFillable', // LIMIT_MAKER order type would immediately match and trade, and not be a pure maker order.
                    'PRICE_FILTER' => '\\ccxt\\InvalidOrder', // price is too high, too low, and or not following the tick size rule for the symbol.
                    'LOT_SIZE' => '\\ccxt\\InvalidOrder', // quantity is too high, too low, and or not following the step size rule for the symbol.
                    'MIN_NOTIONAL' => '\\ccxt\\InvalidOrder', // price* quantity is too low to be a valid order for the symbol.
                    'MAX_NUM_ORDERS' => '\\ccxt\\InvalidOrder', // Account has too many open orders on the symbol.
                    'MAX_ALGO_ORDERS' => '\\ccxt\\InvalidOrder', // Account has too many open stop loss and or take profit orders on the symbol.
                    'BROKER_MAX_NUM_ORDERS' => '\\ccxt\\InvalidOrder', // Account has too many open orders on the broker.
                    'BROKER_MAX_ALGO_ORDERS' => '\\ccxt\\InvalidOrder', // Account has too many open stop loss and or take profit orders on the broker.
                    'ICEBERG_PARTS' => '\\ccxt\\BadRequest', // Iceberg order would break into too many parts; icebergQty is too small.
                ),
            ),
        ));
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         *
         * @see https://docs.coins.ph/rest-api/#all-coins-information-user_data
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        if (!$this->check_required_credentials(false)) {
            return array();
        }
        $response = $this->privateGetOpenapiWalletV1ConfigGetall ($params);
        //
        //    [
        //        array(
        //            "coin" => "PHP",
        //            "name" => "PHP",
        //            "depositAllEnable" => false,
        //            "withdrawAllEnable" => false,
        //            "free" => "0",
        //            "locked" => "0",
        //            "transferPrecision" => "2",
        //            "transferMinQuantity" => "0",
        //            "networkList" => array(),
        //            "legalMoney" => true
        //        ),
        //        {
        //            "coin" => "USDT",
        //            "name" => "USDT",
        //            "depositAllEnable" => true,
        //            "withdrawAllEnable" => true,
        //            "free" => "0",
        //            "locked" => "0",
        //            "transferPrecision" => "8",
        //            "transferMinQuantity" => "0",
        //            "networkList" => [
        //                array(
        //                    "addressRegex" => "^0x[0-9a-fA-F]{40}$",
        //                    "memoRegex" => " ",
        //                    "network" => "ETH",
        //                    "name" => "Ethereum (ERC20)",
        //                    "depositEnable" => true,
        //                    "minConfirm" => "12",
        //                    "unLockConfirm" => "-1",
        //                    "withdrawDesc" => "",
        //                    "withdrawEnable" => true,
        //                    "withdrawFee" => "6",
        //                    "withdrawIntegerMultiple" => "0.000001",
        //                    "withdrawMax" => "500000",
        //                    "withdrawMin" => "10",
        //                    "sameAddress" => false
        //                ),
        //                {
        //                    "addressRegex" => "^T[0-9a-zA-Z]{33}$",
        //                    "memoRegex" => "",
        //                    "network" => "TRX",
        //                    "name" => "TRON",
        //                    "depositEnable" => true,
        //                    "minConfirm" => "19",
        //                    "unLockConfirm" => "-1",
        //                    "withdrawDesc" => "",
        //                    "withdrawEnable" => true,
        //                    "withdrawFee" => "3",
        //                    "withdrawIntegerMultiple" => "0.000001",
        //                    "withdrawMax" => "1000000",
        //                    "withdrawMin" => "20",
        //                    "sameAddress" => false
        //                }
        //            ],
        //            "legalMoney" => false
        //        }
        //    ]
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $entry = $response[$i];
            $id = $this->safe_string($entry, 'coin');
            $code = $this->safe_currency_code($id);
            $isFiat = $this->safe_bool($entry, 'isLegalMoney');
            $networkList = $this->safe_list($entry, 'networkList', array());
            $networks = array();
            for ($j = 0; $j < count($networkList); $j++) {
                $networkItem = $networkList[$j];
                $network = $this->safe_string($networkItem, 'network');
                $networkCode = $this->network_id_to_code($network);
                $networks[$networkCode] = array(
                    'info' => $networkItem,
                    'id' => $network,
                    'network' => $networkCode,
                    'active' => null,
                    'deposit' => $this->safe_bool($networkItem, 'depositEnable'),
                    'withdraw' => $this->safe_bool($networkItem, 'withdrawEnable'),
                    'fee' => $this->safe_number($networkItem, 'withdrawFee'),
                    'precision' => $this->safe_number($networkItem, 'withdrawIntegerMultiple'),
                    'limits' => array(
                        'withdraw' => array(
                            'min' => $this->safe_number($networkItem, 'withdrawMin'),
                            'max' => $this->safe_number($networkItem, 'withdrawMax'),
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                );
            }
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $id,
                'name' => $this->safe_string($entry, 'name'),
                'code' => $code,
                'type' => $isFiat ? 'fiat' : 'crypto',
                'precision' => $this->parse_number($this->parse_precision($this->safe_string($entry, 'transferPrecision'))),
                'info' => $entry,
                'active' => null,
                'deposit' => $this->safe_bool($entry, 'depositAllEnable'),
                'withdraw' => $this->safe_bool($entry, 'withdrawAllEnable'),
                'networks' => $networks,
                'fee' => null,
                'fees' => null,
                'limits' => array(),
            ));
        }
        return $result;
    }

    public function calculate_rate_limiter_cost($api, $method, $path, $params, $config = array ()) {
        if ((is_array($config) && array_key_exists('noSymbol', $config)) && !(is_array($params) && array_key_exists('symbol', $params))) {
            return $config['noSymbol'];
        } elseif ((is_array($config) && array_key_exists('noSymbolAndNoSymbols', $config)) && !(is_array($params) && array_key_exists('symbol', $params)) && !(is_array($params) && array_key_exists('symbols', $params))) {
            return $config['noSymbolAndNoSymbols'];
        } elseif ((is_array($config) && array_key_exists('byNumberOfSymbols', $config)) && (is_array($params) && array_key_exists('symbols', $params))) {
            $symbols = $params['symbols'];
            $symbolsAmount = count($symbols);
            $byNumberOfSymbols = $config['byNumberOfSymbols'];
            for ($i = 0; $i < count($byNumberOfSymbols); $i++) {
                $entry = $byNumberOfSymbols[$i];
                if ($symbolsAmount >= $entry[0]) {
                    return $entry[1];
                }
            }
        } elseif ((is_array($config) && array_key_exists('byLimit', $config)) && (is_array($params) && array_key_exists('limit', $params))) {
            $limit = $params['limit'];
            $byLimit = $config['byLimit'];
            for ($i = 0; $i < count($byLimit); $i++) {
                $entry = $byLimit[$i];
                if ($limit >= $entry[0]) {
                    return $entry[1];
                }
            }
        }
        return $this->safe_value($config, 'cost', 1);
    }

    public function fetch_status($params = array ()) {
        /**
         * the latest known information on the availability of the exchange API
         *
         * @see https://coins-docs.github.io/rest-api/#test-connectivity
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-status-structure status structure~
         */
        $response = $this->publicGetOpenapiV1Ping ($params);
        return array(
            'status' => 'ok', // if there's no Errors, status = 'ok'
            'updated' => null,
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://coins-docs.github.io/rest-api/#check-server-time
         *
         * @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->publicGetOpenapiV1Time ($params);
        //
        //     array("serverTime":1677705408268)
        //
        return $this->safe_integer($response, 'serverTime');
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all $markets for coinsph
         *
         * @see https://coins-docs.github.io/rest-api/#exchange-information
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $response = $this->publicGetOpenapiV1ExchangeInfo ($params);
        //
        //     {
        //         "timezone" => "UTC",
        //         "serverTime" => "1677449496897",
        //         "exchangeFilters" => array(),
        //         "symbols" => array(
        //             array(
        //                 "symbol" => "XRPPHP",
        //                 "status" => "TRADING",
        //                 "baseAsset" => "XRP",
        //                 "baseAssetPrecision" => "2",
        //                 "quoteAsset" => "PHP",
        //                 "quoteAssetPrecision" => "4",
        //                 "orderTypes" => array(
        //                     "LIMIT",
        //                     "MARKET",
        //                     "LIMIT_MAKER",
        //                     "STOP_LOSS_LIMIT",
        //                     "STOP_LOSS",
        //                     "TAKE_PROFIT_LIMIT",
        //                     "TAKE_PROFIT"
        //                 ),
        //                 "filters" => array(
        //                     array(
        //                         "minPrice" => "0.01",
        //                         "maxPrice" => "99999999.00000000",
        //                         "tickSize" => "0.01",
        //                         "filterType" => "PRICE_FILTER"
        //                     ),
        //                     array(
        //                         "minQty" => "0.01",
        //                         "maxQty" => "99999999999.00000000",
        //                         "stepSize" => "0.01",
        //                         "filterType" => "LOT_SIZE"
        //                     ),
        //                     array( minNotional => "50", filterType => "NOTIONAL" ),
        //                     array( minNotional => "50", filterType => "MIN_NOTIONAL" ),
        //                     array(
        //                         "priceUp" => "99999999",
        //                         "priceDown" => "0.01",
        //                         "filterType" => "STATIC_PRICE_RANGE"
        //                     ),
        //                     array(
        //                         "multiplierUp" => "1.1",
        //                         "multiplierDown" => "0.9",
        //                         "filterType" => "PERCENT_PRICE_INDEX"
        //                     ),
        //                     array(
        //                         "multiplierUp" => "1.1",
        //                         "multiplierDown" => "0.9",
        //                         "filterType" => "PERCENT_PRICE_ORDER_SIZE"
        //                     ),
        //                     array( maxNumOrders => "200", filterType => "MAX_NUM_ORDERS" ),
        //                     array( maxNumAlgoOrders => "5", filterType => "MAX_NUM_ALGO_ORDERS" )
        //                 )
        //             ),
        //         )
        //     }
        //
        $markets = $this->safe_list($response, 'symbols', array());
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $id = $this->safe_string($market, 'symbol');
            $baseId = $this->safe_string($market, 'baseAsset');
            $quoteId = $this->safe_string($market, 'quoteAsset');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $limits = $this->index_by($this->safe_list($market, 'filters', array()), 'filterType');
            $amountLimits = $this->safe_value($limits, 'LOT_SIZE', array());
            $priceLimits = $this->safe_value($limits, 'PRICE_FILTER', array());
            $costLimits = $this->safe_value($limits, 'NOTIONAL', array());
            $result[] = array(
                'id' => $id,
                'symbol' => $base . '/' . $quote,
                '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' => $this->safe_string_lower($market, 'status') === 'trading',
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'taker' => null,
                'maker' => null,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->parse_number($this->safe_string($amountLimits, 'stepSize')),
                    'price' => $this->parse_number($this->safe_string($priceLimits, 'tickSize')),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => $this->parse_number($this->safe_string($amountLimits, 'minQty')),
                        'max' => $this->parse_number($this->safe_string($amountLimits, 'maxQty')),
                    ),
                    'price' => array(
                        'min' => $this->parse_number($this->safe_string($priceLimits, 'minPrice')),
                        'max' => $this->parse_number($this->safe_string($priceLimits, 'maxPrice')),
                    ),
                    'cost' => array(
                        'min' => $this->parse_number($this->safe_string($costLimits, 'minNotional')),
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $market,
            );
        }
        $this->set_markets($result);
        return $result;
    }

    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://coins-docs.github.io/rest-api/#24hr-ticker-price-change-statistics
         * @see https://coins-docs.github.io/rest-api/#symbol-price-ticker
         * @see https://coins-docs.github.io/rest-api/#symbol-order-book-ticker
         *
         * @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();
        $request = array();
        if ($symbols !== null) {
            $ids = array();
            for ($i = 0; $i < count($symbols); $i++) {
                $market = $this->market($symbols[$i]);
                $id = $market['id'];
                $ids[] = $id;
            }
            $request['symbols'] = $ids;
        }
        $defaultMethod = 'publicGetOpenapiQuoteV1Ticker24hr';
        $options = $this->safe_dict($this->options, 'fetchTickers', array());
        $method = $this->safe_string($options, 'method', $defaultMethod);
        $tickers = null;
        if ($method === 'publicGetOpenapiQuoteV1TickerPrice') {
            $tickers = $this->publicGetOpenapiQuoteV1TickerPrice ($this->extend($request, $params));
        } elseif ($method === 'publicGetOpenapiQuoteV1TickerBookTicker') {
            $tickers = $this->publicGetOpenapiQuoteV1TickerBookTicker ($this->extend($request, $params));
        } else {
            $tickers = $this->publicGetOpenapiQuoteV1Ticker24hr ($this->extend($request, $params));
        }
        return $this->parse_tickers($tickers, $symbols, $params);
    }

    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://coins-docs.github.io/rest-api/#24hr-$ticker-price-change-statistics
         * @see https://coins-docs.github.io/rest-api/#$symbol-price-$ticker
         * @see https://coins-docs.github.io/rest-api/#$symbol-order-book-$ticker
         *
         * @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(
            'symbol' => $market['id'],
        );
        $defaultMethod = 'publicGetOpenapiQuoteV1Ticker24hr';
        $options = $this->safe_dict($this->options, 'fetchTicker', array());
        $method = $this->safe_string($options, 'method', $defaultMethod);
        $ticker = null;
        if ($method === 'publicGetOpenapiQuoteV1TickerPrice') {
            $ticker = $this->publicGetOpenapiQuoteV1TickerPrice ($this->extend($request, $params));
        } elseif ($method === 'publicGetOpenapiQuoteV1TickerBookTicker') {
            $ticker = $this->publicGetOpenapiQuoteV1TickerBookTicker ($this->extend($request, $params));
        } else {
            $ticker = $this->publicGetOpenapiQuoteV1Ticker24hr ($this->extend($request, $params));
        }
        return $this->parse_ticker($ticker, $market);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // publicGetOpenapiQuoteV1Ticker24hr
        //     {
        //         "symbol" => "ETHUSDT",
        //         "priceChange" => "41.440000000000000000",
        //         "priceChangePercent" => "0.0259",
        //         "weightedAvgPrice" => "1631.169825783972125436",
        //         "prevClosePrice" => "1601.520000000000000000",
        //         "lastPrice" => "1642.96",
        //         "lastQty" => "0.000001000000000000",
        //         "bidPrice" => "1638.790000000000000000",
        //         "bidQty" => "0.280075000000000000",
        //         "askPrice" => "1647.340000000000000000",
        //         "askQty" => "0.165183000000000000",
        //         "openPrice" => "1601.52",
        //         "highPrice" => "1648.28",
        //         "lowPrice" => "1601.52",
        //         "volume" => "0.000287",
        //         "quoteVolume" => "0.46814574",
        //         "openTime" => "1677417000000",
        //         "closeTime" => "1677503415200",
        //         "firstId" => "1364680572697591809",
        //         "lastId" => "1365389809203560449",
        //         "count" => "100"
        //     }
        //
        // publicGetOpenapiQuoteV1TickerPrice
        //     array( "symbol" => "ETHUSDT", "price" => "1599.68" )
        //
        // publicGetOpenapiQuoteV1TickerBookTicker
        //     {
        //         "symbol" => "ETHUSDT",
        //         "bidPrice" => "1596.57",
        //         "bidQty" => "0.246405",
        //         "askPrice" => "1605.12",
        //         "askQty" => "0.242681"
        //     }
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($ticker, 'closeTime');
        $bid = $this->safe_string($ticker, 'bidPrice');
        $ask = $this->safe_string($ticker, 'askPrice');
        $bidVolume = $this->safe_string($ticker, 'bidQty');
        $askVolume = $this->safe_string($ticker, 'askQty');
        $baseVolume = $this->safe_string($ticker, 'volume');
        $quoteVolume = $this->safe_string($ticker, 'quoteVolume');
        $open = $this->safe_string($ticker, 'openPrice');
        $high = $this->safe_string($ticker, 'highPrice');
        $low = $this->safe_string($ticker, 'lowPrice');
        $prevClose = $this->safe_string($ticker, 'prevClosePrice');
        $vwap = $this->safe_string($ticker, 'weightedAvgPrice');
        $changeValue = $this->safe_string($ticker, 'priceChange');
        $changePcnt = $this->safe_string($ticker, 'priceChangePercent');
        $changePcnt = Precise::string_mul($changePcnt, '100');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'open' => $open,
            'high' => $high,
            'low' => $low,
            'close' => $this->safe_string_2($ticker, 'lastPrice', 'price'),
            'bid' => $bid,
            'bidVolume' => $bidVolume,
            'ask' => $ask,
            'askVolume' => $askVolume,
            'vwap' => $vwap,
            'previousClose' => $prevClose,
            'change' => $changeValue,
            'percentage' => $changePcnt,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            '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://coins-docs.github.io/rest-api/#order-book
         *
         * @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 (default 100, max 200)
         * @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(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetOpenapiQuoteV1Depth ($this->extend($request, $params));
        //
        //     {
        //         "lastUpdateId" => "1667022157000699400",
        //         "bids" => array(
        //             array( '1651.810000000000000000', '0.214556000000000000' ),
        //             array( '1651.730000000000000000', '0.257343000000000000' ),
        //         ),
        //         "asks" => array(
        //             array( '1660.510000000000000000', '0.299092000000000000' ),
        //             array( '1660.600000000000000000', '0.253667000000000000' ),
        //         )
        //     }
        //
        $orderbook = $this->parse_order_book($response, $symbol);
        $orderbook['nonce'] = $this->safe_integer($response, 'lastUpdateId');
        return $orderbook;
    }

    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://coins-docs.github.io/rest-api/#klinecandlestick-data
         *
         * @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 (default 500, max 1000)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $interval = $this->safe_string($this->timeframes, $timeframe);
        $until = $this->safe_integer($params, 'until');
        $request = array(
            'symbol' => $market['id'],
            'interval' => $interval,
        );
        if ($limit === null) {
            $limit = 1000;
        }
        if ($since !== null) {
            $request['startTime'] = $since;
            // $since work properly only when it is "younger" than last "limit" candle
            if ($until !== null) {
                $request['endTime'] = $until;
            } else {
                $duration = $this->parse_timeframe($timeframe) * 1000;
                $endTimeByLimit = $this->sum($since, $duration * ($limit - 1));
                $now = $this->milliseconds();
                $request['endTime'] = min ($endTimeByLimit, $now);
            }
        } elseif ($until !== null) {
            $request['endTime'] = $until;
            // $since work properly only when it is "younger" than last "limit" candle
            $duration = $this->parse_timeframe($timeframe) * 1000;
            $request['startTime'] = $until - ($duration * ($limit - 1));
        }
        $request['limit'] = $limit;
        $params = $this->omit($params, 'until');
        $response = $this->publicGetOpenapiQuoteV1Klines ($this->extend($request, $params));
        //
        //     array(
        //         array(
        //             1499040000000,      // Open time
        //             "0.01634790",       // Open
        //             "0.80000000",       // High
        //             "0.01575800",       // Low
        //             "0.01577100",       // Close
        //             "148976.11427815",  // Volume
        //             1499644799999,      // Close time
        //             "2434.19055334",    // Quote asset volume
        //             308,                // Number of trades
        //             "1756.87402397",    // Taker buy base asset volume
        //             "28.46694368"       // Taker buy quote asset volume
        //         )
        //     )
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        return array(
            $this->safe_integer($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_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://coins-docs.github.io/rest-api/#recent-trades-list
         *
         * @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 (default 500, max 1000)
         * @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(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            // $since work properly only when it is "younger" than last 'limit' trade
            $request['limit'] = 1000;
        } else {
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
        }
        $response = $this->publicGetOpenapiQuoteV1Trades ($this->extend($request, $params));
        //
        //     array(
        //         array(
        //             "price" => "89685.8",
        //             "id" => "1365561108437680129",
        //             "qty" => "0.000004",
        //             "quoteQty" => "0.000004000000000000",
        //             "time" => "1677523569575",
        //             "isBuyerMaker" => false,
        //             "isBestMatch" => true
        //         ),
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://coins-docs.github.io/rest-api/#account-trade-list-user_data
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve (default 500, max 1000)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['startTime'] = $since;
            // $since work properly only when it is "younger" than last 'limit' trade
            $request['limit'] = 1000;
        } elseif ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenapiV1MyTrades ($this->extend($request, $params));
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all the trades made from a single order
         *
         * @see https://coins-docs.github.io/rest-api/#account-trade-list-user_data
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrderTrades() requires a $symbol argument');
        }
        $request = array(
            'orderId' => $id,
        );
        return $this->fetch_my_trades($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades
        //     array(
        //         "price" => "89685.8",
        //         "id" => "1365561108437680129",
        //         "qty" => "0.000004",
        //         "quoteQty" => "0.000004000000000000", // warning => report to exchange - this is not quote quantity, this is base quantity
        //         "time" => "1677523569575",
        //         "isBuyerMaker" => false,
        //         "isBestMatch" => true
        //     ),
        //
        // fetchMyTrades
        //     {
        //         "symbol" => "ETHUSDT",
        //         "id" => 1375426310524125185,
        //         "orderId" => 1375426310415879614,
        //         "price" => "1580.91",
        //         "qty" => "0.01",
        //         "quoteQty" => "15.8091",
        //         "commission" => "0",
        //         "commissionAsset" => "USDT",
        //         "time" => 1678699593307,
        //         "isBuyer" => false,
        //         "isMaker":false,
        //         "isBestMatch":false
        //     }
        //
        // createOrder
        //     {
        //         "price" => "1579.51",
        //         "qty" => "0.001899",
        //         "commission" => "0",
        //         "commissionAsset" => "ETH",
        //         "tradeId":1375445992035598337
        //     }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $id = $this->safe_string_2($trade, 'id', 'tradeId');
        $orderId = $this->safe_string($trade, 'orderId');
        $timestamp = $this->safe_integer($trade, 'time');
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'qty');
        $type = null;
        $fee = null;
        $feeCost = $this->safe_string($trade, 'commission');
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'commissionAsset');
            $fee = array(
                'cost' => $feeCost,
                'currency' => $this->safe_currency_code($feeCurrencyId),
            );
        }
        $isBuyer = $this->safe_bool_2($trade, 'isBuyer', 'isBuyerMaker', null);
        $side = null;
        if ($isBuyer !== null) {
            $side = ($isBuyer === true) ? 'buy' : 'sell';
        }
        $isMaker = $this->safe_string_2($trade, 'isMaker', null);
        $takerOrMaker = null;
        if ($isMaker !== null) {
            $takerOrMaker = ($isMaker === 'true') ? 'maker' : 'taker';
        }
        $costString = null;
        if ($orderId !== null) {
            $costString = $this->safe_string($trade, 'quoteQty');
        }
        return $this->safe_trade(array(
            'id' => $id,
            'order' => $orderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => $costString,
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    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://coins-docs.github.io/rest-api/#accept-the-quote
         *
         * @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->privateGetOpenapiV1Account ($params);
        //
        //     {
        //         "accountType" => "SPOT",
        //         "balances" => array(
        //             array(
        //                 "asset" => "BTC",
        //                 "free" => "4723846.89208129",
        //                 "locked" => "0.00000000"
        //             ),
        //             {
        //                 "asset" => "LTC",
        //                 "free" => "4763368.68006011",
        //                 "locked" => "0.00000000"
        //             }
        //         ),
        //         "canDeposit" => true,
        //         "canTrade" => true,
        //         "canWithdraw" => true,
        //         "updateTime" => "1677430932528"
        //     }
        //
        return $this->parse_balance($response);
    }

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

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://coins-docs.github.io/rest-api/#new-order--trade
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market', 'limit', 'stop_loss', 'take_profit', 'stop_loss_limit', 'take_profit_limit' or 'limit_maker'
         * @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
         * @param {float} [$params->cost] the quote quantity that can be used alternative for the $amount for $market buy orders
         * @param {bool} [$params->test] set to true to test an order, no order will be created but the $request will be validated
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        // todo => add test order low priority
        $this->load_markets();
        $market = $this->market($symbol);
        $testOrder = $this->safe_bool($params, 'test', false);
        $params = $this->omit($params, 'test');
        $orderType = $this->safe_string($params, 'type', $type);
        $orderType = $this->encode_order_type($orderType);
        $params = $this->omit($params, 'type');
        $orderSide = $this->encode_order_side($side);
        $request = array(
            'symbol' => $market['id'],
            'type' => $orderType,
            'side' => $orderSide,
        );
        $options = $this->safe_value($this->options, 'createOrder', array());
        $newOrderRespType = $this->safe_value($options, 'newOrderRespType', array());
        // if limit order
        if ($orderType === 'LIMIT' || $orderType === 'STOP_LOSS_LIMIT' || $orderType === 'TAKE_PROFIT_LIMIT' || $orderType === 'LIMIT_MAKER') {
            if ($price === null) {
                throw new ArgumentsRequired($this->id . ' createOrder() requires a $price argument for a ' . $type . ' order');
            }
            $newOrderRespType = $this->safe_string($newOrderRespType, 'limit', 'FULL');
            $request['price'] = $this->price_to_precision($symbol, $price);
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            if ($orderType !== 'LIMIT_MAKER') {
                $request['timeInForce'] = $this->safe_string($options, 'timeInForce', 'GTC');
            }
        // if $market order
        } elseif ($orderType === 'MARKET' || $orderType === 'STOP_LOSS' || $orderType === 'TAKE_PROFIT') {
            $newOrderRespType = $this->safe_string($newOrderRespType, 'market', 'FULL');
            if ($orderSide === 'SELL') {
                $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            } elseif ($orderSide === 'BUY') {
                $quoteAmount = null;
                $createMarketBuyOrderRequiresPrice = true;
                list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                $cost = $this->safe_number_2($params, 'cost', 'quoteOrderQty');
                $params = $this->omit($params, 'cost');
                if ($cost !== null) {
                    $quoteAmount = $this->cost_to_precision($symbol, $cost);
                } elseif ($createMarketBuyOrderRequiresPrice) {
                    if ($price === null) {
                        throw new InvalidOrder($this->id . ' createOrder() requires the $price argument for $market buy orders to calculate the total $cost to spend ($amount * $price), alternatively set the $createMarketBuyOrderRequiresPrice option or param to false and pass the $cost to spend in the $amount argument');
                    } else {
                        $amountString = $this->number_to_string($amount);
                        $priceString = $this->number_to_string($price);
                        $costRequest = Precise::string_mul($amountString, $priceString);
                        $quoteAmount = $this->cost_to_precision($symbol, $costRequest);
                    }
                } else {
                    $quoteAmount = $this->cost_to_precision($symbol, $amount);
                }
                $request['quoteOrderQty'] = $quoteAmount;
            }
        }
        if ($orderType === 'STOP_LOSS' || $orderType === 'STOP_LOSS_LIMIT' || $orderType === 'TAKE_PROFIT' || $orderType === 'TAKE_PROFIT_LIMIT') {
            $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
            if ($triggerPrice === null) {
                throw new InvalidOrder($this->id . ' createOrder () requires a $triggerPrice or stopPrice param for stop_loss, take_profit, stop_loss_limit, and take_profit_limit orders');
            }
            $request['stopPrice'] = $this->price_to_precision($symbol, $triggerPrice);
        }
        $request['newOrderRespType'] = $newOrderRespType;
        $params = $this->omit($params, 'price', 'stopPrice', 'triggerPrice', 'quantity', 'quoteOrderQty');
        $response = null;
        if ($testOrder) {
            $response = $this->privatePostOpenapiV1OrderTest ($this->extend($request, $params));
        } else {
            $response = $this->privatePostOpenapiV1Order ($this->extend($request, $params));
        }
        //
        //     {
        //         "symbol" => "ETHUSDT",
        //         "orderId" => "1375407140139731486",
        //         "clientOrderId" => "1375407140139733169",
        //         "transactTime" => "1678697308023",
        //         "price" => "1600",
        //         "origQty" => "0.02",
        //         "executedQty" => "0.02",
        //         "cummulativeQuoteQty" => "31.9284",
        //         "status" => "FILLED",
        //         "timeInForce" => "GTC",
        //         "type" => "LIMIT",
        //         "side" => "BUY",
        //         "stopPrice" => "0",
        //         "origQuoteOrderQty" => "0",
        //         "fills" => array(
        //             array(
        //                 "price" => "1596.42",
        //                 "qty" => "0.02",
        //                 "commission" => "0",
        //                 "commissionAsset" => "ETH",
        //                 "tradeId" => "1375407140281532417"
        //             }
        //         )
        //     ),
        //
        return $this->parse_order($response, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://coins-docs.github.io/rest-api/#query-order-user_data
         *
         * @param {int|string} $id order $id
         * @param {string} $symbol not used by coinsph fetchOrder ()
         * @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();
        $request = array();
        $clientOrderId = $this->safe_value_2($params, 'origClientOrderId', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['origClientOrderId'] = $clientOrderId;
        } else {
            $request['orderId'] = $id;
        }
        $params = $this->omit($params, array( 'clientOrderId', 'origClientOrderId' ));
        $response = $this->privateGetOpenapiV1Order ($this->extend($request, $params));
        return $this->parse_order($response);
    }

    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://coins-docs.github.io/rest-api/#current-open-orders-user_data
         *
         * @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~
         */
        $this->load_markets();
        $market = null;
        $request = array();
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $response = $this->privateGetOpenapiV1OpenOrders ($this->extend($request, $params));
        return $this->parse_orders($response, $market, $since, $limit);
    }

    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://coins-docs.github.io/rest-api/#history-orders-user_data
         *
         * @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 (default 500, max 1000)
         * @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 . ' fetchClosedOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['startTime'] = $since;
            // $since work properly only when it is "younger" than last 'limit' order
            $request['limit'] = 1000;
        } elseif ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenapiV1HistoryOrders ($this->extend($request, $params));
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://coins-docs.github.io/rest-api/#cancel-order-trade
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by coinsph 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();
        $request = array();
        $clientOrderId = $this->safe_value_2($params, 'origClientOrderId', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['origClientOrderId'] = $clientOrderId;
        } else {
            $request['orderId'] = $id;
        }
        $params = $this->omit($params, array( 'clientOrderId', 'origClientOrderId' ));
        $response = $this->privateDeleteOpenapiV1Order ($this->extend($request, $params));
        return $this->parse_order($response);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel open orders of $market
         *
         * @see https://coins-docs.github.io/rest-api/#cancel-all-open-orders-on-a-$symbol-trade
         *
         * @param {string} $symbol unified $market $symbol
         * @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~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = null;
        $request = array();
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $response = $this->privateDeleteOpenapiV1OpenOrders ($this->extend($request, $params));
        return $this->parse_orders($response, $market);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder POST /openapi/v1/order
        //     {
        //         "symbol" => "ETHUSDT",
        //         "orderId" => 1375445991893797391,
        //         "clientOrderId" => "1375445991893799115",
        //         "transactTime" => 1678701939513,
        //         "price" => "0",
        //         "origQty" => "0",
        //         "executedQty" => "0.001899",
        //         "cummulativeQuoteQty" => "2.99948949",
        //         "status" => "FILLED",
        //         "timeInForce" => "GTC",
        //         "type" => "MARKET",
        //         "side" => "BUY",
        //         "stopPrice" => "0",
        //         "origQuoteOrderQty" => "3",
        //         "fills" => array(
        //             {
        //                 "price" => "1579.51",
        //                 "qty" => "0.001899",
        //                 "commission" => "0",
        //                 "commissionAsset" => "ETH",
        //                 "tradeId":1375445992035598337
        //             }
        //         )
        //     }
        //
        // fetchOrder GET /openapi/v1/order
        // fetchOpenOrders GET /openapi/v1/openOrders
        // fetchClosedOrders GET /openapi/v1/historyOrders
        // cancelAllOrders DELETE /openapi/v1/openOrders
        //     {
        //         "symbol" => "DOGEPHP",
        //         "orderId":1375465375097982423,
        //         "clientOrderId" => "1375465375098001241",
        //         "price" => "0",
        //         "origQty" => "0",
        //         "executedQty" => "13",
        //         "cummulativeQuoteQty" => "49.621",
        //         "status" => "FILLED",
        //         "timeInForce" => "GTC",
        //         "type" => "MARKET",
        //         "side" => "BUY",
        //         "stopPrice" => "0",
        //         "time":1678704250171,
        //         "updateTime":1678704250256,
        //         "isWorking":false,
        //         "origQuoteOrderQty" => "50"
        //     }
        //
        // cancelOrder DELETE /openapi/v1/order
        //     {
        //         "symbol" => "ETHPHP",
        //         "orderId":1375609441915774332,
        //         "clientOrderId" => "1375609441915899557",
        //         "price" => "96000",
        //         "origQty" => "0.001",
        //         "executedQty" => "0",
        //         "cummulativeQuoteQty" => "0",
        //         "status" => "CANCELED",
        //         "timeInForce" => "GTC",
        //         "type" => "LIMIT",
        //         "side" => "SELL",
        //         "stopPrice" => "0",
        //         "origQuoteOrderQty" => "0"
        //     }
        //
        $id = $this->safe_string($order, 'orderId');
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer_2($order, 'time', 'transactTime');
        $trades = $this->safe_value($order, 'fills', null);
        $triggerPrice = $this->safe_string($order, 'stopPrice');
        if (Precise::string_eq($triggerPrice, '0')) {
            $triggerPrice = null;
        }
        return $this->safe_order(array(
            'id' => $id,
            'clientOrderId' => $this->safe_string($order, 'clientOrderId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'status' => $this->parse_order_status($this->safe_string($order, 'status')),
            'symbol' => $market['symbol'],
            'type' => $this->parse_order_type($this->safe_string($order, 'type')),
            'timeInForce' => $this->parse_order_time_in_force($this->safe_string($order, 'timeInForce')),
            'side' => $this->parse_order_side($this->safe_string($order, 'side')),
            'price' => $this->safe_string($order, 'price'),
            'triggerPrice' => $triggerPrice,
            'average' => null,
            'amount' => $this->safe_string($order, 'origQty'),
            'cost' => $this->safe_string($order, 'cummulativeQuoteQty'),
            'filled' => $this->safe_string($order, 'executedQty'),
            'remaining' => null,
            'fee' => null,
            'fees' => null,
            'trades' => $trades,
            'info' => $order,
        ), $market);
    }

    public function parse_order_side($status) {
        $statuses = array(
            'BUY' => 'buy',
            'SELL' => 'sell',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function encode_order_side($status) {
        $statuses = array(
            'buy' => 'BUY',
            'sell' => 'SELL',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($status) {
        $statuses = array(
            'MARKET' => 'market',
            'LIMIT' => 'limit',
            'LIMIT_MAKER' => 'limit',
            'STOP_LOSS' => 'market',
            'STOP_LOSS_LIMIT' => 'limit',
            'TAKE_PROFIT' => 'market',
            'TAKE_PROFIT_LIMIT' => 'limit',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function encode_order_type($status) {
        $statuses = array(
            'market' => 'MARKET',
            'limit' => 'LIMIT',
            'limit_maker' => 'LIMIT_MAKER',
            'stop_loss' => 'STOP_LOSS',
            'stop_loss_limit' => 'STOP_LOSS_LIMIT',
            'take_profit' => 'TAKE_PROFIT',
            'take_profit_limit' => 'TAKE_PROFIT_LIMIT',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'NEW' => 'open',
            'FILLED' => 'closed',
            'CANCELED' => 'canceled',
            'PARTIALLY_FILLED' => 'open',
            'PARTIALLY_CANCELED' => 'canceled',
            'REJECTED' => 'rejected',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_time_in_force($status) {
        $statuses = array(
            'GTC' => 'GTC',
            'FOK' => 'FOK',
            'IOC' => 'IOC',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): array {
        /**
         * fetch the trading fees for a $market
         *
         * @see https://coins-docs.github.io/rest-api/#trade-fee-user_data
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->privateGetOpenapiV1AssetTradeFee ($this->extend($request, $params));
        //
        //     array(
        //       {
        //         "symbol" => "ETHUSDT",
        //         "makerCommission" => "0.0025",
        //         "takerCommission" => "0.003"
        //       }
        //     )
        //
        $tradingFee = $this->safe_dict($response, 0, array());
        return $this->parse_trading_fee($tradingFee, $market);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading fees for multiple markets
         *
         * @see https://coins-docs.github.io/rest-api/#trade-$fee-user_data
         *
         * @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
         */
        $this->load_markets();
        $response = $this->privateGetOpenapiV1AssetTradeFee ($params);
        //
        //     array(
        //         array(
        //             "symbol" => "ETHPHP",
        //             "makerCommission" => "0.0025",
        //             "takerCommission" => "0.003"
        //         ),
        //         array(
        //             "symbol" => "UNIPHP",
        //             "makerCommission" => "0.0025",
        //             "takerCommission" => "0.003"
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $fee = $this->parse_trading_fee($response[$i]);
            $symbol = $fee['symbol'];
            $result[$symbol] = $fee;
        }
        return $result;
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "ETHUSDT",
        //         "makerCommission" => "0.0025",
        //         "takerCommission" => "0.003"
        //     }
        //
        $marketId = $this->safe_string($fee, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        return array(
            'info' => $fee,
            'symbol' => $symbol,
            'maker' => $this->safe_number($fee, 'makerCommission'),
            'taker' => $this->safe_number($fee, 'takerCommission'),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal to coins_ph account
         *
         * @see https://coins-docs.github.io/rest-api/#withdrawuser_data
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address not used by coinsph withdraw ()
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $options = $this->safe_value($this->options, 'withdraw');
        $warning = $this->safe_bool($options, 'warning', true);
        if ($warning) {
            throw new InvalidAddress($this->id . " withdraw() makes a withdrawals only to coins_ph account, add .options['withdraw']['warning'] = false to make a withdrawal to your coins_ph account");
        }
        $networkCode = $this->safe_string($params, 'network');
        $networkId = $this->network_code_to_id($networkCode, $code);
        if ($networkId === null) {
            throw new BadRequest($this->id . ' withdraw() require network parameter');
        }
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
            'amount' => $this->number_to_string($amount),
            'network' => $networkId,
            'address' => $address,
        );
        if ($tag !== null) {
            $request['withdrawOrderId'] = $tag;
        }
        $params = $this->omit($params, 'network');
        $response = $this->privatePostOpenapiWalletV1WithdrawApply ($this->extend($request, $params));
        return $this->parse_transaction($response, $currency);
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         *
         * @see https://coins-docs.github.io/rest-api/#deposit-history-user_data
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        // todo => returns an empty array - find out why
        $this->load_markets();
        $currency = null;
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenapiWalletV1DepositHistory ($this->extend($request, $params));
        //
        // array(
        //     array(
        //         "id" => "d_769800519366885376",
        //         "amount" => "0.001",
        //         "coin" => "BNB",
        //         "network" => "BNB",
        //         "status" => 0,
        //         "address" => "bnb136ns6lfw4zs5hg4n85vdthaad7hq5m4gtkgf23",
        //         "addressTag" => "101764890",
        //         "txId" => "98A3EA560C6B3336D348B6C83F0F95ECE4F1F5919E94BD006E5BF3BF264FACFC",
        //         "insertTime" => 1661493146000,
        //         "confirmNo" => 10,
        //     ),
        //     {
        //         "id" => "d_769754833590042625",
        //         "amount":"0.5",
        //         "coin":"IOTA",
        //         "network":"IOTA",
        //         "status":1,
        //         "address":"SIZ9VLMHWATXKV99LH99CIGFJFUMLEHGWVZVNNZXRJJVWBPHYWPPBOSDORZ9EQSHCZAMPVAPGFYQAUUV9DROOXJLNW",
        //         "addressTag":"",
        //         "txId":"ESBFVQUTPIWQNJSPXFNHNYHSQNTGKRVKPRABQWTAXCDWOAKDKYWPTVG9BGXNVNKTLEJGESAVXIKIZ9999",
        //         "insertTime":1599620082000,
        //         "confirmNo" => 20,
        //     }
        // )
        //
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://coins-docs.github.io/rest-api/#withdraw-history-user_data
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch withdrawals for
         * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        // todo => returns an empty array - find out why
        $this->load_markets();
        $currency = null;
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenapiWalletV1WithdrawHistory ($this->extend($request, $params));
        //
        // array(
        //     array(
        //         "id" => "459890698271244288",
        //         "amount" => "0.01",
        //         "transactionFee" => "0",
        //         "coin" => "ETH",
        //         "status" => 1,
        //         "address" => "0x386AE30AE2dA293987B5d51ddD03AEb70b21001F",
        //         "addressTag" => "",
        //         "txId" => "0x4ae2fed36a90aada978fc31c38488e8b60d7435cfe0b4daed842456b4771fcf7",
        //         "applyTime" => 1673601139000,
        //         "network" => "ETH",
        //         "withdrawOrderId" => "thomas123",
        //         "info" => "",
        //         "confirmNo" => 100
        //     ),
        //     {
        //         "id" => "451899190746456064",
        //         "amount" => "0.00063",
        //         "transactionFee" => "0.00037",
        //         "coin" => "ETH",
        //         "status" => 1,
        //         "address" => "0x386AE30AE2dA293987B5d51ddD03AEb70b21001F",
        //         "addressTag" => "",
        //         "txId" => "0x62690ca4f9d6a8868c258e2ce613805af614d9354dda7b39779c57b2e4da0260",
        //         "applyTime" => 1671695815000,
        //         "network" => "ETH",
        //         "withdrawOrderId" => "",
        //         "info" => "",
        //         "confirmNo" => 100
        //     }
        // )
        //
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fetchDeposits
        //     {
        //         "coin" => "PHP",
        //         "address" => "Internal Transfer",
        //         "addressTag" => "Internal Transfer",
        //         "amount" => "0.02",
        //         "id" => "31312321312312312312322",
        //         "network" => "Internal",
        //         "transferType" => "0",
        //         "status" => 3,
        //         "confirmTimes" => "",
        //         "unlockConfirm" => "",
        //         "txId" => "Internal Transfer",
        //         "insertTime" => 1657623798000,
        //         "depositOrderId" => "the deposit $id which created by client"
        //     }
        //
        // fetchWithdrawals
        //     {
        //         "coin" => "BTC",
        //         "address" => "Internal Transfer",
        //         "amount" => "0.1",
        //         "id" => "1201515362324421632",
        //         "withdrawOrderId" => null,
        //         "network" => "Internal",
        //         "transferType" => "0",
        //         "status" => 0,
        //         "transactionFee" => "0",
        //         "confirmNo" => 0,
        //         "info" => "array()",
        //         "txId" => "Internal Transfer",
        //         "applyTime" => 1657967792000
        //     }
        //
        // todo => this is in progress
        $id = $this->safe_string($transaction, 'id');
        $address = $this->safe_string($transaction, 'address');
        $tag = $this->safe_string($transaction, 'addressTag');
        if ($tag !== null) {
            if (strlen($tag) < 1) {
                $tag = null;
            }
        }
        $txid = $this->safe_string($transaction, 'txId');
        $currencyId = $this->safe_string($transaction, 'coin');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = null;
        $timestamp = $this->safe_integer_2($transaction, 'insertTime', 'applyTime');
        $updated = null;
        $type = null;
        $withdrawOrderId = $this->safe_string($transaction, 'withdrawOrderId');
        $depositOrderId = $this->safe_string($transaction, 'depositOrderId');
        if ($withdrawOrderId !== null) {
            $type = 'withdrawal';
        } elseif ($depositOrderId !== null) {
            $type = 'deposit';
        }
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $amount = $this->safe_number($transaction, 'amount');
        $feeCost = $this->safe_number($transaction, 'transactionFee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array( 'currency' => $code, 'cost' => $feeCost );
        }
        $network = $this->safe_string($transaction, 'network');
        $internal = $network === 'Internal';
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => $network,
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'internal' => $internal,
            'comment' => null,
            'fee' => $fee,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            '0' => 'pending',
            '1' => 'ok',
            '2' => 'failed',
            '3' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account
         *
         * @see https://coins-docs.github.io/rest-api/#deposit-address-user_data
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network] network for fetch deposit address
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $networkCode = $this->safe_string($params, 'network');
        $networkId = $this->network_code_to_id($networkCode, $code);
        if ($networkId === null) {
            throw new BadRequest($this->id . ' fetchDepositAddress() require network parameter');
        }
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
            'network' => $networkId,
        );
        $params = $this->omit($params, 'network');
        $response = $this->privateGetOpenapiWalletV1DepositAddress ($this->extend($request, $params));
        //
        //     {
        //         "coin" => "ETH",
        //         "address" => "0xfe98628173830bf79c59f04585ce41f7de168784",
        //         "addressTag" => ""
        //     }
        //
        return $this->parse_deposit_address($response, $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //     {
        //         "coin" => "ETH",
        //         "address" => "0xfe98628173830bf79c59f04585ce41f7de168784",
        //         "addressTag" => ""
        //     }
        //
        $currencyId = $this->safe_string($depositAddress, 'coin');
        $parsedCurrency = $this->safe_currency_code($currencyId, $currency);
        return array(
            'info' => $depositAddress,
            'currency' => $parsedCurrency,
            'network' => null,
            'address' => $this->safe_string($depositAddress, 'address'),
            'tag' => $this->safe_string($depositAddress, 'addressTag'),
        );
    }

    public function url_encode_query($query = array ()) {
        $encodedArrayParams = '';
        $keys = is_array($query) ? array_keys($query) : array();
        for ($i = 0; $i < count($keys); $i++) {
            $key = $keys[$i];
            if (gettype($query[$key]) === 'array' && array_keys($query[$key]) === array_keys(array_keys($query[$key]))) {
                if ($i !== 0) {
                    $encodedArrayParams .= '&';
                }
                $innerArray = $query[$key];
                $query = $this->omit($query, $key);
                $encodedArrayParam = $this->parse_array_param($innerArray, $key);
                $encodedArrayParams .= $encodedArrayParam;
            }
        }
        $encodedQuery = $this->urlencode($query);
        if (strlen($encodedQuery) !== 0) {
            return $encodedQuery . '&' . $encodedArrayParams;
        } else {
            return $encodedArrayParams;
        }
    }

    public function parse_array_param($array, $key) {
        $stringifiedArray = $this->json($array);
        $stringifiedArray = str_replace('[', '%5B', $stringifiedArray);
        $stringifiedArray = str_replace(']', '%5D', $stringifiedArray);
        $urlEncodedParam = $key . '=' . $stringifiedArray;
        return $urlEncodedParam;
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api'][$api];
        $query = $this->omit($params, $this->extract_params($path));
        $endpoint = $this->implode_params($path, $params);
        $url = $url . '/' . $endpoint;
        if ($api === 'private') {
            $this->check_required_credentials();
            $query['timestamp'] = $this->milliseconds();
            $recvWindow = $this->safe_integer($query, 'recvWindow');
            if ($recvWindow === null) {
                $defaultRecvWindow = $this->safe_integer($this->options, 'recvWindow');
                if ($defaultRecvWindow !== null) {
                    $query['recvWindow'] = $defaultRecvWindow;
                }
            }
            $query = $this->url_encode_query($query);
            $signature = $this->hmac($this->encode($query), $this->encode($this->secret), 'sha256');
            $url = $url . '?' . $query . '&$signature=' . $signature;
            $headers = array(
                'X-COINS-APIKEY' => $this->apiKey,
            );
        } else {
            $query = $this->url_encode_query($query);
            if (strlen($query) !== 0) {
                $url .= '?' . $query;
            }
        }
        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) {
        if ($response === null) {
            return null;
        }
        $responseCode = $this->safe_string($response, 'code', null);
        if (($responseCode !== null) && ($responseCode !== '200') && ($responseCode !== '0')) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $responseCode, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
