# -*- 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
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,
                },
            },
        })

    def fetch_currencies(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        request = {
            'omsId': omsId,
        }
        response = 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

    def fetch_markets(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        request = {
            'omsId': omsId,
        }
        response = 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

    def fetch_order_book(self, symbol, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        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 = 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,
        }

    def fetch_ticker(self, symbol, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        market = self.market(symbol)
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
        }
        response = 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),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        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 = 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,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        market = self.market(symbol)
        request = {
            'omsId': omsId,
            'InstrumentId': market['id'],
        }
        if limit is not None:
            request['Count'] = limit
        response = 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)

    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 = 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

    def fetch_balance(self, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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,
        }

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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,
        })

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = self.privatePostSendOrder(self.extend(request, params))
        #
        #     {
        #         "status":"Accepted",
        #         "errormsg":"",
        #         "OrderId": 2543565231
        #     }
        #
        return self.parse_order(response, market)

    def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = self.privatePostCancelReplaceOrder(self.extend(request, params))
        #
        #     {
        #         "replacementOrderId": 1234,
        #         "replacementClOrdId": 1561,
        #         "origOrderId": 5678,
        #         "origClOrdId": 91011,
        #     }
        #
        return self.parse_order(response, market)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def cancel_all_orders(self, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = self.privatePostCancelAllOrders(self.extend(request, params))
        #
        #     {
        #         "result":true,
        #         "errormsg":null,
        #         "errorcode":0,
        #         "detail":null
        #     }
        #
        return response

    def cancel_order(self, id, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = self.privatePostCancelOrder(self.extend(request, params))
        order = self.parse_order(response, market)
        return self.extend(order, {
            'id': id,
            'clientOrderId': clientOrderId,
        })

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def fetch_order(self, id, symbol=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def fetch_deposit_address(self, code, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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,
        }

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

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        omsId = self.safe_integer(self.options, 'omsId', 1)
        self.load_markets()
        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 = 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)
