# -*- 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 json
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import OrderNotFound
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class ndax(Exchange):

    def describe(self):
        return self.deep_extend(super(ndax, self).describe(), {
            'id': 'ndax',
            'name': 'NDAX',
            'countries': ['US'],  # United States
            'rateLimit': 1000,
            'pro': True,
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createOrder': True,
                'editOrder': True,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderTrades': True,
                'fetchTicker': True,
                'fetchTrades': True,
            },
            'timeframes': {
                '1m': '60',
                '5m': '300',
                '15m': '900',
                '30m': '1800',
                '1h': '3600',
                '2h': '7200',
                '4h': '14400',
                '6h': '21600',
                '12h': '43200',
                '1d': '86400',
                '1w': '604800',
                '1M': '2419200',
                '4M': '9676800',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/108623144-67a3ef00-744e-11eb-8140-75c6b851e945.jpg',
                'test': {
                    'public': 'https://ndaxmarginstaging.cdnhop.net:8443/AP',
                    'private': 'https://ndaxmarginstaging.cdnhop.net:8443/AP',
                },
                'api': {
                    'public': 'https://api.ndax.io:8443/AP',
                    'private': 'https://api.ndax.io:8443/AP',
                },
                'www': 'https://ndax.io',
                'doc': [
                    'https://apidoc.ndax.io/',
                ],
                'fees': 'https://ndax.io/fees',
                'referral': 'https://one.ndax.io/bfQiSL',
            },
            'api': {
                'public': {
                    'get': [
                        'Activate2FA',
                        'Authenticate2FA',
                        'AuthenticateUser',
                        'GetL2Snapshot',
                        'GetLevel1',
                        'GetValidate2FARequiredEndpoints',
                        'LogOut',
                        'GetTickerHistory',
                        'GetProduct',
                        'GetProducts',
                        'GetInstrument',
                        'GetInstruments',
                        'Ping',
                        'trades',  # undocumented
                        'GetLastTrades',  # undocumented
                        'SubscribeLevel1',
                        'SubscribeLevel2',
                        'SubscribeTicker',
                        'SubscribeTrades',
                        'SubscribeBlockTrades',
                        'UnsubscribeBlockTrades',
                        'UnsubscribeLevel1',
                        'UnsubscribeLevel2',
                        'UnsubscribeTicker',
                        'UnsubscribeTrades',
                        'Authenticate',  # undocumented
                    ],
                },
                'private': {
                    'get': [
                        'GetUserAccountInfos',
                        'GetUserAccounts',
                        'GetUserAffiliateCount',
                        'GetUserAffiliateTag',
                        'GetUserConfig',
                        'GetAllUnredactedUserConfigsForUser',
                        'GetUnredactedUserConfigByKey',
                        'GetUserDevices',
                        'GetUserReportTickets',
                        'GetUserReportWriterResultRecords',
                        'GetAccountInfo',
                        'GetAccountPositions',
                        'GetAllAccountConfigs',
                        'GetTreasuryProductsForAccount',
                        'GetAccountTrades',
                        'GetAccountTransactions',
                        'GetOpenTradeReports',
                        'GetAllOpenTradeReports',
                        'GetTradesHistory',
                        'GetOpenOrders',
                        'GetOpenQuotes',
                        'GetOrderFee',
                        'GetOrderHistory',
                        'GetOrdersHistory',
                        'GetOrderStatus',
                        'GetOmsFeeTiers',
                        'GetAccountDepositTransactions',
                        'GetAccountWithdrawTransactions',
                        'GetAllDepositRequestInfoTemplates',
                        'GetDepositInfo',
                        'GetDepositRequestInfoTemplate',
                        'GetDeposits',
                        'GetDepositTicket',
                        'GetDepositTickets',
                        'GetOMSWithdrawFees',
                        'GetWithdrawFee',
                        'GetWithdraws',
                        'GetWithdrawTemplate',
                        'GetWithdrawTemplateTypes',
                        'GetWithdrawTicket',
                        'GetWithdrawTickets',
                    ],
                    'post': [
                        'AddUserAffiliateTag',
                        'CancelUserReport',
                        'RegisterNewDevice',
                        'SubscribeAccountEvents',
                        'UpdateUserAffiliateTag',
                        'GenerateTradeActivityReport',
                        'GenerateTransactionActivityReport',
                        'GenerateTreasuryActivityReport',
                        'ScheduleTradeActivityReport',
                        'ScheduleTransactionActivityReport',
                        'ScheduleTreasuryActivityReport',
                        'CancelAllOrders',
                        'CancelOrder',
                        'CancelQuote',
                        'CancelReplaceOrder',
                        'CreateQuote',
                        'ModifyOrder',
                        'SendOrder',
                        'SubmitBlockTrade',
                        'UpdateQuote',
                        'CancelWithdraw',
                        'CreateDepositTicket',
                        'CreateWithdrawTicket',
                        'SubmitDepositTicketComment',
                        'SubmitWithdrawTicketComment',
                        'GetOrderHistoryByOrderId',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.2 / 100,
                    'taker': 0.25 / 100,
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'uid': True,
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    'Not_Enough_Funds': InsufficientFunds,  # {"status":"Rejected","errormsg":"Not_Enough_Funds","errorcode":101}
                    'Server Error': ExchangeError,  # {"result":false,"errormsg":"Server Error","errorcode":102,"detail":null}
                    'Resource Not Found': OrderNotFound,  # {"result":false,"errormsg":"Resource Not Found","errorcode":104,"detail":null}
                },
                'broad': {
                    'Invalid InstrumentId': BadSymbol,  # {"result":false,"errormsg":"Invalid InstrumentId: 10000","errorcode":100,"detail":null}
                },
            },
            'options': {
                'omsId': 1,
                'orderTypes': {
                    'Market': 1,
                    'Limit': 2,
                    'StopMarket': 3,
                    'StopLimit': 4,
                    'TrailingStopMarket': 5,
                    'TrailingStopLimit': 6,
                    'BlockTrade': 7,
                },
            },
        })

    async def fetch_currencies(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        request = {
            'omsId': omsId,
        }
        response = await self.publicGetGetProducts(self.extend(request, params))
        #
        #     [
        #         {
        #             "OMSId":1,
        #             "ProductId":1,
        #             "Product":"BTC",
        #             "ProductFullName":"Bitcoin",
        #             "ProductType":"CryptoCurrency",
        #             "DecimalPlaces":8,
        #             "TickSize":0.0000000100000000000000000000,
        #             "NoFees":false,
        #             "IsDisabled":false,
        #             "MarginEnabled":false
        #         },
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'ProductId')
            name = self.safe_string(currency, 'ProductFullName')
            type = self.safe_string(currency, 'ProductType')
            code = self.safe_currency_code(self.safe_string(currency, 'Product'))
            precision = self.safe_number(currency, 'TickSize')
            isDisabled = self.safe_value(currency, 'IsDisabled')
            active = not isDisabled
            result[code] = {
                'id': id,
                'name': name,
                'code': code,
                'type': type,
                'precision': precision,
                'info': currency,
                'active': active,
                'fee': None,
                'limits': self.limits,
            }
        return result

    async def fetch_markets(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        request = {
            'omsId': omsId,
        }
        response = await self.publicGetGetInstruments(self.extend(request, params))
        #
        #     [
        #         {
        #             "OMSId":1,
        #             "InstrumentId":3,
        #             "Symbol":"LTCBTC",
        #             "Product1":3,
        #             "Product1Symbol":"LTC",
        #             "Product2":1,
        #             "Product2Symbol":"BTC",
        #             "InstrumentType":"Standard",
        #             "VenueInstrumentId":3,
        #             "VenueId":1,
        #             "SortIndex":0,
        #             "SessionStatus":"Running",
        #             "PreviousSessionStatus":"Stopped",
        #             "SessionStatusDateTime":"2020-11-25T19:42:15.245Z",
        #             "SelfTradePrevention":true,
        #             "QuantityIncrement":0.0000000100000000000000000000,
        #             "PriceIncrement":0.0000000100000000000000000000,
        #             "MinimumQuantity":0.0100000000000000000000000000,
        #             "MinimumPrice":0.0000010000000000000000000000,
        #             "VenueSymbol":"LTCBTC",
        #             "IsDisable":false,
        #             "MasterDataId":0,
        #             "PriceCollarThreshold":0.0000000000000000000000000000,
        #             "PriceCollarPercent":0.0000000000000000000000000000,
        #             "PriceCollarEnabled":false,
        #             "PriceFloorLimit":0.0000000000000000000000000000,
        #             "PriceFloorLimitEnabled":false,
        #             "PriceCeilingLimit":0.0000000000000000000000000000,
        #             "PriceCeilingLimitEnabled":false,
        #             "CreateWithMarketRunning":true,
        #             "AllowOnlyMarketMakerCounterParty":false,
        #             "PriceCollarIndexDifference":0.0000000000000000000000000000,
        #             "PriceCollarConvertToOtcEnabled":false,
        #             "PriceCollarConvertToOtcClientUserId":0,
        #             "PriceCollarConvertToOtcAccountId":0,
        #             "PriceCollarConvertToOtcThreshold":0.0000000000000000000000000000,
        #             "OtcConvertSizeThreshold":0.0000000000000000000000000000,
        #             "OtcConvertSizeEnabled":false,
        #             "OtcTradesPublic":true,
        #             "PriceTier":0
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'InstrumentId')
            # lowercaseId = self.safe_string_lower(market, 'symbol')
            baseId = self.safe_string(market, 'Product1')
            quoteId = self.safe_string(market, 'Product2')
            base = self.safe_currency_code(self.safe_string(market, 'Product1Symbol'))
            quote = self.safe_currency_code(self.safe_string(market, 'Product2Symbol'))
            symbol = base + '/' + quote
            precision = {
                'amount': self.safe_number(market, 'QuantityIncrement'),
                'price': self.safe_number(market, 'PriceIncrement'),
            }
            sessionStatus = self.safe_string(market, 'SessionStatus')
            isDisable = self.safe_value(market, 'IsDisable')
            sessionRunning = (sessionStatus == 'Running')
            active = True if (sessionRunning and not isDisable) else False
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'info': market,
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_number(market, 'MinimumQuantity'),
                        'max': None,
                    },
                    'price': {
                        'min': self.safe_number(market, 'MinimumPrice'),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    def parse_order_book(self, orderbook, symbol, timestamp=None, bidsKey='bids', asksKey='asks', priceKey=6, amountKey=8):
        nonce = None
        result = {
            'symbol': symbol,
            'bids': [],
            'asks': [],
            'timestamp': None,
            'datetime': None,
            'nonce': None,
        }
        for i in range(0, len(orderbook)):
            level = orderbook[i]
            if timestamp is None:
                timestamp = self.safe_integer(level, 2)
            else:
                newTimestamp = self.safe_integer(level, 2)
                timestamp = max(timestamp, newTimestamp)
            if nonce is None:
                nonce = self.safe_integer(level, 0)
            else:
                newNonce = self.safe_integer(level, 0)
                nonce = max(nonce, newNonce)
            bidask = self.parse_bid_ask(level, priceKey, amountKey)
            levelSide = self.safe_integer(level, 9)
            side = asksKey if levelSide else bidsKey
            result[side].append(bidask)
        result['bids'] = self.sort_by(result['bids'], 0, True)
        result['asks'] = self.sort_by(result['asks'], 0)
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        result['nonce'] = nonce
        return result

    async def fetch_order_book(self, symbol, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        market = self.market(symbol)
        limit = 100 if (limit is None) else limit  # default 100
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
            'Depth': limit,  # default 100
        }
        response = await self.publicGetGetL2Snapshot(self.extend(request, params))
        #
        #     [
        #         [
        #             0,   # 0 MDUpdateId
        #             1,   # 1 Number of Unique Accounts
        #             123,  # 2 ActionDateTime in Posix format X 1000
        #             0,   # 3 ActionType 0(New), 1(Update), 2(Delete)
        #             0.0,  # 4 LastTradePrice
        #             0,   # 5 Number of Orders
        #             0.0,  # 6 Price
        #             0,   # 7 ProductPairCode
        #             0.0,  # 8 Quantity
        #             0,   # 9 Side
        #         ],
        #         [97244115,1,1607456142963,0,19069.32,1,19069.31,8,0.140095,0],
        #         [97244115,0,1607456142963,0,19069.32,1,19068.64,8,0.0055,0],
        #         [97244115,0,1607456142963,0,19069.32,1,19068.26,8,0.021291,0],
        #         [97244115,1,1607456142964,0,19069.32,1,19069.32,8,0.099636,1],
        #         [97244115,0,1607456142964,0,19069.32,1,19069.98,8,0.1,1],
        #         [97244115,0,1607456142964,0,19069.32,1,19069.99,8,0.141604,1],
        #     ]
        #
        return self.parse_order_book(response, symbol)

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "OMSId":1,
        #         "InstrumentId":8,
        #         "BestBid":19069.31,
        #         "BestOffer":19069.32,
        #         "LastTradedPx":19069.32,
        #         "LastTradedQty":0.0001,
        #         "LastTradeTime":1607040406424,
        #         "SessionOpen":19069.32,
        #         "SessionHigh":19069.32,
        #         "SessionLow":19069.32,
        #         "SessionClose":19069.32,
        #         "Volume":0.0001,
        #         "CurrentDayVolume":0.0001,
        #         "CurrentDayNotional":1.906932,
        #         "CurrentDayNumTrades":1,
        #         "CurrentDayPxChange":0.00,
        #         "Rolling24HrVolume":0.000000000000000000000000000,
        #         "Rolling24HrNotional":0.00000000000000000000000,
        #         "Rolling24NumTrades":0,
        #         "Rolling24HrPxChange":0,
        #         "TimeStamp":"1607040406425",
        #         "BidQty":0,
        #         "AskQty":0,
        #         "BidOrderCt":0,
        #         "AskOrderCt":0,
        #         "Rolling24HrPxChangePercent":0,
        #     }
        #
        timestamp = self.safe_integer(ticker, 'TimeStamp')
        marketId = self.safe_string(ticker, 'InstrumentId')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_number(ticker, 'LastTradedPx')
        percentage = self.safe_number(ticker, 'Rolling24HrPxChangePercent')
        change = self.safe_number(ticker, 'Rolling24HrPxChange')
        open = self.safe_number(ticker, 'SessionOpen')
        average = None
        if (last is not None) and (change is not None):
            average = self.sum(last, open) / 2
        baseVolume = self.safe_number(ticker, 'Rolling24HrVolume')
        quoteVolume = self.safe_number(ticker, 'Rolling24HrNotional')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'SessionHigh'),
            'low': self.safe_number(ticker, 'SessionLow'),
            'bid': self.safe_number(ticker, 'BestBid'),
            'bidVolume': None,  # self.safe_number(ticker, 'BidQty'), always shows 0
            'ask': self.safe_number(ticker, 'BestOffer'),
            'askVolume': None,  # self.safe_number(ticker, 'AskQty'), always shows 0
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
        }
        response = await self.publicGetGetLevel1(self.extend(request, params))
        #
        #     {
        #         "OMSId":1,
        #         "InstrumentId":8,
        #         "BestBid":19069.31,
        #         "BestOffer":19069.32,
        #         "LastTradedPx":19069.32,
        #         "LastTradedQty":0.0001,
        #         "LastTradeTime":1607040406424,
        #         "SessionOpen":19069.32,
        #         "SessionHigh":19069.32,
        #         "SessionLow":19069.32,
        #         "SessionClose":19069.32,
        #         "Volume":0.0001,
        #         "CurrentDayVolume":0.0001,
        #         "CurrentDayNotional":1.906932,
        #         "CurrentDayNumTrades":1,
        #         "CurrentDayPxChange":0.00,
        #         "Rolling24HrVolume":0.000000000000000000000000000,
        #         "Rolling24HrNotional":0.00000000000000000000000,
        #         "Rolling24NumTrades":0,
        #         "Rolling24HrPxChange":0,
        #         "TimeStamp":"1607040406425",
        #         "BidQty":0,
        #         "AskQty":0,
        #         "BidOrderCt":0,
        #         "AskOrderCt":0,
        #         "Rolling24HrPxChangePercent":0,
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1501603632000,  # 0 DateTime
        #         2700.33,       # 1 High
        #         2687.01,       # 2 Low
        #         2687.01,       # 3 Open
        #         2687.01,       # 4 Close
        #         24.86100992,   # 5 Volume
        #         0,             # 6 Inside Bid Price
        #         2870.95,       # 7 Inside Ask Price
        #         1              # 8 InstrumentId
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
            'Interval': self.timeframes[timeframe],
        }
        duration = self.parse_timeframe(timeframe)
        now = self.milliseconds()
        if since is None:
            if limit is not None:
                request['FromDate'] = self.ymd(now - duration * limit * 1000)
                request['ToDate'] = self.ymd(now)
        else:
            request['FromDate'] = self.ymd(since)
            if limit is None:
                request['ToDate'] = self.ymd(now)
            else:
                request['ToDate'] = self.ymd(self.sum(since, duration * limit * 1000))
        response = await self.publicGetGetTickerHistory(self.extend(request, params))
        #
        #     [
        #         [1607299260000,19069.32,19069.32,19069.32,19069.32,0,19069.31,19069.32,8,1607299200000],
        #         [1607299320000,19069.32,19069.32,19069.32,19069.32,0,19069.31,19069.32,8,1607299260000],
        #         [1607299380000,19069.32,19069.32,19069.32,19069.32,0,19069.31,19069.32,8,1607299320000],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     [
        #         6913253,       #  0 TradeId
        #         8,             #  1 ProductPairCode
        #         0.03340802,    #  2 Quantity
        #         19116.08,      #  3 Price
        #         2543425077,    #  4 Order1
        #         2543425482,    #  5 Order2
        #         1606935922416,  #  6 Tradetime
        #         0,             #  7 Direction
        #         1,             #  8 TakerSide
        #         0,             #  9 BlockTrade
        #         0,             # 10 Either Order1ClientId or Order2ClientId
        #     ]
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "OMSId":1,
        #         "ExecutionId":16916567,
        #         "TradeId":14476351,
        #         "OrderId":2543565231,
        #         "AccountId":449,
        #         "AccountName":"igor@ccxt.trade",
        #         "SubAccountId":0,
        #         "ClientOrderId":0,
        #         "InstrumentId":8,
        #         "Side":"Sell",
        #         "OrderType":"Market",
        #         "Quantity":0.1230000000000000000000000000,
        #         "RemainingQuantity":0.0000000000000000000000000000,
        #         "Price":19069.310000000000000000000000,
        #         "Value":2345.5251300000000000000000000,
        #         "CounterParty":"7",
        #         "OrderTradeRevision":1,
        #         "Direction":"NoChange",
        #         "IsBlockTrade":false,
        #         "Fee":1.1727625650000000000000000000,
        #         "FeeProductId":8,
        #         "OrderOriginator":446,
        #         "UserName":"igor@ccxt.trade",
        #         "TradeTimeMS":1607565031569,
        #         "MakerTaker":"Taker",
        #         "AdapterTradeId":0,
        #         "InsideBid":19069.310000000000000000000000,
        #         "InsideBidSize":0.2400950000000000000000000000,
        #         "InsideAsk":19069.320000000000000000000000,
        #         "InsideAskSize":0.0997360000000000000000000000,
        #         "IsQuote":false,
        #         "CounterPartyClientUserId":1,
        #         "NotionalProductId":2,
        #         "NotionalRate":1.0000000000000000000000000000,
        #         "NotionalValue":2345.5251300000000000000000000,
        #         "NotionalHoldAmount":0,
        #         "TradeTime":637431618315686826
        #     }
        #
        # fetchOrderTrades
        #
        #     {
        #         "Side":"Sell",
        #         "OrderId":2543565235,
        #         "Price":18600.000000000000000000000000,
        #         "Quantity":0.0000000000000000000000000000,
        #         "DisplayQuantity":0.0000000000000000000000000000,
        #         "Instrument":8,
        #         "Account":449,
        #         "AccountName":"igor@ccxt.trade",
        #         "OrderType":"Limit",
        #         "ClientOrderId":0,
        #         "OrderState":"FullyExecuted",
        #         "ReceiveTime":1607585844956,
        #         "ReceiveTimeTicks":637431826449564182,
        #         "LastUpdatedTime":1607585844959,
        #         "LastUpdatedTimeTicks":637431826449593893,
        #         "OrigQuantity":0.1230000000000000000000000000,
        #         "QuantityExecuted":0.1230000000000000000000000000,
        #         "GrossValueExecuted":2345.3947500000000000000000000,
        #         "ExecutableValue":0.0000000000000000000000000000,
        #         "AvgPrice":19068.250000000000000000000000,
        #         "CounterPartyId":0,
        #         "ChangeReason":"Trade",
        #         "OrigOrderId":2543565235,
        #         "OrigClOrdId":0,
        #         "EnteredBy":446,
        #         "UserName":"igor@ccxt.trade",
        #         "IsQuote":false,
        #         "InsideAsk":19069.320000000000000000000000,
        #         "InsideAskSize":0.0997360000000000000000000000,
        #         "InsideBid":19068.250000000000000000000000,
        #         "InsideBidSize":1.3300010000000000000000000000,
        #         "LastTradePrice":19068.250000000000000000000000,
        #         "RejectReason":"",
        #         "IsLockedIn":false,
        #         "CancelReason":"",
        #         "OrderFlag":"0",
        #         "UseMargin":false,
        #         "StopPrice":0.0000000000000000000000000000,
        #         "PegPriceType":"Unknown",
        #         "PegOffset":0.0000000000000000000000000000,
        #         "PegLimitOffset":0.0000000000000000000000000000,
        #         "IpAddress":"5.228.233.138",
        #         "ClientOrderIdUuid":null,
        #         "OMSId":1
        #     }
        #
        priceString = None
        amountString = None
        cost = None
        timestamp = None
        id = None
        marketId = None
        side = None
        orderId = None
        takerOrMaker = None
        fee = None
        type = None
        if isinstance(trade, list):
            priceString = self.safe_string(trade, 3)
            amountString = self.safe_string(trade, 2)
            timestamp = self.safe_integer(trade, 6)
            id = self.safe_string(trade, 0)
            marketId = self.safe_integer(trade, 1)
            takerSide = self.safe_value(trade, 8)
            side = 'sell' if takerSide else 'buy'
            orderId = self.safe_string(trade, 4)
        else:
            timestamp = self.safe_integer_2(trade, 'TradeTimeMS', 'ReceiveTime')
            id = self.safe_string(trade, 'TradeId')
            orderId = self.safe_string_2(trade, 'OrderId', 'OrigOrderId')
            marketId = self.safe_string_2(trade, 'InstrumentId', 'Instrument')
            priceString = self.safe_string(trade, 'Price')
            amountString = self.safe_string(trade, 'Quantity')
            cost = self.safe_number_2(trade, 'Value', 'GrossValueExecuted')
            takerOrMaker = self.safe_string_lower(trade, 'MakerTaker')
            side = self.safe_string_lower(trade, 'Side')
            type = self.safe_string_lower(trade, 'OrderType')
            feeCost = self.safe_number(trade, 'Fee')
            if feeCost is not None:
                feeCurrencyId = self.safe_string(trade, 'FeeProductId')
                feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
                fee = {
                    'cost': feeCost,
                    'currency': feeCurrencyCode,
                }
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        if cost is None:
            cost = self.parse_number(Precise.string_mul(priceString, amountString))
        symbol = self.safe_symbol(marketId, market)
        return {
            'info': trade,
            'id': id,
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
        }
        if limit is not None:
            request['Count'] = limit
        response = await self.publicGetGetLastTrades(self.extend(request, params))
        #
        #     [
        #         [6913253,8,0.03340802,19116.08,2543425077,2543425482,1606935922416,0,1,0,0],
        #         [6913254,8,0.01391671,19117.42,2543427510,2543427811,1606935927998,1,1,0,0],
        #         [6913255,8,0.000006,19107.81,2543430495,2543430793,1606935933881,2,0,0,0],
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_accounts(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.check_required_credentials()
        request = {
            'omsId': omsId,
            'UserId': self.uid,
            'UserName': 'igor@ccxt.trade',
        }
        response = await self.privateGetGetUserAccounts(self.extend(request, params))
        #
        #     [449]  # comma-separated list of account ids
        #
        result = []
        for i in range(0, len(response)):
            accountId = self.safe_string(response, i)
            result.append({
                'id': accountId,
                'type': None,
                'currency': None,
                'info': accountId,
            })
        return result

    async def fetch_balance(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        response = await self.privateGetGetAccountPositions(self.extend(request, params))
        #
        #     [
        #         {
        #             "OMSId":1,
        #             "AccountId":449,
        #             "ProductSymbol":"BTC",
        #             "ProductId":1,
        #             "Amount":10.000000000000000000000000000,
        #             "Hold":0,
        #             "PendingDeposits":0.0000000000000000000000000000,
        #             "PendingWithdraws":0.0000000000000000000000000000,
        #             "TotalDayDeposits":10.000000000000000000000000000,
        #             "TotalMonthDeposits":10.000000000000000000000000000,
        #             "TotalYearDeposits":10.000000000000000000000000000,
        #             "TotalDayDepositNotional":10.000000000000000000000000000,
        #             "TotalMonthDepositNotional":10.000000000000000000000000000,
        #             "TotalYearDepositNotional":10.000000000000000000000000000,
        #             "TotalDayWithdraws":0,
        #             "TotalMonthWithdraws":0,
        #             "TotalYearWithdraws":0,
        #             "TotalDayWithdrawNotional":0,
        #             "TotalMonthWithdrawNotional":0,
        #             "TotalYearWithdrawNotional":0,
        #             "NotionalProductId":8,
        #             "NotionalProductSymbol":"USDT",
        #             "NotionalValue":10.000000000000000000000000000,
        #             "NotionalHoldAmount":0,
        #             "NotionalRate":1
        #         },
        #     ]
        #
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'ProductId')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(balance, 'Amount')
            account['used'] = self.safe_string(balance, 'Hold')
            result[code] = account
        return self.parse_balance(result, False)

    def parse_ledger_entry_type(self, type):
        types = {
            'Trade': 'trade',
            'Deposit': 'transaction',
            'Withdraw': 'transaction',
            'Transfer': 'transfer',
            'OrderHold': 'trade',
            'WithdrawHold': 'transaction',
            'DepositHold': 'transaction',
            'MarginHold': 'trade',
            'ManualHold': 'trade',
            'ManualEntry': 'trade',
            'MarginAcquisition': 'trade',
            'MarginRelinquish': 'trade',
            'MarginQuoteHold': 'trade',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         "TransactionId":2663709493,
        #         "ReferenceId":68,
        #         "OMSId":1,
        #         "AccountId":449,
        #         "CR":10.000000000000000000000000000,
        #         "DR":0.0000000000000000000000000000,
        #         "Counterparty":3,
        #         "TransactionType":"Other",
        #         "ReferenceType":"Deposit",
        #         "ProductId":1,
        #         "Balance":10.000000000000000000000000000,
        #         "TimeStamp":1607532331591
        #     }
        #
        id = self.safe_string(item, 'TransactionId')
        account = self.safe_string(item, 'AccountId')
        referenceId = self.safe_string(item, 'ReferenceId')
        referenceAccount = self.safe_string(item, 'Counterparty')
        type = self.parse_ledger_entry_type(self.safe_string(item, 'ReferenceType'))
        currencyId = self.safe_string(item, 'ProductId')
        code = self.safe_currency_code(currencyId, currency)
        credit = self.safe_number(item, 'CR')
        debit = self.safe_number(item, 'DR')
        amount = None
        direction = None
        if credit > 0:
            amount = credit
            direction = 'in'
        elif debit > 0:
            amount = debit
            direction = 'out'
        timestamp = self.safe_integer(item, 'TimeStamp')
        before = None
        after = self.safe_number(item, 'Balance')
        if direction == 'out':
            before = self.sum(after, amount)
        elif direction == 'in':
            before = max(0, after - amount)
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': before,
            'after': after,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        if limit is not None:
            request['Depth'] = limit
        response = await self.privateGetGetAccountTransactions(self.extend(request, params))
        #
        #     [
        #         {
        #             "TransactionId":2663709493,
        #             "ReferenceId":68,
        #             "OMSId":1,
        #             "AccountId":449,
        #             "CR":10.000000000000000000000000000,
        #             "DR":0.0000000000000000000000000000,
        #             "Counterparty":3,
        #             "TransactionType":"Other",
        #             "ReferenceType":"Deposit",
        #             "ProductId":1,
        #             "Balance":10.000000000000000000000000000,
        #             "TimeStamp":1607532331591
        #         },
        #     ]
        #
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_ledger(response, currency, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'Accepted': 'open',
            'Rejected': 'rejected',
            'Working': 'open',
            'Canceled': 'canceled',
            'Expired': 'expired',
            'FullyExecuted': 'closed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "status":"Accepted",
        #         "errormsg":"",
        #         "OrderId": 2543565231
        #     }
        #
        # editOrder
        #
        #     {
        #         "ReplacementOrderId": 1234,
        #         "ReplacementClOrdId": 1561,
        #         "OrigOrderId": 5678,
        #         "OrigClOrdId": 91011,
        #     }
        #
        # fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "Side":"Buy",
        #         "OrderId":2543565233,
        #         "Price":19010,
        #         "Quantity":0.345,
        #         "DisplayQuantity":0.345,
        #         "Instrument":8,
        #         "Account":449,
        #         "AccountName":"igor@ccxt.trade",
        #         "OrderType":"Limit",
        #         "ClientOrderId":0,
        #         "OrderState":"Working",
        #         "ReceiveTime":1607579326003,
        #         "ReceiveTimeTicks":637431761260028981,
        #         "LastUpdatedTime":1607579326005,
        #         "LastUpdatedTimeTicks":637431761260054714,
        #         "OrigQuantity":0.345,
        #         "QuantityExecuted":0,
        #         "GrossValueExecuted":0,
        #         "ExecutableValue":0,
        #         "AvgPrice":0,
        #         "CounterPartyId":0,
        #         "ChangeReason":"NewInputAccepted",
        #         "OrigOrderId":2543565233,
        #         "OrigClOrdId":0,
        #         "EnteredBy":446,
        #         "UserName":"igor@ccxt.trade",
        #         "IsQuote":false,
        #         "InsideAsk":19069.32,
        #         "InsideAskSize":0.099736,
        #         "InsideBid":19068.25,
        #         "InsideBidSize":1.330001,
        #         "LastTradePrice":19068.25,
        #         "RejectReason":"",
        #         "IsLockedIn":false,
        #         "CancelReason":"",
        #         "OrderFlag":"AddedToBook",
        #         "UseMargin":false,
        #         "StopPrice":0,
        #         "PegPriceType":"Unknown",
        #         "PegOffset":0,
        #         "PegLimitOffset":0,
        #         "IpAddress":null,
        #         "ClientOrderIdUuid":null,
        #         "OMSId":1
        #     }
        #
        id = self.safe_string_2(order, 'ReplacementOrderId', 'OrderId')
        timestamp = self.safe_integer(order, 'ReceiveTime')
        lastTradeTimestamp = self.safe_integer(order, 'LastUpdatedTime')
        marketId = self.safe_string(order, 'Instrument')
        symbol = self.safe_symbol(marketId, market)
        side = self.safe_string_lower(order, 'Side')
        type = self.safe_string_lower(order, 'OrderType')
        clientOrderId = self.safe_string_2(order, 'ReplacementClOrdId', 'ClientOrderId')
        price = self.safe_number(order, 'Price', 0.0)
        price = price if (price > 0.0) else None
        amount = self.safe_number(order, 'OrigQuantity')
        filled = self.safe_number(order, 'QuantityExecuted')
        cost = self.safe_number(order, 'GrossValueExecuted')
        average = self.safe_number(order, 'AvgPrice', 0.0)
        average = average if (average > 0) else None
        stopPrice = self.safe_number(order, 'StopPrice', 0.0)
        stopPrice = stopPrice if (stopPrice > 0.0) else None
        timeInForce = None
        status = self.parse_order_status(self.safe_string(order, 'OrderState'))
        fee = None
        trades = None
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': None,
            'fee': fee,
            'trades': trades,
        })

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        clientOrderId = self.safe_integer_2(params, 'ClientOrderId', 'clientOrderId')
        params = self.omit(params, ['accountId', 'AccountId', 'clientOrderId', 'ClientOrderId'])
        market = self.market(symbol)
        orderSide = 0 if (side == 'buy') else 1
        request = {
            'InstrumentId': int(market['id']),
            'omsId': omsId,
            'AccountId': accountId,
            'TimeInForce': 1,  # 0 Unknown, 1 GTC by default, 2 OPG execute as close to opening price as possible, 3 IOC immediate or canceled,  4 FOK fill-or-kill, 5 GTX good 'til executed, 6 GTD good 'til date
            # 'ClientOrderId': clientOrderId,  # defaults to 0
            # If self order is order A, OrderIdOCO refers to the order ID of an order B(which is not the order being created by self call).
            # If order B executes, then order A created by self call is canceled.
            # You can also set up order B to watch order A in the same way, but that may require an update to order B to make it watch self one, which could have implications for priority in the order book.
            # See CancelReplaceOrder and ModifyOrder.
            # 'OrderIdOCO': 0,  # The order ID if One Cancels the Other.
            # 'UseDisplayQuantity': False,  # If you enter a Limit order with a reserve, you must set UseDisplayQuantity to True
            'Side': orderSide,  # 0 Buy, 1 Sell, 2 Short, 3 unknown an error condition
            'Quantity': float(self.amount_to_precision(symbol, amount)),
            'OrderType': self.safe_integer(self.options['orderTypes'], self.capitalize(type)),  # 0 Unknown, 1 Market, 2 Limit, 3 StopMarket, 4 StopLimit, 5 TrailingStopMarket, 6 TrailingStopLimit, 7 BlockTrade
            # 'PegPriceType': 3,  # 1 Last, 2 Bid, 3 Ask, 4 Midpoint
            # 'LimitPrice': float(self.price_to_precision(symbol, price)),
        }
        # If OrderType=1(Market), Side=0(Buy), and LimitPrice is supplied, the Market order will execute up to the value specified
        if price is not None:
            request['LimitPrice'] = float(self.price_to_precision(symbol, price))
        if clientOrderId is not None:
            request['ClientOrderId'] = clientOrderId
        response = await self.privatePostSendOrder(self.extend(request, params))
        #
        #     {
        #         "status":"Accepted",
        #         "errormsg":"",
        #         "OrderId": 2543565231
        #     }
        #
        return self.parse_order(response, market)

    async def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        clientOrderId = self.safe_integer_2(params, 'ClientOrderId', 'clientOrderId')
        params = self.omit(params, ['accountId', 'AccountId', 'clientOrderId', 'ClientOrderId'])
        market = self.market(symbol)
        orderSide = 0 if (side == 'buy') else 1
        request = {
            'OrderIdToReplace': int(id),
            'InstrumentId': int(market['id']),
            'omsId': omsId,
            'AccountId': accountId,
            'TimeInForce': 1,  # 0 Unknown, 1 GTC by default, 2 OPG execute as close to opening price as possible, 3 IOC immediate or canceled,  4 FOK fill-or-kill, 5 GTX good 'til executed, 6 GTD good 'til date
            # 'ClientOrderId': clientOrderId,  # defaults to 0
            # If self order is order A, OrderIdOCO refers to the order ID of an order B(which is not the order being created by self call).
            # If order B executes, then order A created by self call is canceled.
            # You can also set up order B to watch order A in the same way, but that may require an update to order B to make it watch self one, which could have implications for priority in the order book.
            # See CancelReplaceOrder and ModifyOrder.
            # 'OrderIdOCO': 0,  # The order ID if One Cancels the Other.
            # 'UseDisplayQuantity': False,  # If you enter a Limit order with a reserve, you must set UseDisplayQuantity to True
            'Side': orderSide,  # 0 Buy, 1 Sell, 2 Short, 3 unknown an error condition
            'Quantity': float(self.amount_to_precision(symbol, amount)),
            'OrderType': self.safe_integer(self.options['orderTypes'], self.capitalize(type)),  # 0 Unknown, 1 Market, 2 Limit, 3 StopMarket, 4 StopLimit, 5 TrailingStopMarket, 6 TrailingStopLimit, 7 BlockTrade
            # 'PegPriceType': 3,  # 1 Last, 2 Bid, 3 Ask, 4 Midpoint
            # 'LimitPrice': float(self.price_to_precision(symbol, price)),
        }
        # If OrderType=1(Market), Side=0(Buy), and LimitPrice is supplied, the Market order will execute up to the value specified
        if price is not None:
            request['LimitPrice'] = float(self.price_to_precision(symbol, price))
        if clientOrderId is not None:
            request['ClientOrderId'] = clientOrderId
        response = await self.privatePostCancelReplaceOrder(self.extend(request, params))
        #
        #     {
        #         "replacementOrderId": 1234,
        #         "replacementClOrdId": 1561,
        #         "origOrderId": 5678,
        #         "origClOrdId": 91011,
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        request = {
            'omsId': omsId,
            'AccountId': accountId,
            # 'InstrumentId': market['id'],
            # 'TradeId': 123,  # If you specify TradeId, GetTradesHistory can return all states for a single trade
            # 'OrderId': 456,  # If specified, the call returns all trades associated with the order
            # 'UserId': integer. The ID of the logged-in user. If not specified, the call returns trades associated with the users belonging to the default account for the logged-in user of self OMS.
            # 'StartTimeStamp': long integer. The historical date and time at which to begin the trade report, in POSIX format. If not specified, reverts to the start date of self account on the trading venue.
            # 'EndTimeStamp': long integer. Date at which to end the trade report, in POSIX format.
            # 'Depth': integer. In self case, the count of trades to return, counting from the StartIndex. If Depth is not specified, returns all trades between BeginTimeStamp and EndTimeStamp, beginning at StartIndex.
            # 'StartIndex': 0  # from the most recent trade 0 and moving backwards in time
            # 'ExecutionId': 123,  # The ID of the individual buy or sell execution. If not specified, returns all.
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['InstrumentId'] = market['id']
        if since is not None:
            request['StartTimeStamp'] = int(since / 1000)
        if limit is not None:
            request['Depth'] = limit
        response = await self.privateGetGetTradesHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             "OMSId":1,
        #             "ExecutionId":16916567,
        #             "TradeId":14476351,
        #             "OrderId":2543565231,
        #             "AccountId":449,
        #             "AccountName":"igor@ccxt.trade",
        #             "SubAccountId":0,
        #             "ClientOrderId":0,
        #             "InstrumentId":8,
        #             "Side":"Sell",
        #             "OrderType":"Market",
        #             "Quantity":0.1230000000000000000000000000,
        #             "RemainingQuantity":0.0000000000000000000000000000,
        #             "Price":19069.310000000000000000000000,
        #             "Value":2345.5251300000000000000000000,
        #             "CounterParty":"7",
        #             "OrderTradeRevision":1,
        #             "Direction":"NoChange",
        #             "IsBlockTrade":false,
        #             "Fee":1.1727625650000000000000000000,
        #             "FeeProductId":8,
        #             "OrderOriginator":446,
        #             "UserName":"igor@ccxt.trade",
        #             "TradeTimeMS":1607565031569,
        #             "MakerTaker":"Taker",
        #             "AdapterTradeId":0,
        #             "InsideBid":19069.310000000000000000000000,
        #             "InsideBidSize":0.2400950000000000000000000000,
        #             "InsideAsk":19069.320000000000000000000000,
        #             "InsideAskSize":0.0997360000000000000000000000,
        #             "IsQuote":false,
        #             "CounterPartyClientUserId":1,
        #             "NotionalProductId":2,
        #             "NotionalRate":1.0000000000000000000000000000,
        #             "NotionalValue":2345.5251300000000000000000000,
        #             "NotionalHoldAmount":0,
        #             "TradeTime":637431618315686826
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def cancel_all_orders(self, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['IntrumentId'] = market['id']
        response = await self.privatePostCancelAllOrders(self.extend(request, params))
        #
        #     {
        #         "result":true,
        #         "errormsg":null,
        #         "errorcode":0,
        #         "detail":null
        #     }
        #
        return response

    async def cancel_order(self, id, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        # defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        # accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        # params = self.omit(params, ['accountId', 'AccountId'])
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'omsId': omsId,
            # 'AccountId': accountId,
        }
        clientOrderId = self.safe_integer_2(params, 'clientOrderId', 'ClOrderId')
        if clientOrderId is not None:
            request['ClOrderId'] = clientOrderId
        else:
            request['OrderId'] = int(id)
        params = self.omit(params, ['clientOrderId', 'ClOrderId'])
        response = await self.privatePostCancelOrder(self.extend(request, params))
        order = self.parse_order(response, market)
        return self.extend(order, {
            'id': id,
            'clientOrderId': clientOrderId,
        })

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        response = await self.privateGetGetOpenOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "Side":"Buy",
        #             "OrderId":2543565233,
        #             "Price":19010,
        #             "Quantity":0.345,
        #             "DisplayQuantity":0.345,
        #             "Instrument":8,
        #             "Account":449,
        #             "AccountName":"igor@ccxt.trade",
        #             "OrderType":"Limit",
        #             "ClientOrderId":0,
        #             "OrderState":"Working",
        #             "ReceiveTime":1607579326003,
        #             "ReceiveTimeTicks":637431761260028981,
        #             "LastUpdatedTime":1607579326005,
        #             "LastUpdatedTimeTicks":637431761260054714,
        #             "OrigQuantity":0.345,
        #             "QuantityExecuted":0,
        #             "GrossValueExecuted":0,
        #             "ExecutableValue":0,
        #             "AvgPrice":0,
        #             "CounterPartyId":0,
        #             "ChangeReason":"NewInputAccepted",
        #             "OrigOrderId":2543565233,
        #             "OrigClOrdId":0,
        #             "EnteredBy":446,
        #             "UserName":"igor@ccxt.trade",
        #             "IsQuote":false,
        #             "InsideAsk":19069.32,
        #             "InsideAskSize":0.099736,
        #             "InsideBid":19068.25,
        #             "InsideBidSize":1.330001,
        #             "LastTradePrice":19068.25,
        #             "RejectReason":"",
        #             "IsLockedIn":false,
        #             "CancelReason":"",
        #             "OrderFlag":"AddedToBook",
        #             "UseMargin":false,
        #             "StopPrice":0,
        #             "PegPriceType":"Unknown",
        #             "PegOffset":0,
        #             "PegLimitOffset":0,
        #             "IpAddress":null,
        #             "ClientOrderIdUuid":null,
        #             "OMSId":1
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        request = {
            'omsId': omsId,
            'AccountId': accountId,
            # 'ClientOrderId': clientOrderId,
            # 'OriginalOrderId': id,
            # 'OriginalClientOrderId': long integer,
            # 'UserId': integer,
            # 'InstrumentId': market['id'],
            # 'StartTimestamp': since,
            # 'EndTimestamp': self.milliseconds(),
            # 'Depth': limit,
            # 'StartIndex': 0,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['InstrumentId'] = market['id']
        if since is not None:
            request['StartTimeStamp'] = int(since / 1000)
        if limit is not None:
            request['Depth'] = limit
        response = await self.privateGetGetOrdersHistory(self.extend(request, params))
        #
        #     [
        #         {
        #             "Side":"Buy",
        #             "OrderId":2543565233,
        #             "Price":19010.000000000000000000000000,
        #             "Quantity":0.0000000000000000000000000000,
        #             "DisplayQuantity":0.3450000000000000000000000000,
        #             "Instrument":8,
        #             "Account":449,
        #             "AccountName":"igor@ccxt.trade",
        #             "OrderType":"Limit",
        #             "ClientOrderId":0,
        #             "OrderState":"Canceled",
        #             "ReceiveTime":1607579326003,
        #             "ReceiveTimeTicks":637431761260028981,
        #             "LastUpdatedTime":1607580965346,
        #             "LastUpdatedTimeTicks":637431777653463754,
        #             "OrigQuantity":0.3450000000000000000000000000,
        #             "QuantityExecuted":0.0000000000000000000000000000,
        #             "GrossValueExecuted":0.0000000000000000000000000000,
        #             "ExecutableValue":0.0000000000000000000000000000,
        #             "AvgPrice":0.0000000000000000000000000000,
        #             "CounterPartyId":0,
        #             "ChangeReason":"UserModified",
        #             "OrigOrderId":2543565233,
        #             "OrigClOrdId":0,
        #             "EnteredBy":446,
        #             "UserName":"igor@ccxt.trade",
        #             "IsQuote":false,
        #             "InsideAsk":19069.320000000000000000000000,
        #             "InsideAskSize":0.0997360000000000000000000000,
        #             "InsideBid":19068.250000000000000000000000,
        #             "InsideBidSize":1.3300010000000000000000000000,
        #             "LastTradePrice":19068.250000000000000000000000,
        #             "RejectReason":"",
        #             "IsLockedIn":false,
        #             "CancelReason":"UserModified",
        #             "OrderFlag":"AddedToBook, RemovedFromBook",
        #             "UseMargin":false,
        #             "StopPrice":0.0000000000000000000000000000,
        #             "PegPriceType":"Unknown",
        #             "PegOffset":0.0000000000000000000000000000,
        #             "PegLimitOffset":0.0000000000000000000000000000,
        #             "IpAddress":"5.228.233.138",
        #             "ClientOrderIdUuid":null,
        #             "OMSId":1
        #         },
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'omsId': omsId,
            'AccountId': accountId,
            'OrderId': int(id),
        }
        response = await self.privateGetGetOrderStatus(self.extend(request, params))
        #
        #     {
        #         "Side":"Sell",
        #         "OrderId":2543565232,
        #         "Price":0.0000000000000000000000000000,
        #         "Quantity":0.0000000000000000000000000000,
        #         "DisplayQuantity":0.0000000000000000000000000000,
        #         "Instrument":8,
        #         "Account":449,
        #         "AccountName":"igor@ccxt.trade",
        #         "OrderType":"Market",
        #         "ClientOrderId":0,
        #         "OrderState":"FullyExecuted",
        #         "ReceiveTime":1607569475591,
        #         "ReceiveTimeTicks":637431662755912377,
        #         "LastUpdatedTime":1607569475596,
        #         "LastUpdatedTimeTicks":637431662755960902,
        #         "OrigQuantity":1.0000000000000000000000000000,
        #         "QuantityExecuted":1.0000000000000000000000000000,
        #         "GrossValueExecuted":19068.270478610000000000000000,
        #         "ExecutableValue":0.0000000000000000000000000000,
        #         "AvgPrice":19068.270478610000000000000000,
        #         "CounterPartyId":0,
        #         "ChangeReason":"Trade",
        #         "OrigOrderId":2543565232,
        #         "OrigClOrdId":0,
        #         "EnteredBy":446,
        #         "UserName":"igor@ccxt.trade",
        #         "IsQuote":false,
        #         "InsideAsk":19069.320000000000000000000000,
        #         "InsideAskSize":0.0997360000000000000000000000,
        #         "InsideBid":19069.310000000000000000000000,
        #         "InsideBidSize":0.2400950000000000000000000000,
        #         "LastTradePrice":19069.310000000000000000000000,
        #         "RejectReason":"",
        #         "IsLockedIn":false,
        #         "CancelReason":"",
        #         "OrderFlag":"0",
        #         "UseMargin":false,
        #         "StopPrice":0.0000000000000000000000000000,
        #         "PegPriceType":"Unknown",
        #         "PegOffset":0.0000000000000000000000000000,
        #         "PegLimitOffset":0.0000000000000000000000000000,
        #         "IpAddress":"5.228.233.138",
        #         "ClientOrderIdUuid":null,
        #         "OMSId":1
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        # defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        # accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        # params = self.omit(params, ['accountId', 'AccountId'])
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'OMSId': int(omsId),
            # 'AccountId': accountId,
            'OrderId': id,
        }
        response = await self.privatePostGetOrderHistoryByOrderId(self.extend(request, params))
        #
        #     [
        #         {
        #             "Side":"Sell",
        #             "OrderId":2543565235,
        #             "Price":18600.000000000000000000000000,
        #             "Quantity":0.0000000000000000000000000000,
        #             "DisplayQuantity":0.0000000000000000000000000000,
        #             "Instrument":8,
        #             "Account":449,
        #             "AccountName":"igor@ccxt.trade",
        #             "OrderType":"Limit",
        #             "ClientOrderId":0,
        #             "OrderState":"FullyExecuted",
        #             "ReceiveTime":1607585844956,
        #             "ReceiveTimeTicks":637431826449564182,
        #             "LastUpdatedTime":1607585844959,
        #             "LastUpdatedTimeTicks":637431826449593893,
        #             "OrigQuantity":0.1230000000000000000000000000,
        #             "QuantityExecuted":0.1230000000000000000000000000,
        #             "GrossValueExecuted":2345.3947500000000000000000000,
        #             "ExecutableValue":0.0000000000000000000000000000,
        #             "AvgPrice":19068.250000000000000000000000,
        #             "CounterPartyId":0,
        #             "ChangeReason":"Trade",
        #             "OrigOrderId":2543565235,
        #             "OrigClOrdId":0,
        #             "EnteredBy":446,
        #             "UserName":"igor@ccxt.trade",
        #             "IsQuote":false,
        #             "InsideAsk":19069.320000000000000000000000,
        #             "InsideAskSize":0.0997360000000000000000000000,
        #             "InsideBid":19068.250000000000000000000000,
        #             "InsideBidSize":1.3300010000000000000000000000,
        #             "LastTradePrice":19068.250000000000000000000000,
        #             "RejectReason":"",
        #             "IsLockedIn":false,
        #             "CancelReason":"",
        #             "OrderFlag":"0",
        #             "UseMargin":false,
        #             "StopPrice":0.0000000000000000000000000000,
        #             "PegPriceType":"Unknown",
        #             "PegOffset":0.0000000000000000000000000000,
        #             "PegLimitOffset":0.0000000000000000000000000000,
        #             "IpAddress":"5.228.233.138",
        #             "ClientOrderIdUuid":null,
        #             "OMSId":1
        #         },
        #     ]
        #
        grouped = self.group_by(response, 'ChangeReason')
        trades = self.safe_value(grouped, 'Trade', [])
        return self.parse_trades(trades, market, since, limit)

    async def fetch_deposit_address(self, code, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        currency = self.currency(code)
        request = {
            'omsId': omsId,
            'AccountId': accountId,
            'ProductId': currency['id'],
            'GenerateNewKey': False,
        }
        response = await self.privateGetGetDepositInfo(self.extend(request, params))
        #
        #     {
        #         "result":true,
        #         "errormsg":null,
        #         "statuscode":0,
        #         "AssetManagerId":1,
        #         "AccountId":57922,
        #         "AssetId":16,
        #         "ProviderId":23,
        #         "DepositInfo":"[\"0x8A27564b5c30b91C93B1591821642420F323a210\"]"
        #     }
        #
        return self.parse_deposit_address(response, currency)

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        # fetchDepositAddress, createDepositAddress
        #
        #     {
        #         "result":true,
        #         "errormsg":null,
        #         "statuscode":0,
        #         "AssetManagerId":1,
        #         "AccountId":449,
        #         "AssetId":1,
        #         "ProviderId":1,
        #         "DepositInfo":"[\"r3e95RwVsLH7yCbnMfyh7SA8FdwUJCB4S2?memo=241452010\"]"
        #     }
        #
        depositInfoString = self.safe_string(depositAddress, 'DepositInfo')
        depositInfo = json.loads(depositInfoString)
        depositInfoLength = len(depositInfo)
        lastString = self.safe_string(depositInfo, depositInfoLength - 1)
        parts = lastString.split('?memo=')
        address = self.safe_string(parts, 0)
        tag = self.safe_string(parts, 1)
        code = None
        if currency is not None:
            code = currency['code']
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    async def create_deposit_address(self, code, params={}):
        request = {
            'GenerateNewKey': True,
        }
        return await self.fetch_deposit_address(code, self.extend(request, params))

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        currency = None
        if code is not None:
            currency = self.currency(code)
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        response = await self.privateGetGetDeposits(self.extend(request, params))
        #
        #     [
        #         {
        #             "OMSId":1,
        #             "DepositId":44,
        #             "AccountId":449,
        #             "SubAccountId":0,
        #             "ProductId":4,
        #             "Amount":200.00000000000000000000000000,
        #             "LastUpdateTimeStamp":637431291261187806,
        #             "ProductType":"CryptoCurrency",
        #             "TicketStatus":"FullyProcessed",
        #             "DepositInfo":"{}",
        #             "DepositCode":"ab0e23d5-a9ce-4d94-865f-9ab464fb1de3",
        #             "TicketNumber":71,
        #             "NotionalProductId":13,
        #             "NotionalValue":200.00000000000000000000000000,
        #             "FeeAmount":0.0000000000000000000000000000,
        #         },
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        await self.load_markets()
        await self.load_accounts()
        defaultAccountId = self.safe_integer_2(self.options, 'accountId', 'AccountId', int(self.accounts[0]['id']))
        accountId = self.safe_integer_2(params, 'accountId', 'AccountId', defaultAccountId)
        params = self.omit(params, ['accountId', 'AccountId'])
        currency = None
        if code is not None:
            currency = self.currency(code)
        request = {
            'omsId': omsId,
            'AccountId': accountId,
        }
        response = await self.privateGetGetWithdraws(self.extend(request, params))
        #
        #     [
        #         {
        #             "Amount": 0.0,
        #             "FeeAmount": 0.0,
        #             "NotionalValue": 0.0,
        #             "WithdrawId": 0,
        #             "AssetManagerId": 0,
        #             "AccountId": 0,
        #             "AssetId": 0,
        #             "TemplateForm": "{\"TemplateType\": \"TetherRPCWithdraw\",\"Comment\": \"TestWithdraw\",\"ExternalAddress\": \"ms6C3pKAAr8gRCcnVebs8VRkVrjcvqNYv3\"}",
        #             "TemplateFormType": "TetherRPCWithdraw",
        #             "omsId": 0,
        #             "TicketStatus": 0,
        #             "TicketNumber": 0,
        #             "WithdrawTransactionDetails": "",
        #             "WithdrawType": "",
        #             "WithdrawCode": "490b4fa3-53fc-44f4-bd29-7e16be86fba3",
        #             "AssetType": 0,
        #             "Reaccepted": True,
        #             "NotionalProductId": 0
        #         },
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def parse_transaction_status_by_type(self, status, type=None):
        statusesByType = {
            'deposit': {
                'New': 'pending',  # new ticket awaiting operator review
                'AdminProcessing': 'pending',  # an admin is looking at the ticket
                'Accepted': 'pending',  # an admin accepts the ticket
                'Rejected': 'rejected',  # admin rejects the ticket
                'SystemProcessing': 'pending',  # automatic processing; an unlikely status for a deposit
                'FullyProcessed': 'ok',  # the deposit has concluded
                'Failed': 'failed',  # the deposit has failed for some reason
                'Pending': 'pending',  # Account Provider has set status to pending
                'Confirmed': 'pending',  # Account Provider confirms the deposit
                'AmlProcessing': 'pending',  # anti-money-laundering process underway
                'AmlAccepted': 'pending',  # anti-money-laundering process successful
                'AmlRejected': 'rejected',  # deposit did not stand up to anti-money-laundering process
                'AmlFailed': 'failed',  # anti-money-laundering process failed/did not complete
                'LimitsAccepted': 'pending',  # deposit meets limits for fiat or crypto asset
                'LimitsRejected': 'rejected',  # deposit does not meet limits for fiat or crypto asset
            },
            'withdrawal': {
                'New': 'pending',  # awaiting operator review
                'AdminProcessing': 'pending',  # An admin is looking at the ticket
                'Accepted': 'pending',  # withdrawal will proceed
                'Rejected': 'rejected',  # admin or automatic rejection
                'SystemProcessing': 'pending',  # automatic processing underway
                'FullyProcessed': 'ok',  # the withdrawal has concluded
                'Failed': 'failed',  # the withdrawal failed for some reason
                'Pending': 'pending',  # the admin has placed the withdrawal in pending status
                'Pending2Fa': 'pending',  # user must click 2-factor authentication confirmation link
                'AutoAccepted': 'pending',  # withdrawal will be automatically processed
                'Delayed': 'pending',  # waiting for funds to be allocated for the withdrawal
                'UserCanceled': 'canceled',  # withdraw canceled by user or Superuser
                'AdminCanceled': 'canceled',  # withdraw canceled by Superuser
                'AmlProcessing': 'pending',  # anti-money-laundering process underway
                'AmlAccepted': 'pending',  # anti-money-laundering process complete
                'AmlRejected': 'rejected',  # withdrawal did not stand up to anti-money-laundering process
                'AmlFailed': 'failed',  # withdrawal did not complete anti-money-laundering process
                'LimitsAccepted': 'pending',  # withdrawal meets limits for fiat or crypto asset
                'LimitsRejected': 'rejected',  # withdrawal does not meet limits for fiat or crypto asset
                'Submitted': 'pending',  # withdrawal sent to Account Provider; awaiting blockchain confirmation
                'Confirmed': 'pending',  # Account Provider confirms that withdrawal is on the blockchain
                'ManuallyConfirmed': 'pending',  # admin has sent withdrawal via wallet or admin function directly; marks ticket as FullyProcessed; debits account
                'Confirmed2Fa': 'pending',  # user has confirmed withdraw via 2-factor authentication.
            },
        }
        statuses = self.safe_value(statusesByType, type, {})
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         "OMSId":1,
        #         "DepositId":44,
        #         "AccountId":449,
        #         "SubAccountId":0,
        #         "ProductId":4,
        #         "Amount":200.00000000000000000000000000,
        #         "LastUpdateTimeStamp":637431291261187806,
        #         "ProductType":"CryptoCurrency",
        #         "TicketStatus":"FullyProcessed",
        #         "DepositInfo":"{}",
        #         "DepositCode":"ab0e23d5-a9ce-4d94-865f-9ab464fb1de3",
        #         "TicketNumber":71,
        #         "NotionalProductId":13,
        #         "NotionalValue":200.00000000000000000000000000,
        #         "FeeAmount":0.0000000000000000000000000000,
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "Amount": 0.0,
        #         "FeeAmount": 0.0,
        #         "NotionalValue": 0.0,
        #         "WithdrawId": 0,
        #         "AssetManagerId": 0,
        #         "AccountId": 0,
        #         "AssetId": 0,
        #         "TemplateForm": "{\"TemplateType\": \"TetherRPCWithdraw\",\"Comment\": \"TestWithdraw\",\"ExternalAddress\": \"ms6C3pKAAr8gRCcnVebs8VRkVrjcvqNYv3\"}",
        #         "TemplateFormType": "TetherRPCWithdraw",
        #         "omsId": 0,
        #         "TicketStatus": 0,
        #         "TicketNumber": 0,
        #         "WithdrawTransactionDetails": "",
        #         "WithdrawType": "",
        #         "WithdrawCode": "490b4fa3-53fc-44f4-bd29-7e16be86fba3",
        #         "AssetType": 0,
        #         "Reaccepted": True,
        #         "NotionalProductId": 0
        #     }
        #
        id = self.safe_string(transaction, 'DepositId')
        txid = None
        currencyId = self.safe_string(transaction, 'ProductId')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = None
        type = None
        if 'DepositId' in transaction:
            type = 'deposit'
        elif 'WithdrawId' in transaction:
            type = 'withdrawal'
        templateFormString = self.safe_string(transaction, 'TemplateForm')
        address = None
        updated = self.safe_integer(transaction, 'LastUpdateTimeStamp')
        if templateFormString is not None:
            templateForm = json.loads(templateFormString)
            address = self.safe_string(templateForm, 'ExternalAddress')
            txid = self.safe_string(templateForm, 'TxId')
            timestamp = self.safe_integer(templateForm, 'TimeSubmitted')
            updated = self.safe_integer(templateForm, 'LastUpdated', updated)
        addressTo = address
        status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'TicketStatus'), type)
        amount = self.safe_number(transaction, 'Amount')
        feeCost = self.safe_number(transaction, 'FeeAmount')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressTo': addressTo,
            'addressFrom': None,
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            nonce = str(self.nonce())
            auth = nonce + self.uid + self.apiKey
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            headers = {
                'Nonce': nonce,
                'APIKey': self.apiKey,
                'Signature': signature,
                'UserId': self.uid,
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(query)
            else:
                if query:
                    url += '?' + self.urlencode(query)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if code == 404:
            raise AuthenticationError(self.id + ' ' + body)
        if response is None:
            return
        #
        #     {"status":"Rejected","errormsg":"Not_Enough_Funds","errorcode":101}
        #     {"result":false,"errormsg":"Server Error","errorcode":102,"detail":null}
        #
        message = self.safe_string(response, 'errormsg')
        if (message is not None) and (message != ''):
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            raise ExchangeError(feedback)
