# -*- coding: utf-8 -*-

# 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

from ccxt.async_support.base.exchange import Exchange
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import DDoSProtection


class okcoinusd (Exchange):

    def describe(self):
        return self.deep_extend(super(okcoinusd, self).describe(), {
            'id': 'okcoinusd',
            'name': 'OKCoin USD',
            'countries': ['CN', 'US'],
            'version': 'v1',
            'rateLimit': 1000,  # up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms
            'has': {
                'CORS': False,
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrders': False,
                'fetchOpenOrders': True,
                'fetchClosedOrders': True,
                'fetchTickers': True,
                'withdraw': True,
                'futures': False,
            },
            'extension': '.do',  # appended to endpoint URL
            'timeframes': {
                '1m': '1min',
                '3m': '3min',
                '5m': '5min',
                '15m': '15min',
                '30m': '30min',
                '1h': '1hour',
                '2h': '2hour',
                '4h': '4hour',
                '6h': '6hour',
                '12h': '12hour',
                '1d': '1day',
                '3d': '3day',
                '1w': '1week',
            },
            'api': {
                'web': {
                    'get': [
                        'futures/pc/market/marketOverview',
                        'spot/markets/index-tickers',
                        'spot/markets/currencies',
                        'spot/markets/products',
                        'spot/markets/tickers',
                        'spot/user-level',
                    ],
                    'post': [
                        'futures/pc/market/futuresCoin',
                    ],
                },
                'public': {
                    'get': [
                        'depth',
                        'exchange_rate',
                        'future_depth',
                        'future_estimated_price',
                        'future_hold_amount',
                        'future_index',
                        'future_kline',
                        'future_price_limit',
                        'future_ticker',
                        'future_trades',
                        'kline',
                        'otcs',
                        'ticker',
                        'tickers',
                        'trades',
                    ],
                },
                'private': {
                    'post': [
                        'account_records',
                        'batch_trade',
                        'borrow_money',
                        'borrow_order_info',
                        'borrows_info',
                        'cancel_borrow',
                        'cancel_order',
                        'cancel_otc_order',
                        'cancel_withdraw',
                        'funds_transfer',
                        'future_batch_trade',
                        'future_cancel',
                        'future_devolve',
                        'future_explosive',
                        'future_order_info',
                        'future_orders_info',
                        'future_position',
                        'future_position_4fix',
                        'future_trade',
                        'future_trades_history',
                        'future_userinfo',
                        'future_userinfo_4fix',
                        'lend_depth',
                        'order_fee',
                        'order_history',
                        'order_info',
                        'orders_info',
                        'otc_order_history',
                        'otc_order_info',
                        'repayment',
                        'submit_otc_order',
                        'trade',
                        'trade_history',
                        'trade_otc_order',
                        'wallet_info',
                        'withdraw',
                        'withdraw_info',
                        'unrepayments_info',
                        'userinfo',
                    ],
                },
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766791-89ffb502-5ee5-11e7-8a5b-c5950b68ac65.jpg',
                'api': {
                    'web': 'https://www.okcoin.com/v2',
                    'public': 'https://www.okcoin.com/api',
                    'private': 'https://www.okcoin.com',
                },
                'www': 'https://www.okcoin.com',
                'doc': [
                    'https://www.okcoin.com/docs/en/',
                    'https://www.npmjs.com/package/okcoin.com',
                ],
                'referral': 'https://www.okcoin.com/account/register?flag=activity&channelId=600001513',
            },
            # these are okcoin.com fees, okex fees are in okex.js
            'fees': {
                'trading': {
                    'taker': 0.001,
                    'maker': 0.0005,
                },
            },
            'exceptions': {
                # see https://github.com/okcoin-okex/API-docs-OKEx.com/blob/master/API-For-Spot-EN/Error%20Code%20For%20Spot.md
                '10000': ExchangeError,  # "Required field, can not be null"
                '10001': DDoSProtection,  # "Request frequency too high to exceed the limit allowed"
                '10005': AuthenticationError,  # "'SecretKey' does not exist"
                '10006': AuthenticationError,  # "'Api_key' does not exist"
                '10007': AuthenticationError,  # "Signature does not match"
                '1002': InsufficientFunds,  # "The transaction amount exceed the balance"
                '1003': InvalidOrder,  # "The transaction amount is less than the minimum requirement"
                '1004': InvalidOrder,  # "The transaction amount is less than 0"
                '1013': InvalidOrder,  # no contract type(PR-1101)
                '1027': InvalidOrder,  # createLimitBuyOrder(symbol, 0, 0): Incorrect parameter may exceeded limits
                '1050': InvalidOrder,  # returned when trying to cancel an order that was filled or canceled previously
                '1217': InvalidOrder,  # "Order was sent at ±5% of the current market price. Please resend"
                '10014': InvalidOrder,  # "Order price must be between 0 and 1,000,000"
                '1009': OrderNotFound,  # for spot markets, cancelling closed order
                '1019': OrderNotFound,  # order closed?("Undo order failed")
                '1051': OrderNotFound,  # for spot markets, cancelling "just closed" order
                '10009': OrderNotFound,  # for spot markets, "Order does not exist"
                '20015': OrderNotFound,  # for future markets
                '10008': BadRequest,  # Illegal URL parameter
                # todo: sort out below
                # 10000 Required parameter is empty
                # 10001 Request frequency too high to exceed the limit allowed
                # 10002 Authentication failure
                # 10002 System error
                # 10003 This connection has requested other user data
                # 10004 Request failed
                # 10005 api_key or sign is invalid, 'SecretKey' does not exist
                # 10006 'Api_key' does not exist
                # 10007 Signature does not match
                # 10008 Illegal parameter, Parameter erorr
                # 10009 Order does not exist
                # 10010 Insufficient funds
                # 10011 Amount too low
                # 10012 Only btc_usd ltc_usd supported
                # 10013 Only support https request
                # 10014 Order price must be between 0 and 1,000,000
                # 10015 Order price differs from current market price too much / Channel subscription temporally not available
                # 10016 Insufficient coins balance
                # 10017 API authorization error / WebSocket authorization error
                # 10018 borrow amount less than lower limit [usd:100,btc:0.1,ltc:1]
                # 10019 loan agreement not checked
                # 1002 The transaction amount exceed the balance
                # 10020 rate cannot exceed 1%
                # 10021 rate cannot less than 0.01%
                # 10023 fail to get latest ticker
                # 10024 balance not sufficient
                # 10025 quota is full, cannot borrow temporarily
                # 10026 Loan(including reserved loan) and margin cannot be withdrawn
                # 10027 Cannot withdraw within 24 hrs of authentication information modification
                # 10028 Withdrawal amount exceeds daily limit
                # 10029 Account has unpaid loan, please cancel/pay off the loan before withdraw
                # 1003 The transaction amount is less than the minimum requirement
                # 10031 Deposits can only be withdrawn after 6 confirmations
                # 10032 Please enabled phone/google authenticator
                # 10033 Fee higher than maximum network transaction fee
                # 10034 Fee lower than minimum network transaction fee
                # 10035 Insufficient BTC/LTC
                # 10036 Withdrawal amount too low
                # 10037 Trade password not set
                # 1004 The transaction amount is less than 0
                # 10040 Withdrawal cancellation fails
                # 10041 Withdrawal address not exsit or approved
                # 10042 Admin password error
                # 10043 Account equity error, withdrawal failure
                # 10044 fail to cancel borrowing order
                # 10047 self function is disabled for sub-account
                # 10048 withdrawal information does not exist
                # 10049 User can not have more than 50 unfilled small orders(amount<0.15BTC)
                # 10050 can't cancel more than once
                # 10051 order completed transaction
                # 10052 not allowed to withdraw
                # 10064 after a USD deposit, that portion of assets will not be withdrawable for the next 48 hours
                # 1007 No trading market information
                # 1008 No latest market information
                # 1009 No order
                # 1010 Different user of the cancelled order and the original order
                # 10100 User account frozen
                # 10101 order type is wrong
                # 10102 incorrect ID
                # 10103 the private otc order's key incorrect
                # 10106 API key domain not matched
                # 1011 No documented user
                # 1013 No order type
                # 1014 No login
                # 1015 No market depth information
                # 1017 Date error
                # 1018 Order failed
                # 1019 Undo order failed
                # 10216 Non-available API / non-public API
                # 1024 Currency does not exist
                # 1025 No chart type
                # 1026 No base currency quantity
                # 1027 Incorrect parameter may exceeded limits
                # 1028 Reserved decimal failed
                # 1029 Preparing
                # 1030 Account has margin and futures, transactions can not be processed
                # 1031 Insufficient Transferring Balance
                # 1032 Transferring Not Allowed
                # 1035 Password incorrect
                # 1036 Google Verification code Invalid
                # 1037 Google Verification code incorrect
                # 1038 Google Verification replicated
                # 1039 Message Verification Input exceed the limit
                # 1040 Message Verification invalid
                # 1041 Message Verification incorrect
                # 1042 Wrong Google Verification Input exceed the limit
                # 1043 Login password cannot be same as the trading password
                # 1044 Old password incorrect
                # 1045 2nd Verification Needed
                # 1046 Please input old password
                # 1048 Account Blocked
                # 1050 Orders have been withdrawn or withdrawn
                # 1051 Order completed
                # 1201 Account Deleted at 00: 00
                # 1202 Account Not Exist
                # 1203 Insufficient Balance
                # 1204 Invalid currency
                # 1205 Invalid Account
                # 1206 Cash Withdrawal Blocked
                # 1207 Transfer Not Support
                # 1208 No designated account
                # 1209 Invalid api
                # 1216 Market order temporarily suspended. Please send limit order
                # 1217 Order was sent at ±5% of the current market price. Please resend
                # 1218 Place order failed. Please try again later
                # 20001 User does not exist
                # 20002 Account frozen
                # 20003 Account frozen due to forced liquidation
                # 20004 Contract account frozen
                # 20005 User contract account does not exist
                # 20006 Required field missing
                # 20007 Illegal parameter
                # 20008 Contract account balance is too low
                # 20009 Contract status error
                # 20010 Risk rate ratio does not exist
                # 20011 Risk rate lower than 90%/80% before opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% before opening LTC position with 10x/20x leverage
                # 20012 Risk rate lower than 90%/80% after opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% after opening LTC position with 10x/20x leverage
                # 20013 Temporally no counter party price
                # 20014 System error
                # 20015 Order does not exist
                # 20016 Close amount bigger than your open positions, liquidation quantity bigger than holding
                # 20017 Not authorized/illegal operation/illegal order ID
                # 20018 Order price cannot be more than 103-105% or less than 95-97% of the previous minute price
                # 20019 IP restricted from accessing the resource
                # 20020 Secret key does not exist
                # 20021 Index information does not exist
                # 20022 Wrong API interface(Cross margin mode shall call cross margin API, fixed margin mode shall call fixed margin API)
                # 20023 Account in fixed-margin mode
                # 20024 Signature does not match
                # 20025 Leverage rate error
                # 20026 API Permission Error
                # 20027 no transaction record
                # 20028 no such contract
                # 20029 Amount is large than available funds
                # 20030 Account still has debts
                # 20038 Due to regulation, self function is not availavle in the country/region your currently reside in.
                # 20049 Request frequency too high
                # 20100 request time out
                # 20101 the format of data is error
                # 20102 invalid login
                # 20103 event type error
                # 20104 subscription type error
                # 20107 JSON format error
                # 20115 The quote is not match
                # 20116 Param not match
                # 21020 Contracts are being delivered, orders cannot be placed
                # 21021 Contracts are being settled, contracts cannot be placed
            },
            'options': {
                'marketBuyPrice': False,
                'fetchOHLCVWarning': True,
                'contractTypes': {
                    '1': 'this_week',
                    '2': 'next_week',
                    '4': 'quarter',
                },
                'fetchTickersMethod': 'fetch_tickers_from_api',
            },
        })

    async def fetch_markets(self, params={}):
        # TODO: they have a new fee schedule as of Feb 7
        # the new fees are progressive and depend on 30-day traded volume
        # the following is the worst case
        result = []
        spotResponse = await self.webGetSpotMarketsProducts()
        #
        #     {
        #         "code": 0,
        #         "data": [
        #             {
        #                 "baseCurrency":0,
        #                 "brokerId":0,
        #                 "callAuctionOrCallNoCancelAuction":false,
        #                 "callNoCancelSwitchTime":{},
        #                 "collect":"0",
        #                 "continuousSwitchTime":{},
        #                 "groupId":1,
        #                 "isMarginOpen":true,
        #                 "listDisplay":0,
        #                 "marginRiskPreRatio":1.2,
        #                 "marginRiskRatio":1.1,
        #                 "marketFrom":118,
        #                 "maxMarginLeverage":5,
        #                 "maxPriceDigit":1,
        #                 "maxSizeDigit":8,
        #                 "mergeTypes":"0.1,1,10",
        #                 "minTradeSize":0.00100000,
        #                 "online":1,
        #                 "productId":20,
        #                 "quoteCurrency":7,
        #                 "quoteIncrement":"0.1",
        #                 "quotePrecision":2,
        #                 "sort":30038,
        #                 "symbol":"btc_usdt",
        #                 "tradingMode":3
        #             },
        #         ]
        #     }
        #
        spotMarkets = self.safe_value(spotResponse, 'data', [])
        markets = spotMarkets
        if self.has['futures']:
            futuresResponse = await self.webPostFuturesPcMarketFuturesCoin()
            #
            #     {
            #         "msg":"success",
            #         "code":0,
            #         "detailMsg":"",
            #         "data": [
            #             {
            #                 "symbolId":0,
            #                 "symbol":"f_usd_btc",
            #                 "iceSingleAvgMinAmount":2,
            #                 "minTradeSize":1,
            #                 "iceSingleAvgMaxAmount":500,
            #                 "contractDepthLevel":["0.01","0.2"],
            #                 "dealAllMaxAmount":999,
            #                 "maxSizeDigit":4,
            #                 "contracts":[
            #                     {"marketFrom":34, "id":201905240000034, "type":1, "desc":"BTC0524"},
            #                     {"marketFrom":13, "id":201905310000013, "type":2, "desc":"BTC0531"},
            #                     {"marketFrom":12, "id":201906280000012, "type":4, "desc":"BTC0628"},
            #                 ],
            #                 "maxPriceDigit":2,
            #                 "nativeRate":1,
            #                 "quote":"usd",
            #                 "nativeCurrency":"usd",
            #                 "nativeCurrencyMark":"$",
            #                 "contractSymbol":0,
            #                 "unitAmount":100.00,
            #                 "symbolMark":"฿",
            #                 "symbolDesc":"BTC"
            #             },
            #         ]
            #     }
            #
            futuresMarkets = self.safe_value(futuresResponse, 'data', [])
            markets = self.array_concat(spotMarkets, futuresMarkets)
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'symbol')
            symbol = None
            base = None
            quote = None
            baseId = None
            quoteId = None
            baseNumericId = None
            quoteNumericId = None
            lowercaseId = None
            uppercaseBaseId = None
            precision = {
                'amount': self.safe_integer(market, 'maxSizeDigit'),
                'price': self.safe_integer(market, 'maxPriceDigit'),
            }
            minAmount = self.safe_float(market, 'minTradeSize')
            minPrice = math.pow(10, -precision['price'])
            contracts = self.safe_value(market, 'contracts')
            if contracts is None:
                # spot markets
                lowercaseId = id
                parts = id.split('_')
                baseId = parts[0]
                quoteId = parts[1]
                baseNumericId = self.safe_integer(market, 'baseCurrency')
                quoteNumericId = self.safe_integer(market, 'quoteCurrency')
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                contracts = [{}]
            else:
                # futures markets
                quoteId = self.safe_string(market, 'quote')
                uppercaseBaseId = self.safe_string(market, 'symbolDesc')
                baseId = uppercaseBaseId.lower()
                lowercaseId = baseId + '_' + quoteId
                base = self.safe_currency_code(uppercaseBaseId)
                quote = self.safe_currency_code(quoteId)
            for k in range(0, len(contracts)):
                contract = contracts[k]
                type = self.safe_string(contract, 'type', 'spot')
                contractType = None
                spot = True
                future = False
                active = True
                if type == 'spot':
                    symbol = base + '/' + quote
                    active = market['online'] != 0
                else:
                    contractId = self.safe_string(contract, 'id')
                    symbol = base + '-' + quote + '-' + contractId[2:8]
                    contractType = self.safe_string(self.options['contractTypes'], type)
                    type = 'future'
                    spot = False
                    future = True
                fees = self.safe_value_2(self.fees, type, 'trading', {})
                result.append(self.extend(fees, {
                    'id': id,
                    'lowercaseId': lowercaseId,
                    'contractType': contractType,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'baseNumericId': baseNumericId,
                    'quoteNumericId': quoteNumericId,
                    'info': market,
                    'type': type,
                    'spot': spot,
                    'future': future,
                    'active': active,
                    'precision': precision,
                    'limits': {
                        'amount': {
                            'min': minAmount,
                            'max': None,
                        },
                        'price': {
                            'min': minPrice,
                            'max': None,
                        },
                        'cost': {
                            'min': minAmount * minPrice,
                            'max': None,
                        },
                    },
                }))
        return result

    def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
        market = self.markets[symbol]
        key = 'quote'
        rate = market[takerOrMaker]
        cost = float(self.cost_to_precision(symbol, amount * rate))
        if side == 'sell':
            cost *= price
        else:
            key = 'base'
        return {
            'type': takerOrMaker,
            'currency': market[key],
            'rate': rate,
            'cost': float(self.fee_to_precision(symbol, cost)),
        }

    async def fetch_tickers_from_api(self, symbols=None, params={}):
        await self.load_markets()
        request = {}
        response = await self.publicGetTickers(self.extend(request, params))
        tickers = response['tickers']
        timestamp = self.safe_timestamp(response, 'date')
        result = {}
        for i in range(0, len(tickers)):
            ticker = tickers[i]
            ticker = self.parse_ticker(self.extend(tickers[i], {'timestamp': timestamp}))
            symbol = ticker['symbol']
            result[symbol] = ticker
        return result

    async def fetch_tickers_from_web(self, symbols=None, params={}):
        await self.load_markets()
        request = {}
        response = await self.webGetSpotMarketsTickers(self.extend(request, params))
        tickers = self.safe_value(response, 'data')
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return result

    async def fetch_tickers(self, symbols=None, params={}):
        method = self.options['fetchTickersMethod']
        return await getattr(self, method)(symbols, params)

    async def fetch_order_book(self, symbol=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        method = 'publicGetFutureDepth' if market['future'] else 'publicGetDepth'
        request = self.create_request(market, params)
        if limit is not None:
            request['size'] = limit
        response = await getattr(self, method)(request)
        return self.parse_order_book(response)

    def parse_ticker(self, ticker, market=None):
        #
        #     {             buy:   "48.777300",
        #                 change:   "-1.244500",
        #       changePercentage:   "-2.47%",
        #                  close:   "49.064000",
        #            createdDate:    1531704852254,
        #             currencyId:    527,
        #                dayHigh:   "51.012500",
        #                 dayLow:   "48.124200",
        #                   high:   "51.012500",
        #                inflows:   "0",
        #                   last:   "49.064000",
        #                    low:   "48.124200",
        #             marketFrom:    627,
        #                   name: {},
        #                   open:   "50.308500",
        #               outflows:   "0",
        #              productId:    527,
        #                   sell:   "49.064000",
        #                 symbol:   "zec_okb",
        #                 volume:   "1049.092535"   }
        #
        timestamp = self.safe_integer_2(ticker, 'timestamp', 'createdDate')
        symbol = None
        if market is None:
            if 'symbol' in ticker:
                marketId = ticker['symbol']
                if marketId in self.markets_by_id:
                    market = self.markets_by_id[marketId]
                else:
                    baseId, quoteId = ticker['symbol'].split('_')
                    base = self.safe_currency_code(baseId)
                    quote = self.safe_currency_code(quoteId)
                    symbol = base + '/' + quote
        if market is not None:
            symbol = market['symbol']
        last = self.safe_float(ticker, 'last')
        open = self.safe_float(ticker, 'open')
        change = self.safe_float(ticker, 'change')
        percentage = self.safe_float(ticker, 'changePercentage')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'buy'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'sell'),
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float_2(ticker, 'vol', 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        method = 'publicGetFutureTicker' if market['future'] else 'publicGetTicker'
        request = self.create_request(market, params)
        response = await getattr(self, method)(request)
        ticker = self.safe_value(response, 'ticker')
        if ticker is None:
            raise ExchangeError(self.id + ' fetchTicker returned an empty response: ' + self.json(response))
        timestamp = self.safe_timestamp(response, 'date')
        if timestamp is not None:
            ticker = self.extend(ticker, {'timestamp': timestamp})
        return self.parse_ticker(ticker, market)

    def parse_trade(self, trade, market=None):
        symbol = None
        if market:
            symbol = market['symbol']
        timestamp = self.safe_integer(trade, 'date_ms')
        id = self.safe_string(trade, 'tid')
        type = None
        side = self.safe_string(trade, 'type')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = None
        if price is not None:
            if amount is not None:
                cost = price * amount
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': None,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': None,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        method = 'publicGetFutureTrades' if market['future'] else 'publicGetTrades'
        request = self.create_request(market, params)
        response = await getattr(self, method)(request)
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        numElements = len(ohlcv)
        volumeIndex = 6 if (numElements > 6) else 5
        return [
            ohlcv[0],  # timestamp
            float(ohlcv[1]),  # Open
            float(ohlcv[2]),  # High
            float(ohlcv[3]),  # Low
            float(ohlcv[4]),  # Close
            # float(ohlcv[5]),  # quote volume
            # float(ohlcv[6]),  # base volume
            float(ohlcv[volumeIndex]),  # okex will return base volume in the 7th element for future markets
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        method = 'publicGetFutureKline' if market['future'] else 'publicGetKline'
        request = self.create_request(market, {
            'type': self.timeframes[timeframe],
            # 'since': since is self.milliseconds() - 86400000 if None else since,  # default last 24h
        })
        if since is not None:
            request['since'] = int((self.milliseconds() - 86400000) / 1000)  # default last 24h
        if limit is not None:
            if self.options['fetchOHLCVWarning']:
                raise ExchangeError(self.id + ' fetchOHLCV counts "limit" candles backwards in chronological ascending order, therefore the "limit" argument for ' + self.id + ' is disabled. Set ' + self.id + '.options["fetchOHLCVWarning"] = False to suppress self warning message.')
            request['size'] = int(limit)  # max is 1440 candles
        response = await getattr(self, method)(self.extend(request, params))
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privatePostUserinfo(params)
        info = self.safe_value(response, 'info', {})
        balances = self.safe_value(info, 'funds', {})
        result = {'info': response}
        ids = list(balances['free'].keys())
        usedField = 'freezed'
        # wtf, okex?
        # https://github.com/okcoin-okex/API-docs-OKEx.com/commit/01cf9dd57b1f984a8737ef76a037d4d3795d2ac7
        if not(usedField in list(balances.keys())):
            usedField = 'holds'
        usedKeys = list(balances[usedField].keys())
        ids = self.array_concat(ids, usedKeys)
        for i in range(0, len(ids)):
            id = ids[i]
            code = self.safe_currency_code(id)
            account = self.account()
            account['free'] = self.safe_float(balances['free'], id)
            account['used'] = self.safe_float(balances[usedField], id)
            result[code] = account
        return self.parse_balance(result)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        method = 'privatePostFutureTrade' if market['future'] else 'privatePostTrade'
        orderSide = (side + '_market') if (type == 'market') else side
        isMarketBuy = ((market['spot']) and(type == 'market') and(side == 'buy') and(not self.options['marketBuyPrice']))
        orderPrice = self.safe_float(params, 'cost') if isMarketBuy else price
        request = self.create_request(market, {
            'type': orderSide,
        })
        if market['future']:
            request['match_price'] = 1 if (type == 'market') else 0  # match best counter party price? 0 or 1, ignores price if 1
            request['lever_rate'] = 10  # leverage rate value: 10 or 20(10 by default)
            request['type'] = '1' if (side == 'buy') else '2'
        elif type == 'market':
            if side == 'buy':
                if not orderPrice:
                    if self.options['marketBuyPrice']:
                        # eslint-disable-next-line quotes
                        raise ExchangeError(self.id + " market buy orders require a price argument(the amount you want to spend or the cost of the order) when self.options['marketBuyPrice'] is True.")
                    else:
                        # eslint-disable-next-line quotes
                        raise ExchangeError(self.id + " market buy orders require an additional cost parameter, cost = price * amount. If you want to pass the cost of the market order(the amount you want to spend) in the price argument(the default " + self.id + " behaviour), set self.options['marketBuyPrice'] = True. It will effectively suppress self warning exception as well.")
                else:
                    request['price'] = orderPrice
            else:
                request['amount'] = amount
        if type != 'market':
            request['price'] = orderPrice
            request['amount'] = amount
        params = self.omit(params, 'cost')
        response = await getattr(self, method)(self.extend(request, params))
        timestamp = self.milliseconds()
        return {
            'info': response,
            'id': self.safe_string(response, 'order_id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': None,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'filled': None,
            'remaining': None,
            'cost': None,
            'trades': None,
            'fee': None,
        }

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        method = 'privatePostFutureCancel' if market['future'] else 'privatePostCancelOrder'
        request = self.create_request(market, {
            'order_id': id,
        })
        response = await getattr(self, method)(self.extend(request, params))
        return response

    def parse_order_status(self, status):
        statuses = {
            '-1': 'canceled',
            '0': 'open',
            '1': 'open',
            '2': 'closed',
            '3': 'open',
            '4': 'canceled',
        }
        return self.safe_value(statuses, status, status)

    def parse_order_side(self, side):
        if side == 1:
            return 'buy'  # open long position
        elif side == 2:
            return 'sell'  # open short position
        elif side == 3:
            return 'sell'  # liquidate long position
        elif side == 4:
            return 'buy'  # liquidate short position
        return side

    def parse_order(self, order, market=None):
        side = None
        type = None
        if 'type' in order:
            if (order['type'] == 'buy') or (order['type'] == 'sell'):
                side = order['type']
                type = 'limit'
            elif order['type'] == 'buy_market':
                side = 'buy'
                type = 'market'
            elif order['type'] == 'sell_market':
                side = 'sell'
                type = 'market'
            else:
                side = self.parse_order_side(order['type'])
                if ('contract_name' in list(order.keys())) or ('lever_rate' in list(order.keys())):
                    type = 'margin'
        status = self.parse_order_status(self.safe_string(order, 'status'))
        symbol = None
        if market is None:
            marketId = self.safe_string(order, 'symbol')
            if marketId in self.markets_by_id:
                market = self.markets_by_id[marketId]
        if market:
            symbol = market['symbol']
        createDateField = self.get_create_date_field()
        timestamp = self.safe_integer(order, createDateField)
        amount = self.safe_float(order, 'amount')
        filled = self.safe_float(order, 'deal_amount')
        amount = max(amount, filled)
        remaining = max(0, amount - filled)
        if type == 'market':
            remaining = 0
        average = self.safe_float(order, 'avg_price')
        # https://github.com/ccxt/ccxt/issues/2452
        average = self.safe_float(order, 'price_avg', average)
        cost = average * filled
        return {
            'info': order,
            'id': self.safe_string(order, 'order_id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': self.safe_float(order, 'price'),
            'average': average,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
        }

    def get_create_date_field(self):
        # needed for derived exchanges
        # allcoin typo create_data instead of create_date
        return 'create_date'

    def get_orders_field(self):
        # needed for derived exchanges
        # allcoin typo order instead of orders(expected based on their API docs)
        return 'orders'

    async def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ExchangeError(self.id + ' fetchOrder requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        method = 'privatePostFutureOrderInfo' if market['future'] else 'privatePostOrderInfo'
        request = self.create_request(market, {
            'order_id': id,
            # 'status': 0,  # 0 for unfilled orders, 1 for filled orders
            # 'current_page': 1,  # current page number
            # 'page_length': 200,  # number of orders returned per page, maximum 200
        })
        response = await getattr(self, method)(self.extend(request, params))
        ordersField = self.get_orders_field()
        numOrders = len(response[ordersField])
        if numOrders > 0:
            return self.parse_order(response[ordersField][0])
        raise OrderNotFound(self.id + ' order ' + id + ' not found')

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ExchangeError(self.id + ' fetchOrders requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        method = 'privatePostFutureOrdersInfo' if market['future'] else 'privatePost'
        request = self.create_request(market)
        order_id_in_params = ('order_id' in list(params.keys()))
        if market['future']:
            if not order_id_in_params:
                raise ExchangeError(self.id + ' fetchOrders() requires order_id param for futures market ' + symbol + '(a string of one or more order ids, comma-separated)')
        else:
            status = params['type'] if ('type' in list(params.keys())) else params['status']
            if status is None:
                name = 'type' if order_id_in_params else 'status'
                raise ExchangeError(self.id + ' fetchOrders() requires ' + name + ' param for spot market ' + symbol + '(0 - for unfilled orders, 1 - for filled/canceled orders)')
            if order_id_in_params:
                method += 'OrdersInfo'
                request = self.extend(request, {
                    'type': status,
                    'order_id': params['order_id'],
                })
            else:
                method += 'OrderHistory'
                request = self.extend(request, {
                    'status': status,
                    'current_page': 1,  # current page number
                    'page_length': 200,  # number of orders returned per page, maximum 200
                })
            params = self.omit(params, ['type', 'status'])
        response = await getattr(self, method)(self.extend(request, params))
        ordersField = self.get_orders_field()
        return self.parse_orders(response[ordersField], market, since, limit)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 0,  # 0 for unfilled orders, 1 for filled orders
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 1,  # 0 for unfilled orders, 1 for filled orders
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        # if amount < 0.01:
        #     raise ExchangeError(self.id + ' withdraw() requires amount > 0.01')
        # for some reason they require to supply a pair of currencies for withdrawing one currency
        currencyId = currency['id'] + '_usd'
        if tag:
            address = address + ':' + tag
        request = {
            'symbol': currencyId,
            'withdraw_address': address,
            'withdraw_amount': amount,
            'target': 'address',  # or 'okcn', 'okcom', 'okex'
        }
        query = params
        if 'chargefee' in query:
            request['chargefee'] = query['chargefee']
            query = self.omit(query, 'chargefee')
        else:
            raise ExchangeError(self.id + ' withdraw() requires a `chargefee` parameter')
        if self.password:
            request['trade_pwd'] = self.password
        elif 'password' in query:
            request['trade_pwd'] = query['password']
            query = self.omit(query, 'password')
        elif 'trade_pwd' in query:
            request['trade_pwd'] = query['trade_pwd']
            query = self.omit(query, 'trade_pwd')
        passwordInRequest = ('trade_pwd' in list(request.keys()))
        if not passwordInRequest:
            raise ExchangeError(self.id + ' withdraw() requires self.password set on the exchange instance or a password / trade_pwd parameter')
        response = await self.privatePostWithdraw(self.extend(request, query))
        return {
            'info': response,
            'id': self.safe_string(response, 'withdraw_id'),
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/'
        if api != 'web':
            url += self.version + '/'
        url += path
        if api != 'web':
            url += self.extension
        if api == 'private':
            self.check_required_credentials()
            query = self.keysort(self.extend({
                'api_key': self.apiKey,
            }, params))
            # secret key must be at the end of query
            queryString = self.rawencode(query) + '&secret_key=' + self.secret
            query['sign'] = self.hash(self.encode(queryString)).upper()
            body = self.urlencode(query)
            headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        else:
            if params:
                url += '?' + self.urlencode(params)
        url = self.urls['api'][api] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def create_request(self, market, params={}):
        if market['future']:
            return self.deep_extend({
                'symbol': market['lowercaseId'],
                'contract_type': market['contractType'],
            }, params)
        return self.deep_extend({
            'symbol': market['id'],
        }, params)

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        if 'error_code' in response:
            error = self.safe_string(response, 'error_code')
            message = self.id + ' ' + self.json(response)
            if error in self.exceptions:
                ExceptionClass = self.exceptions[error]
                raise ExceptionClass(message)
            else:
                raise ExchangeError(message)
        if 'result' in response:
            if not response['result']:
                raise ExchangeError(self.id + ' ' + self.json(response))
