# -*- 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.base.exchange import Exchange
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.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class ace(Exchange):

    def describe(self):
        return self.deep_extend(super(ace, self).describe(), {
            'id': 'ace',
            'name': 'ACE',
            'countries': ['TW'],  # Taiwan
            'version': 'v2',
            'rateLimit': 100,
            'pro': False,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'cancelAllOrders': False,
                'cancelOrder': True,
                'cancelOrders': False,
                'createOrder': True,
                'editOrder': False,
                'fetchBalance': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchClosedOrders': False,
                'fetchCurrencies': False,
                'fetchDepositAddress': False,
                'fetchDeposits': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchOrderTrades': True,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': False,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTransactionFees': False,
                'fetchTransactions': False,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': False,
                'setLeverage': False,
                'setMarginMode': False,
                'transfer': False,
                'withdraw': False,
            },
            'timeframes': {
                '1m': 1,
                '5m': 5,
                '10m': 10,
                '30m': 10,
                '1h': 60,
                '2h': 120,
                '4h': 240,
                '8h': 480,
                '12h': 720,
                '1d': 24,
                '1w': 70,
                '1M': 31,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/216908003-fb314cf6-e66e-471c-b91d-1d86e4baaa90.jpg',
                'api': {
                    'public': 'https://ace.io/polarisex',
                    'private': 'https://ace.io/polarisex/open',
                },
                'www': 'https://ace.io/',
                'doc': [
                    'https://github.com/ace-exchange/ace-offical-api-docs',
                ],
                'fees': 'https://helpcenter.ace.io/hc/zh-tw/articles/360018609132-%E8%B2%BB%E7%8E%87%E8%AA%AA%E6%98%8E',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'api': {
                'public': {
                    'get': [
                        'oapi/v2/list/tradePrice',
                        'oapi/v2/list/marketPair',
                        'open/v2/public/getOrderBook',
                    ],
                },
                'private': {
                    'post': [
                        'v2/coin/customerAccount',
                        'v2/kline/getKline',
                        'v2/order/order',
                        'v2/order/cancel',
                        'v2/order/getOrderList',
                        'v2/order/showOrderStatus',
                        'v2/order/showOrderHistory',
                        'v2/order/getTradeList',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'percentage': True,
                    'maker': self.parse_number('0.0005'),
                    'taker': self.parse_number('0.001'),
                },
            },
            'options': {
                'brokerId': 'ccxt',
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '2003': InvalidOrder,
                    '2004': InvalidOrder,
                    '2005': InvalidOrder,
                    '2021': InsufficientFunds,
                    '2036': InvalidOrder,
                    '2039': InvalidOrder,
                    '2053': InvalidOrder,
                    '2061': BadRequest,
                    '2063': InvalidOrder,
                    '9996': BadRequest,
                    '10012': AuthenticationError,
                    '20182': AuthenticationError,
                    '20183': InvalidOrder,
                },
                'broad': {
                },
            },
            'commonCurrencies': {
            },
        })

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for ace
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#oapi-api---market-pair
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = self.publicGetOapiV2ListMarketPair()
        #
        #     [
        #         {
        #             "symbol":"BTC/USDT",
        #             "base":"btc",
        #             "baseCurrencyId": "122"
        #             "quote":"usdt",
        #             "basePrecision":"8",
        #             "quotePrecision":"5",
        #             "minLimitBaseAmount":"0.1",
        #             "maxLimitBaseAmount":"480286"
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            base = self.safe_string(market, 'base')
            baseCode = self.safe_currency_code(base)
            quote = self.safe_string(market, 'quote')
            quoteCode = self.safe_currency_code(quote)
            symbol = base + '/' + quote
            result.append({
                'id': self.safe_string(market, 'symbol'),
                'uppercaseId': None,
                'symbol': symbol,
                'base': baseCode,
                'baseId': self.safe_integer(market, 'baseCurrencyId'),
                'quote': quoteCode,
                'quoteId': self.safe_integer(market, 'quoteCurrencyId'),
                'settle': None,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'derivative': False,
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'limits': {
                    'amount': {
                        'min': self.safe_number(market, 'minLimitBaseAmount'),
                        'max': self.safe_number(market, 'maxLimitBaseAmount'),
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                },
                'precision': {
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'basePrecision'))),
                },
                'active': None,
                'info': market,
            })
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "base_volume":229196.34035399999,
        #         "last_price":11881.06,
        #         "quote_volume":19.2909
        #     }
        #
        marketId = self.safe_string(ticker, 'id')
        symbol = self.safe_symbol(marketId, market)
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': None,
            'low': None,
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': self.safe_string(ticker, 'last_price'),
            'last': self.safe_string(ticker, 'last_price'),
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'base_volume'),
            'quoteVolume': self.safe_string(ticker, 'quote_volume'),
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#oapi-api---trade-data
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = self.publicGetOapiV2ListTradePrice(params)
        marketId = market['id']
        ticker = self.safe_value(response, marketId, {})
        #
        #     {
        #         "BTC/USDT":{
        #             "base_volume":229196.34035399999,
        #             "last_price":11881.06,
        #             "quote_volume":19.2909
        #         }
        #     }
        #
        return self.parse_ticker(ticker, market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#oapi-api---trade-data
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        response = self.publicGetOapiV2ListTradePrice()
        #
        #     {
        #         "BTC/USDT":{
        #             "base_volume":229196.34035399999,
        #             "last_price":11881.06,
        #             "quote_volume":19.2909
        #         }
        #     }
        #
        tickers = []
        pairs = list(response.keys())
        for i in range(0, len(pairs)):
            marketId = pairs[i]
            market = self.safe_market(marketId)
            rawTicker = self.safe_value(response, marketId)
            ticker = self.parse_ticker(rawTicker, market)
            tickers.append(ticker)
        return self.filter_by_array(tickers, 'symbol', symbols)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---order-books
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'quoteCurrencyId': market['quoteId'],
            'baseCurrencyId': market['baseId'],
        }
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetOpenV2PublicGetOrderBook(self.extend(request, params))
        #
        #     {
        #         "attachment": {
        #             "baseCurrencyId": "2",
        #             "quoteCurrencyId": "14",
        #             "baseCurrencyName": "BTC",
        #             "quoteCurrencyName": "USDT",
        #             "bids": [
        #                 [
        #                     "0.0009",
        #                     "19993.53"
        #                 ],
        #                 [
        #                     "0.001",
        #                     "19675.33"
        #                 ],
        #                 [
        #                     "0.001",
        #                     "19357.13"
        #                 ]
        #             ],
        #             "asks": [
        #                 [
        #                     "0.001",
        #                     "20629.92"
        #                 ],
        #                 [
        #                     "0.001",
        #                     "20948.12"
        #                 ]
        #             ]
        #         },
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        orderBook = self.safe_value(response, 'attachment')
        return self.parse_order_book(orderBook, market['symbol'], None, 'bids', 'asks')

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "changeRate": 0,
        #         "volume": 0,
        #         "closePrice": 101000.0,
        #         "lowPrice": 101000.0,
        #         "highPrice": 101000.0,
        #         "highPrice": 1573195740000L,
        #         "openPrice": 101000.0,
        #         "current": 101000.0,
        #         "currentTime": "2019-11-08 14:49:00",
        #         "createTime": "2019-11-08 14:49:00"
        #     }
        #
        dateTime = self.safe_string(ohlcv, 'createTime')
        timestamp = self.parse8601(dateTime)
        if timestamp is not None:
            timestamp = timestamp - 28800000  # 8 hours
        return [
            timestamp,
            self.safe_number(ohlcv, 'openPrice'),
            self.safe_number(ohlcv, 'highPrice'),
            self.safe_number(ohlcv, 'lowPrice'),
            self.safe_number(ohlcv, 'closePrice'),
            self.safe_number(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---klinecandlestick-data
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the ace api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'duration': self.timeframes[timeframe],
            'quoteCurrencyId': market['quoteId'],
            'baseCurrencyId': market['baseId'],
        }
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['startTime'] = since
        response = self.privatePostV2KlineGetKline(self.extend(request, params))
        data = self.safe_value(response, 'attachment', [])
        #
        #     {
        #         "attachment":[
        #                 {
        #                     "changeRate": 0,
        #                     "closePrice": 101000.0,
        #                     "volume": 0,
        #                     "lowPrice": 101000.0,
        #                     "highPrice": 101000.0,
        #                     "highPrice": 1573195740000L,
        #                     "openPrice": 101000.0,
        #                     "current": 101000.0,
        #                     "currentTime": "2019-11-08 14:49:00",
        #                     "createTime": "2019-11-08 14:49:00"
        #                 }
        #         ]
        #     }
        #
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_order_status(self, status):
        statuses = {
            '0': 'open',
            '1': 'open',
            '2': 'closed',
            '4': 'canceled',
            '5': 'canceled',
        }
        return self.safe_string(statuses, status, None)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #         "15697850529570392100421100482693"
        #
        # fetchOpenOrders
        #         {
        #             "uid": 0,
        #             "orderNo": "16113081376560890227301101413941",
        #             "orderTime": "2021-01-22 17:35:37",
        #             "orderTimeStamp": 1611308137656,
        #             "baseCurrencyId": 1,
        #             "baseCurrencyName": "TWD",
        #             "quoteCurrencyId": 14,
        #             "quoteCurrencyName": "USDT",
        #             "buyOrSell": "1",
        #             "num": "6.0000000000000000",
        #             "price": "32.5880000000000000",
        #             "remainNum": "2.0000000000000000",
        #             "tradeNum": "4.0000000000000000",
        #             "tradePrice": "31.19800000000000000000",
        #             "tradeAmount": "124.7920000000000000",
        #             "tradeRate": "0.66666666666666666667",
        #             "status": 1,
        #             "type": 1
        #         }
        #
        id = None
        timestamp = None
        symbol = None
        price = None
        amount = None
        side = None
        type = None
        status = None
        filled = None
        remaining = None
        average = None
        if isinstance(order, str):
            id = order
        else:
            id = self.safe_string(order, 'orderNo')
            timestamp = self.safe_integer(order, 'orderTimeStamp')
            if timestamp is None:
                dateTime = self.safe_string(order, 'orderTime')
                if dateTime is not None:
                    timestamp = self.parse8601(dateTime)
                    timestamp = timestamp - 28800000  # 8 hours
            orderSide = self.safe_number(order, 'buyOrSell')
            if orderSide is not None:
                side = 'buy' if (orderSide == 1) else 'sell'
            amount = self.safe_string(order, 'num')
            price = self.safe_string(order, 'price')
            quoteId = self.safe_string(order, 'quoteCurrencyName')
            baseId = self.safe_string(order, 'baseCurrencyName')
            if quoteId is not None and baseId is not None:
                symbol = baseId + '/' + quoteId
            orderType = self.safe_number(order, 'type')
            if orderType is not None:
                type = 'limit' if (orderType == 1) else 'market'
            filled = self.safe_string(order, 'tradeNum')
            remaining = self.safe_string(order, 'remainNum')
            status = self.parse_order_status(self.safe_string(order, 'status'))
            average = self.safe_string(order, 'averagePrice')
        return self.safe_order({
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
            'info': order,
        }, market)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---new-order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        orderType = type.upper()
        orderSide = side.upper()
        request = {
            'baseCurrencyId': market['baseId'],
            'quoteCurrencyId': market['quoteId'],
            'type': 1 if (orderType == 'LIMIT') else 2,
            'buyOrSell': 1 if (orderSide == 'BUY') else 2,
            'num': self.amount_to_precision(symbol, amount),
        }
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
        response = self.privatePostV2OrderOrder(self.extend(request, params), params)
        #
        #     {
        #         "attachment": "15697850529570392100421100482693",
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        data = self.safe_value(response, 'attachment')
        return self.parse_order(data, market)

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---cancel-order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'orderNo': id,
        }
        response = self.privatePostV2OrderCancel(self.extend(request, params))
        #
        #     {
        #         "attachment": 200,
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        return response

    def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---order-status
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'orderNo': id,
        }
        response = self.privatePostV2OrderShowOrderStatus(self.extend(request, params))
        #
        #     {
        #         "attachment": {
        #             "buyOrSell": 1,
        #             "averagePrice": "490849.75000000",
        #             "num": "0.00000000",
        #             "orderTime": "2022-11-29 18:03:06.318",
        #             "price": "490849.75000000",
        #             "status": 4,
        #             "tradeNum": "0.02697000",
        #             "remainNum": "0.97303000",
        #             "baseCurrencyId": 2,
        #             "baseCurrencyName": "BTC",
        #             "quoteCurrencyId": 1,
        #             "quoteCurrencyName": "TWD",
        #             "orderNo": "16697161898600391472461100244406"
        #         },
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        data = self.safe_value(response, 'attachment')
        return self.parse_order(data, None)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---order-list
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the ace api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires the symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'quoteCurrencyId': market['quoteId'],
            'baseCurrencyId': market['baseId'],
            # 'start': 0,
        }
        if limit is not None:
            request['size'] = limit
        response = self.privatePostV2OrderGetOrderList(self.extend(request, params), params)
        orders = self.safe_value(response, 'attachment')
        #
        #     {
        #         "attachment": [
        #             {
        #                 "uid": 0,
        #                 "orderNo": "16113081376560890227301101413941",
        #                 "orderTime": "2021-01-22 17:35:37",
        #                 "orderTimeStamp": 1611308137656,
        #                 "baseCurrencyId": 1,
        #                 "baseCurrencyName": "TWD",
        #                 "quoteCurrencyId": 14,
        #                 "quoteCurrencyName": "USDT",
        #                 "buyOrSell": "1",
        #                 "num": "6.0000000000000000",
        #                 "price": "32.5880000000000000",
        #                 "remainNum": "2.0000000000000000",
        #                 "tradeNum": "4.0000000000000000",
        #                 "tradePrice": "31.19800000000000000000",
        #                 "tradeAmount": "124.7920000000000000",
        #                 "tradeRate": "0.66666666666666666667",
        #                 "status": 1,
        #                 "type": 1
        #             }
        #         ],
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        return self.parse_orders(orders, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchOrderTrades
        #         {
        #             "amount": 0.0030965,
        #             "tradeNo": "15681920522485652100751000417788",
        #             "price": "0.03096500",
        #             "num": "0.10000000",
        #             "bi": 1,
        #             "time": "2019-09-11 16:54:12.248"
        #         }
        #
        # fetchMyTrades
        #         {
        #             "buyOrSell": 1,
        #             "orderNo": "16708156853695560053601100247906",
        #             "num": "1",
        #             "price": "16895",
        #             "orderAmount": "16895",
        #             "tradeNum": "0.1",
        #             "tradePrice": "16895",
        #             "tradeAmount": "1689.5",
        #             "fee": "0",
        #             "feeSave": "0",
        #             "status": 1,
        #             "isSelf": False,
        #             "tradeNo": "16708186395087940051961000274150",
        #             "tradeTime": "2022-12-12 12:17:19",
        #             "tradeTimestamp": 1670818639508,
        #             "quoteCurrencyId": 14,
        #             "quoteCurrencyName": "USDT",
        #             "baseCurrencyId": 2,
        #             "baseCurrencyName": "BTC"
        #         }
        id = self.safe_string(trade, 'tradeNo')
        price = self.safe_string(trade, 'price')
        amount = self.safe_string(trade, 'num')
        timestamp = self.safe_integer(trade, 'tradeTimestamp')
        if timestamp is None:
            datetime = self.safe_string_2(trade, 'time', 'tradeTime')
            timestamp = self.parse8601(datetime)
            timestamp = timestamp - 28800000  # 8 hours normalize timestamp
        symbol = market['symbol']
        quoteId = self.safe_string(trade, 'quoteCurrencyName')
        baseId = self.safe_string(trade, 'baseCurrencyName')
        if quoteId is not None and baseId is not None:
            symbol = baseId + '/' + quoteId
        side = None
        tradeSide = self.safe_number(trade, 'buyOrSell')
        if tradeSide is not None:
            side = 'buy' if (tradeSide == 1) else 'sell'
        feeString = self.safe_string(trade, 'fee')
        fee = None
        if feeString is not None:
            feeSaveString = self.safe_string(trade, 'feeSave')
            fee = {
                'cost': Precise.string_sub(feeString, feeSaveString),
                'currency': quoteId,
            }
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': self.safe_string(trade, 'orderNo'),
            'symbol': symbol,
            'side': side,
            'type': None,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': fee,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
        }, market)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        """
        fetch all the trades made from a single order
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---order-history
        :param str id: order id
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades to retrieve
        :param dict params: extra parameters specific to the ace api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        self.load_markets()
        market = self.safe_market(symbol)
        request = {
            'orderNo': id,
        }
        response = self.privatePostV2OrderShowOrderHistory(self.extend(request, params))
        #
        #     {
        #         "attachment": {
        #             "order": {
        #                 "buyOrSell": 1,
        #                 "averagePrice": "491343.74000000",
        #                 "num": "1.00000000",
        #                 "orderTime": "2022-11-29 18:32:22.232",
        #                 "price": "491343.74000000",
        #                 "status": 1,
        #                 "tradeNum": "0.01622200",
        #                 "remainNum": "0.98377800",
        #                 "baseCurrencyId": 2,
        #                 "baseCurrencyName": "BTC",
        #                 "quoteCurrencyId": 1,
        #                 "quoteCurrencyName": "TWD",
        #                 "orderNo": "16697179457740441472471100214402"
        #             },
        #             "trades": [
        #                 {
        #                     "price": "491343.74000000",
        #                     "num": "0.01622200",
        #                     "time": "2022-11-29 18:32:25.789",
        #                     "tradeNo": "16697179457897791471461000223437",
        #                     "amount": "7970.57815028"
        #                 }
        #             ]
        #         },
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        data = self.safe_value(response, 'attachment')
        trades = self.safe_value(data, 'trades')
        if trades is None:
            return trades
        return self.parse_trades(trades, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---trade-list
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the ace api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.safe_market(symbol)
        request = {
            # 'buyOrSell': 1,
            # 'start': 0,
        }
        if market['id'] is not None:
            request['quoteCurrencyId'] = market['quoteId']
            request['baseCurrencyId'] = market['baseId']
        if limit is not None:
            request['size'] = limit  # default 10, max 500
        response = self.privatePostV2OrderGetTradeList(self.extend(request, params))
        #
        #     {
        #         "attachment": [
        #             {
        #                 "buyOrSell": 1,
        #                 "orderNo": "16708156853695560053601100247906",
        #                 "num": "1",
        #                 "price": "16895",
        #                 "orderAmount": "16895",
        #                 "tradeNum": "0.1",
        #                 "tradePrice": "16895",
        #                 "tradeAmount": "1689.5",
        #                 "fee": "0",
        #                 "feeSave": "0",
        #                 "status": 1,
        #                 "isSelf": False,
        #                 "tradeNo": "16708186395087940051961000274150",
        #                 "tradeTime": "2022-12-12 12:17:19",
        #                 "tradeTimestamp": 1670818639508,
        #                 "quoteCurrencyId": 14,
        #                 "quoteCurrencyName": "USDT",
        #                 "baseCurrencyId": 2,
        #                 "baseCurrencyName": "BTC"
        #             }
        #         ],
        #         "message": null,
        #         "parameters": null,
        #         "status": 200
        #     }
        #
        trades = self.safe_value(response, 'attachment', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_balance(self, response):
        #
        #     [
        #         {
        #             "currencyId": 4,
        #             "amount": 6.896,
        #             "cashAmount": 6.3855,
        #             "uid": 123,
        #             "currencyName": "BTC"
        #         }
        #     ]
        #
        result = {
            'info': response,
        }
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currencyName')
            code = self.safe_currency_code(currencyId)
            amount = self.safe_string(balance, 'amount')
            available = self.safe_string(balance, 'cashAmount')
            account = {
                'free': available,
                'total': amount,
            }
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        see https://github.com/ace-exchange/ace-official-api-docs/blob/master/api_v2.md#open-api---account-balance
        :param dict params: extra parameters specific to the ace api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        response = self.privatePostV2CoinCustomerAccount(params)
        balances = self.safe_value(response, 'attachment', [])
        #
        #     {
        #         "attachment":[
        #             {
        #                 "currencyId": 4,
        #                 "amount": 6.896,
        #                 "cashAmount": 6.3855,
        #                 "uid": 123,
        #                 "currencyName": "BTC"
        #             }
        #         ],
        #         message: null,
        #         parameters: null,
        #         status: '200'
        #     }
        #
        return self.parse_balance(balances)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if headers is None:
            headers = {}
        if api == 'private':
            self.check_required_credentials()
            nonce = self.milliseconds()
            auth = 'ACE_SIGN' + self.secret
            data = self.extend({
                'apiKey': self.apiKey,
                'timeStamp': nonce,
            }, params)
            dataKeys = list(data.keys())
            sortedDataKeys = self.sort_by(dataKeys, 0)
            for i in range(0, len(sortedDataKeys)):
                key = sortedDataKeys[i]
                auth += self.safe_string(data, key)
            signature = self.hash(self.encode(auth), 'sha256', 'hex')
            data['signKey'] = signature
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            }
            if method == 'POST':
                brokerId = self.safe_string(self.options, 'brokerId')
                if brokerId is not None:
                    headers['Referer'] = brokerId
            body = self.urlencode(data)
        elif api == 'public' and method == 'GET':
            if query:
                url += '?' + self.urlencode(query)
        url = self.urls['api'][api] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to the default error handler
        feedback = self.id + ' ' + body
        status = self.safe_number(response, 'status', 200)
        if status > 200:
            self.throw_exactly_matched_exception(self.exceptions['exact'], status, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], status, feedback)
