# -*- 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

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import OnMaintenance
from ccxt.base.decimal_to_precision import TICK_SIZE


class coinbasepro(Exchange):

    def describe(self):
        return self.deep_extend(super(coinbasepro, self).describe(), {
            'id': 'coinbasepro',
            'name': 'Coinbase Pro',
            'countries': ['US'],
            'rateLimit': 1000,
            'userAgent': self.userAgents['chrome'],
            'pro': True,
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': True,
                'createDepositAddress': True,
                'createOrder': True,
                'deposit': True,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchCurrencies': True,
                'fetchClosedOrders': True,
                'fetchDepositAddress': False,  # the exchange does not have self method, only createDepositAddress, see https://github.com/ccxt/ccxt/pull/7405
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchOrderTrades': True,
                'fetchTime': True,
                'fetchTicker': True,
                'fetchTrades': True,
                'fetchTransactions': True,
                'withdraw': True,
                'fetchDeposits': True,
                'fetchWithdrawals': True,
            },
            'timeframes': {
                '1m': 60,
                '5m': 300,
                '15m': 900,
                '1h': 3600,
                '6h': 21600,
                '1d': 86400,
            },
            'urls': {
                'test': {
                    'public': 'https://api-public.sandbox.pro.coinbase.com',
                    'private': 'https://api-public.sandbox.pro.coinbase.com',
                },
                'logo': 'https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg',
                'api': {
                    'public': 'https://api.pro.coinbase.com',
                    'private': 'https://api.pro.coinbase.com',
                },
                'www': 'https://pro.coinbase.com/',
                'doc': 'https://docs.pro.coinbase.com',
                'fees': [
                    'https://docs.pro.coinbase.com/#fees',
                    'https://support.pro.coinbase.com/customer/en/portal/articles/2945310-fees',
                ],
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'password': True,
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'products',
                        'products/{id}',
                        'products/{id}/book',
                        'products/{id}/candles',
                        'products/{id}/stats',
                        'products/{id}/ticker',
                        'products/{id}/trades',
                        'time',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'accounts/{id}',
                        'accounts/{id}/holds',
                        'accounts/{id}/ledger',
                        'accounts/{id}/transfers',
                        'coinbase-accounts',
                        'fills',
                        'funding',
                        'fees',
                        'margin/profile_information',
                        'margin/buying_power',
                        'margin/withdrawal_power',
                        'margin/withdrawal_power_all',
                        'margin/exit_plan',
                        'margin/liquidation_history',
                        'margin/position_refresh_amounts',
                        'margin/status',
                        'oracle',
                        'orders',
                        'orders/{id}',
                        'orders/client:{client_oid}',
                        'otc/orders',
                        'payment-methods',
                        'position',
                        'profiles',
                        'profiles/{id}',
                        'reports/{report_id}',
                        'transfers',
                        'transfers/{transfer_id}',
                        'users/self/trailing-volume',
                        'users/self/exchange-limits',
                        'withdrawals/fee-estimate',
                    ],
                    'post': [
                        'conversions',
                        'deposits/coinbase-account',
                        'deposits/payment-method',
                        'coinbase-accounts/{id}/addresses',
                        'funding/repay',
                        'orders',
                        'position/close',
                        'profiles/margin-transfer',
                        'profiles/transfer',
                        'reports',
                        'withdrawals/coinbase',
                        'withdrawals/coinbase-account',
                        'withdrawals/crypto',
                        'withdrawals/payment-method',
                    ],
                    'delete': [
                        'orders',
                        'orders/client:{client_oid}',
                        'orders/{id}',
                    ],
                },
            },
            'commonCurrencies': {
                'CGLD': 'CELO',
            },
            'precisionMode': TICK_SIZE,
            'fees': {
                'trading': {
                    'tierBased': True,  # complicated tier system per coin
                    'percentage': True,
                    'maker': 0.5 / 100,  # highest fee of all tiers
                    'taker': 0.5 / 100,  # highest fee of all tiers
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {
                        'BCH': 0,
                        'BTC': 0,
                        'LTC': 0,
                        'ETH': 0,
                        'EUR': 0.15,
                        'USD': 25,
                    },
                    'deposit': {
                        'BCH': 0,
                        'BTC': 0,
                        'LTC': 0,
                        'ETH': 0,
                        'EUR': 0.15,
                        'USD': 10,
                    },
                },
            },
            'exceptions': {
                'exact': {
                    'Insufficient funds': InsufficientFunds,
                    'NotFound': OrderNotFound,
                    'Invalid API Key': AuthenticationError,
                    'invalid signature': AuthenticationError,
                    'Invalid Passphrase': AuthenticationError,
                    'Invalid order id': InvalidOrder,
                    'Private rate limit exceeded': RateLimitExceeded,
                    'Trading pair not available': PermissionDenied,
                    'Product not found': InvalidOrder,
                },
                'broad': {
                    'Order already done': OrderNotFound,
                    'order not found': OrderNotFound,
                    'price too small': InvalidOrder,
                    'price too precise': InvalidOrder,
                    'under maintenance': OnMaintenance,
                    'size is too small': InvalidOrder,
                    'Cancel only mode': OnMaintenance,  # https://github.com/ccxt/ccxt/issues/7690
                },
            },
        })

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCurrencies(params)
        #
        #     [
        #         {
        #             id: 'XTZ',
        #             name: 'Tezos',
        #             min_size: '0.000001',
        #             status: 'online',
        #             message: '',
        #             max_precision: '0.000001',
        #             convertible_to: [],
        #             details: {
        #                 type: 'crypto',
        #                 symbol: 'Τ',
        #                 network_confirmations: 60,
        #                 sort_order: 53,
        #                 crypto_address_link: 'https://tzstats.com/{{address}}',
        #                 crypto_transaction_link: 'https://tzstats.com/{{txId}}',
        #                 push_payment_methods: ['crypto'],
        #                 group_types: [],
        #                 display_name: '',
        #                 processing_time_seconds: 0,
        #                 min_withdrawal_amount: 1
        #             }
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'id')
            name = self.safe_string(currency, 'name')
            code = self.safe_currency_code(id)
            details = self.safe_value(currency, 'details', {})
            precision = self.safe_float(currency, 'max_precision')
            status = self.safe_string(currency, 'status')
            active = (status == 'online')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'type': self.safe_string(details, 'type'),
                'name': name,
                'active': active,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_float(details, 'min_size'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(details, 'min_withdrawal_amount'),
                        'max': None,
                    },
                },
            }
        return result

    async def fetch_markets(self, params={}):
        response = await self.publicGetProducts(params)
        #
        #     [
        #         {
        #             "id":"ZEC-BTC",
        #             "base_currency":"ZEC",
        #             "quote_currency":"BTC",
        #             "base_min_size":"0.01000000",
        #             "base_max_size":"1500.00000000",
        #             "quote_increment":"0.00000100",
        #             "base_increment":"0.00010000",
        #             "display_name":"ZEC/BTC",
        #             "min_market_funds":"0.001",
        #             "max_market_funds":"30",
        #             "margin_enabled":false,
        #             "post_only":false,
        #             "limit_only":false,
        #             "cancel_only":false,
        #             "trading_disabled":false,
        #             "status":"online",
        #             "status_message":""
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'id')
            baseId = self.safe_string(market, 'base_currency')
            quoteId = self.safe_string(market, 'quote_currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            priceLimits = {
                'min': self.safe_float(market, 'quote_increment'),
                'max': None,
            }
            precision = {
                'amount': self.safe_float(market, 'base_increment'),
                'price': self.safe_float(market, 'quote_increment'),
            }
            status = self.safe_string(market, 'status')
            active = (status == 'online')
            result.append(self.extend(self.fees['trading'], {
                'id': id,
                'symbol': symbol,
                'baseId': baseId,
                'quoteId': quoteId,
                'base': base,
                'quote': quote,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_float(market, 'base_min_size'),
                        'max': self.safe_float(market, 'base_max_size'),
                    },
                    'price': priceLimits,
                    'cost': {
                        'min': self.safe_float(market, 'min_market_funds'),
                        'max': self.safe_float(market, 'max_market_funds'),
                    },
                },
                'active': active,
                'info': market,
            }))
        return result

    async def fetch_accounts(self, params={}):
        await self.load_markets()
        response = await self.privateGetAccounts(params)
        #
        #     [
        #         {
        #             id: '4aac9c60-cbda-4396-9da4-4aa71e95fba0',
        #             currency: 'BTC',
        #             balance: '0.0000000000000000',
        #             available: '0',
        #             hold: '0.0000000000000000',
        #             profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da'
        #         },
        #         {
        #             id: 'f75fa69a-1ad1-4a80-bd61-ee7faa6135a3',
        #             currency: 'USDC',
        #             balance: '0.0000000000000000',
        #             available: '0',
        #             hold: '0.0000000000000000',
        #             profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da'
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            account = response[i]
            accountId = self.safe_string(account, 'id')
            currencyId = self.safe_string(account, 'currency')
            code = self.safe_currency_code(currencyId)
            result.append({
                'id': accountId,
                'type': None,
                'currency': code,
                'info': account,
            })
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetAccounts(params)
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = {
                'free': self.safe_float(balance, 'available'),
                'used': self.safe_float(balance, 'hold'),
                'total': self.safe_float(balance, 'balance'),
            }
            result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        # level 1 - only the best bid and ask
        # level 2 - top 50 bids and asks(aggregated)
        # level 3 - full order book(non aggregated)
        request = {
            'id': self.market_id(symbol),
            'level': 2,  # 1 best bidask, 2 aggregated, 3 full
        }
        response = await self.publicGetProductsIdBook(self.extend(request, params))
        #
        #     {
        #         "sequence":1924393896,
        #         "bids":[
        #             ["0.01825","24.34811287",2],
        #             ["0.01824","72.5463",3],
        #             ["0.01823","424.54298049",6],
        #         ],
        #         "asks":[
        #             ["0.01826","171.10414904",4],
        #             ["0.01827","22.60427028",1],
        #             ["0.01828","397.46018784",7],
        #         ]
        #     }
        #
        orderbook = self.parse_order_book(response)
        orderbook['nonce'] = self.safe_integer(response, 'sequence')
        return orderbook

    def parse_ticker(self, ticker, market=None):
        #
        # publicGetProductsIdTicker
        #
        #     {
        #         "trade_id":843439,
        #         "price":"0.997999",
        #         "size":"80.29769",
        #         "time":"2020-01-28T02:13:33.012523Z",
        #         "bid":"0.997094",
        #         "ask":"0.998",
        #         "volume":"1903188.03750000"
        #     }
        #
        # publicGetProductsIdStats
        #
        #     {
        #         "open": "34.19000000",
        #         "high": "95.70000000",
        #         "low": "7.06000000",
        #         "volume": "2.41000000"
        #     }
        #
        timestamp = self.parse8601(self.safe_value(ticker, 'time'))
        bid = self.safe_float(ticker, 'bid')
        ask = self.safe_float(ticker, 'ask')
        last = self.safe_float(ticker, 'price')
        symbol = None if (market is None) else market['symbol']
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': bid,
            'bidVolume': None,
            'ask': ask,
            'askVolume': None,
            'vwap': None,
            'open': self.safe_float(ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
        }
        # publicGetProductsIdTicker or publicGetProductsIdStats
        method = self.safe_string(self.options, 'fetchTickerMethod', 'publicGetProductsIdTicker')
        response = await getattr(self, method)(self.extend(request, params))
        #
        # publicGetProductsIdTicker
        #
        #     {
        #         "trade_id":843439,
        #         "price":"0.997999",
        #         "size":"80.29769",
        #         "time":"2020-01-28T02:13:33.012523Z",
        #         "bid":"0.997094",
        #         "ask":"0.998",
        #         "volume":"1903188.03750000"
        #     }
        #
        # publicGetProductsIdStats
        #
        #     {
        #         "open": "34.19000000",
        #         "high": "95.70000000",
        #         "low": "7.06000000",
        #         "volume": "2.41000000"
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_trade(self, trade, market=None):
        #
        #     {
        #         type: 'match',
        #         trade_id: 82047307,
        #         maker_order_id: '0f358725-2134-435e-be11-753912a326e0',
        #         taker_order_id: '252b7002-87a3-425c-ac73-f5b9e23f3caf',
        #         order_id: 'd50ec984-77a8-460a-b958-66f114b0de9b',
        #         side: 'sell',
        #         size: '0.00513192',
        #         price: '9314.78',
        #         product_id: 'BTC-USD',
        #         profile_id: '6244401d-c078-40d9-b305-7ad3551bc3b0',
        #         sequence: 12038915443,
        #         time: '2020-01-31T20:03:41.158814Z'
        #         created_at: '2014-11-07T22:19:28.578544Z',
        #         liquidity: 'T',
        #         fee: '0.00025',
        #         settled: True,
        #         usd_volume: '0.0924556000000000',
        #         user_id: '595eb864313c2b02ddf2937d'
        #     }
        #
        timestamp = self.parse8601(self.safe_string_2(trade, 'time', 'created_at'))
        marketId = self.safe_string(trade, 'product_id')
        symbol = self.safe_symbol(marketId, market, '-')
        feeRate = None
        feeCurrency = None
        takerOrMaker = None
        cost = None
        if market is not None:
            feeCurrencyId = self.safe_string_lower(market, 'quoteId')
            costField = feeCurrencyId + '_value'
            cost = self.safe_float(trade, costField)
            feeCurrency = market['quote']
            liquidity = self.safe_string(trade, 'liquidity')
            if liquidity is not None:
                takerOrMaker = 'taker' if (liquidity == 'T') else 'maker'
                feeRate = market[takerOrMaker]
        feeCost = self.safe_float_2(trade, 'fill_fees', 'fee')
        fee = {
            'cost': feeCost,
            'currency': feeCurrency,
            'rate': feeRate,
        }
        type = None
        id = self.safe_string(trade, 'trade_id')
        side = 'sell' if (trade['side'] == 'buy') else 'buy'
        orderId = self.safe_string(trade, 'order_id')
        # Coinbase Pro returns inverted side to fetchMyTrades vs fetchTrades
        if orderId is not None:
            side = 'buy' if (trade['side'] == 'buy') else 'sell'
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'size')
        if cost is None:
            cost = amount * price
        return {
            'id': id,
            'order': orderId,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'fee': fee,
            'cost': cost,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        # as of 2018-08-23
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'product_id': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetFills(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],  # fixes issue  #2
        }
        if limit is not None:
            request['limit'] = limit  # default 100
        response = await self.publicGetProductsIdTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1591514160,
        #         0.02507,
        #         0.02507,
        #         0.02507,
        #         0.02507,
        #         0.02816506
        #     ]
        #
        return [
            self.safe_timestamp(ohlcv, 0),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 1),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        granularity = self.timeframes[timeframe]
        request = {
            'id': market['id'],
            'granularity': granularity,
        }
        if since is not None:
            request['start'] = self.iso8601(since)
            if limit is None:
                # https://docs.pro.coinbase.com/#get-historic-rates
                limit = 300  # max = 300
            request['end'] = self.iso8601(self.sum((limit - 1) * granularity * 1000, since))
        response = await self.publicGetProductsIdCandles(self.extend(request, params))
        #
        #     [
        #         [1591514160,0.02507,0.02507,0.02507,0.02507,0.02816506],
        #         [1591514100,0.02507,0.02507,0.02507,0.02507,1.63830323],
        #         [1591514040,0.02505,0.02507,0.02505,0.02507,0.19918178]
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def fetch_time(self, params={}):
        response = await self.publicGetTime(params)
        #
        #     {
        #         "iso":"2020-05-12T08:00:51.504Z",
        #         "epoch":1589270451.504
        #     }
        #
        return self.safe_timestamp(response, 'epoch')

    def parse_order_status(self, status):
        statuses = {
            'pending': 'open',
            'active': 'open',
            'open': 'open',
            'done': 'closed',
            'canceled': 'canceled',
            'canceling': 'open',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "id": "d0c5340b-6d6c-49d9-b567-48c4bfca13d2",
        #         "price": "0.10000000",
        #         "size": "0.01000000",
        #         "product_id": "BTC-USD",
        #         "side": "buy",
        #         "stp": "dc",
        #         "type": "limit",
        #         "time_in_force": "GTC",
        #         "post_only": False,
        #         "created_at": "2016-12-08T20:02:28.53864Z",
        #         "fill_fees": "0.0000000000000000",
        #         "filled_size": "0.00000000",
        #         "executed_value": "0.0000000000000000",
        #         "status": "pending",
        #         "settled": False
        #     }
        #
        timestamp = self.parse8601(self.safe_string(order, 'created_at'))
        marketId = self.safe_string(order, 'product_id')
        market = self.safe_market(marketId, market, '-')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        price = self.safe_float(order, 'price')
        filled = self.safe_float(order, 'filled_size')
        amount = self.safe_float(order, 'size', filled)
        remaining = None
        if amount is not None:
            if filled is not None:
                remaining = amount - filled
        cost = self.safe_float(order, 'executed_value')
        feeCost = self.safe_float(order, 'fill_fees')
        fee = None
        if feeCost is not None:
            feeCurrencyCode = None
            if market is not None:
                feeCurrencyCode = market['quote']
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
                'rate': None,
            }
        id = self.safe_string(order, 'id')
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        timeInForce = self.safe_string(order, 'time_in_force')
        postOnly = self.safe_value(order, 'post_only')
        stopPrice = self.safe_float(order, 'stop_price')
        clientOrderId = self.safe_string(order, 'client_oid')
        return {
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'fee': fee,
            'average': None,
            'trades': None,
        }

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {}
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_oid')
        method = None
        if clientOrderId is None:
            method = 'privateGetOrdersId'
            request['id'] = id
        else:
            method = 'privateGetOrdersClientClientOid'
            request['client_oid'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'client_oid'])
        response = await getattr(self, method)(self.extend(request, params))
        return self.parse_order(response)

    async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'order_id': id,
        }
        response = await self.privateGetFills(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 'all',
        }
        return await self.fetch_open_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['product_id'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 100
        response = await self.privateGetOrders(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'status': 'done',
        }
        return await self.fetch_open_orders(symbol, since, limit, self.extend(request, params))

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            # common params --------------------------------------------------
            # 'client_oid': clientOrderId,
            'type': type,
            'side': side,
            'product_id': market['id'],
            # 'size': self.amount_to_precision(symbol, amount),
            # 'stp': 'dc',  # self-trade prevention, dc = decrease and cancel, co = cancel oldest, cn = cancel newest, cb = cancel both
            # 'stop': 'loss',  # "loss" = stop loss below price, "entry" = take profit above price
            # 'stop_price': self.price_to_precision(symbol, price),
            # limit order params ---------------------------------------------
            # 'price': self.price_to_precision(symbol, price),
            # 'size': self.amount_to_precision(symbol, amount),
            # 'time_in_force': 'GTC',  # GTC, GTT, IOC, or FOK
            # 'cancel_after' [optional]* min, hour, day, requires time_in_force to be GTT
            # 'post_only': False,  # invalid when time_in_force is IOC or FOK
            # market order params --------------------------------------------
            # 'size': self.amount_to_precision(symbol, amount),
            # 'funds': self.cost_to_precision(symbol, amount),
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_oid')
        if clientOrderId is not None:
            request['client_oid'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'client_oid'])
        stopPrice = self.safe_float_2(params, 'stopPrice', 'stop_price')
        if stopPrice is not None:
            request['stop_price'] = self.price_to_precision(symbol, stopPrice)
            params = self.omit(params, ['stopPrice', 'stop_price'])
        timeInForce = self.safe_string_2(params, 'timeInForce', 'time_in_force')
        if timeInForce is not None:
            request['time_in_force'] = timeInForce
            params = self.omit(params, ['timeInForce', 'time_in_force'])
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
            request['size'] = self.amount_to_precision(symbol, amount)
        elif type == 'market':
            cost = self.safe_float_2(params, 'cost', 'funds')
            if cost is None:
                if price is not None:
                    cost = amount * price
            else:
                params = self.omit(params, ['cost', 'funds'])
            if cost is not None:
                request['funds'] = self.cost_to_precision(symbol, cost)
            else:
                request['size'] = self.amount_to_precision(symbol, amount)
        response = await self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "id": "d0c5340b-6d6c-49d9-b567-48c4bfca13d2",
        #         "price": "0.10000000",
        #         "size": "0.01000000",
        #         "product_id": "BTC-USD",
        #         "side": "buy",
        #         "stp": "dc",
        #         "type": "limit",
        #         "time_in_force": "GTC",
        #         "post_only": False,
        #         "created_at": "2016-12-08T20:02:28.53864Z",
        #         "fill_fees": "0.0000000000000000",
        #         "filled_size": "0.00000000",
        #         "executed_value": "0.0000000000000000",
        #         "status": "pending",
        #         "settled": False
        #     }
        #
        return self.parse_order(response, market)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            # 'product_id': market['id'],  # the request will be more performant if you include it
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_oid')
        method = None
        if clientOrderId is None:
            method = 'privateDeleteOrdersId'
            request['id'] = id
        else:
            method = 'privateDeleteOrdersClientClientOid'
            request['client_oid'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'client_oid'])
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['product_id'] = market['symbol']  # the request will be more performant if you include it
        return await getattr(self, method)(self.extend(request, params))

    async def cancel_all_orders(self, symbol=None, params={}):
        await self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['product_id'] = market['symbol']  # the request will be more performant if you include it
        return await self.privateDeleteOrders(self.extend(request, params))

    def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
        market = self.markets[symbol]
        rate = market[takerOrMaker]
        cost = amount * price
        currency = market['quote']
        return {
            'type': takerOrMaker,
            'currency': currency,
            'rate': rate,
            'cost': float(self.currency_to_precision(currency, rate * cost)),
        }

    async def fetch_payment_methods(self, params={}):
        return await self.privateGetPaymentMethods(params)

    async def deposit(self, code, amount, address, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'amount': amount,
        }
        method = 'privatePostDeposits'
        if 'payment_method_id' in params:
            # deposit from a payment_method, like a bank account
            method += 'PaymentMethod'
        elif 'coinbase_account_id' in params:
            # deposit into Coinbase Pro account from a Coinbase account
            method += 'CoinbaseAccount'
        else:
            # deposit methodotherwise we did not receive a supported deposit location
            # relevant docs link for the Googlers
            # https://docs.pro.coinbase.com/#deposits
            raise NotSupported(self.id + ' deposit() requires one of `coinbase_account_id` or `payment_method_id` extra params')
        response = await getattr(self, method)(self.extend(request, params))
        if not response:
            raise ExchangeError(self.id + ' deposit() error: ' + self.json(response))
        return {
            'info': response,
            'id': response['id'],
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'amount': amount,
        }
        method = 'privatePostWithdrawals'
        if 'payment_method_id' in params:
            method += 'PaymentMethod'
        elif 'coinbase_account_id' in params:
            method += 'CoinbaseAccount'
        else:
            method += 'Crypto'
            request['crypto_address'] = address
            if tag is not None:
                request['destination_tag'] = tag
        response = await getattr(self, method)(self.extend(request, params))
        if not response:
            raise ExchangeError(self.id + ' withdraw() error: ' + self.json(response))
        return {
            'info': response,
            'id': response['id'],
        }

    async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        await self.load_accounts()
        currency = None
        id = self.safe_string(params, 'id')  # account id
        if id is None:
            if code is not None:
                currency = self.currency(code)
                accountsByCurrencyCode = self.index_by(self.accounts, 'currency')
                account = self.safe_value(accountsByCurrencyCode, code)
                if account is None:
                    raise ExchangeError(self.id + ' fetchTransactions() could not find account id for ' + code)
                id = account['id']
        request = {}
        if id is not None:
            request['id'] = id
        if limit is not None:
            request['limit'] = limit
        response = None
        if id is None:
            response = await self.privateGetTransfers(self.extend(request, params))
            for i in range(0, len(response)):
                account_id = self.safe_string(response[i], 'account_id')
                account = self.safe_value(self.accountsById, account_id)
                code = self.safe_string(account, 'currency')
                response[i]['currency'] = code
        else:
            response = await self.privateGetAccountsIdTransfers(self.extend(request, params))
            for i in range(0, len(response)):
                response[i]['currency'] = code
        return self.parse_transactions(response, currency, since, limit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        return self.fetch_transactions(code, since, limit, self.extend(params, {'type': 'deposit'}))

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        return self.fetch_transactions(code, since, limit, self.extend(params, {'type': 'withdraw'}))

    def parse_transaction_status(self, transaction):
        canceled = self.safe_value(transaction, 'canceled_at')
        if canceled:
            return 'canceled'
        processed = self.safe_value(transaction, 'processed_at')
        completed = self.safe_value(transaction, 'completed_at')
        if completed:
            return 'ok'
        elif processed and not completed:
            return 'failed'
        else:
            return 'pending'

    def parse_transaction(self, transaction, currency=None):
        details = self.safe_value(transaction, 'details', {})
        id = self.safe_string(transaction, 'id')
        txid = self.safe_string(details, 'crypto_transaction_hash')
        timestamp = self.parse8601(self.safe_string(transaction, 'created_at'))
        updated = self.parse8601(self.safe_string(transaction, 'processed_at'))
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(transaction)
        amount = self.safe_float(transaction, 'amount')
        type = self.safe_string(transaction, 'type')
        address = self.safe_string(details, 'crypto_address')
        tag = self.safe_string(details, 'destination_tag')
        address = self.safe_string(transaction, 'crypto_address', address)
        fee = None
        if type == 'withdraw':
            type = 'withdrawal'
            address = self.safe_string(details, 'sent_to_address', address)
            feeCost = self.safe_float(details, 'fee')
            if feeCost is not None:
                if amount is not None:
                    amount -= feeCost
                fee = {
                    'cost': feeCost,
                    'currency': code,
                }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'tag': tag,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if method == 'GET':
            if query:
                request += '?' + self.urlencode(query)
        url = self.urls['api'][api] + request
        if api == 'private':
            self.check_required_credentials()
            nonce = str(self.nonce())
            payload = ''
            if method != 'GET':
                if query:
                    body = self.json(query)
                    payload = body
            what = nonce + method + request + payload
            secret = self.base64_to_binary(self.secret)
            signature = self.hmac(self.encode(what), secret, hashlib.sha256, 'base64')
            headers = {
                'CB-ACCESS-KEY': self.apiKey,
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-TIMESTAMP': nonce,
                'CB-ACCESS-PASSPHRASE': self.password,
                'Content-Type': 'application/json',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    async def create_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        accounts = self.safe_value(self.options, 'coinbaseAccounts')
        if accounts is None:
            accounts = await self.privateGetCoinbaseAccounts()
            self.options['coinbaseAccounts'] = accounts  # cache it
            self.options['coinbaseAccountsByCurrencyId'] = self.index_by(accounts, 'currency')
        currencyId = currency['id']
        account = self.safe_value(self.options['coinbaseAccountsByCurrencyId'], currencyId)
        if account is None:
            # eslint-disable-next-line quotes
            raise InvalidAddress(self.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in self.options['coinbaseAccountsByCurrencyId']")
        request = {
            'id': account['id'],
        }
        response = await self.privatePostCoinbaseAccountsIdAddresses(self.extend(request, params))
        address = self.safe_string(response, 'address')
        tag = self.safe_string(response, 'destination_tag')
        return {
            'currency': code,
            'address': self.check_address(address),
            'tag': tag,
            'info': response,
        }

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if (code == 400) or (code == 404):
            if body[0] == '{':
                message = self.safe_string(response, 'message')
                feedback = self.id + ' ' + message
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                raise ExchangeError(feedback)  # unknown message
            raise ExchangeError(self.id + ' ' + body)

    async def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
        response = await self.fetch2(path, api, method, params, headers, body)
        if not isinstance(response, basestring):
            if 'message' in response:
                raise ExchangeError(self.id + ' ' + self.json(response))
        return response
