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

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.base.exchange import Exchange
from ccxt.abstract.poloniex import ImplicitAPI
import hashlib
from ccxt.base.types import Any, Balances, Bool, Currencies, Currency, DepositAddress, Int, Leverage, MarginModification, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class poloniex(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(poloniex, self).describe(), {
            'id': 'poloniex',
            'name': 'Poloniex',
            'countries': ['US'],
            # 200 requests per second for some unauthenticated market endpoints => 1000ms / 200 = 5ms between requests
            'rateLimit': 5,
            'certified': False,
            'pro': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': None,  # has but not fully implemented
                'swap': True,
                'future': True,
                'option': False,
                'addMargin': True,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': None,  # not yet implemented, because RL is worse than cancelOrder
                'createDepositAddress': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createOrders': None,  # not yet implemented, because RL is worse than createOrder
                'createStopOrder': True,
                'createTriggerOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrder': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchFundingHistory': False,
                'fetchFundingInterval': False,
                'fetchFundingIntervals': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': None,  # has but not implemented
                'fetchLedger': None,  # has but not implemented
                'fetchLeverage': True,
                'fetchLiquidations': None,  # has but not implemented
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrder': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrderTrades': True,
                'fetchPosition': False,
                'fetchPositionMode': True,
                'fetchPositions': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactions': 'emulated',
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawals': True,
                'reduceMargin': True,
                'sandbox': True,
                'setLeverage': True,
                'setPositionMode': True,
                'transfer': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 'MINUTE_1',
                '5m': 'MINUTE_5',
                '10m': 'MINUTE_10',  # not in swap
                '15m': 'MINUTE_15',
                '30m': 'MINUTE_30',
                '1h': 'HOUR_1',
                '2h': 'HOUR_2',
                '4h': 'HOUR_4',
                '6h': 'HOUR_6',  # not in swap
                '12h': 'HOUR_12',
                '1d': 'DAY_1',
                '3d': 'DAY_3',
                '1w': 'WEEK_1',
                '1M': 'MONTH_1',  # not in swap
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg',
                'api': {
                    'spot': 'https://api.poloniex.com',
                    'swap': 'https://api.poloniex.com',
                },
                'test': {
                    'spot': 'https://sand-spot-api-gateway.poloniex.com',
                },
                'www': 'https://www.poloniex.com',
                'doc': 'https://api-docs.poloniex.com/spot/',
                'fees': 'https://poloniex.com/fees',
                'referral': 'https://poloniex.com/signup?c=UBFZJRPJ',
            },
            'api': {
                'public': {
                    'get': {
                        'markets': 20,
                        'markets/{symbol}': 1,
                        'currencies': 20,
                        'currencies/{currency}': 20,
                        'v2/currencies': 20,
                        'v2/currencies/{currency}': 20,
                        'timestamp': 1,
                        'markets/price': 1,
                        'markets/{symbol}/price': 1,
                        'markets/markPrice': 1,
                        'markets/{symbol}/markPrice': 1,
                        'markets/{symbol}/markPriceComponents': 1,
                        'markets/{symbol}/orderBook': 1,
                        'markets/{symbol}/candles': 1,
                        'markets/{symbol}/trades': 20,
                        'markets/ticker24h': 20,
                        'markets/{symbol}/ticker24h': 20,
                        'markets/collateralInfo': 1,
                        'markets/{currency}/collateralInfo': 1,
                        'markets/borrowRatesInfo': 1,
                    },
                },
                'private': {
                    'get': {
                        'accounts': 4,
                        'accounts/balances': 4,
                        'accounts/{id}/balances': 4,
                        'accounts/activity': 20,
                        'accounts/transfer': 20,
                        'accounts/transfer/{id}': 4,
                        'feeinfo': 20,
                        'accounts/interest/history': 1,
                        'subaccounts': 4,
                        'subaccounts/balances': 20,
                        'subaccounts/{id}/balances': 4,
                        'subaccounts/transfer': 20,
                        'subaccounts/transfer/{id}': 4,
                        'wallets/addresses': 20,
                        'wallets/addresses/{currency}': 20,
                        'wallets/activity': 20,
                        'margin/accountMargin': 4,
                        'margin/borrowStatus': 4,
                        'margin/maxSize': 4,
                        'orders': 20,
                        'orders/{id}': 4,
                        'orders/killSwitchStatus': 4,
                        'smartorders': 20,
                        'smartorders/{id}': 4,
                        'orders/history': 20,
                        'smartorders/history': 20,
                        'trades': 20,
                        'orders/{id}/trades': 4,
                    },
                    'post': {
                        'accounts/transfer': 4,
                        'subaccounts/transfer': 20,
                        'wallets/address': 20,
                        'wallets/withdraw': 20,
                        'v2/wallets/withdraw': 20,
                        'orders': 4,
                        'orders/batch': 20,
                        'orders/killSwitch': 4,
                        'smartorders': 4,
                    },
                    'delete': {
                        'orders/{id}': 4,
                        'orders/cancelByIds': 20,
                        'orders': 20,
                        'smartorders/{id}': 4,
                        'smartorders/cancelByIds': 20,
                        'smartorders': 20,
                    },
                    'put': {
                        'orders/{id}': 20,
                        'smartorders/{id}': 20,
                    },
                },
                'swapPublic': {
                    'get': {
                        # 300 calls / second
                        'v3/market/allInstruments': 2 / 3,
                        'v3/market/instruments': 2 / 3,
                        'v3/market/orderBook': 2 / 3,
                        'v3/market/candles': 10,  # candles have differnt RL
                        'v3/market/indexPriceCandlesticks': 10,
                        'v3/market/premiumIndexCandlesticks': 10,
                        'v3/market/markPriceCandlesticks': 10,
                        'v3/market/trades': 2 / 3,
                        'v3/market/liquidationOrder': 2 / 3,
                        'v3/market/tickers': 2 / 3,
                        'v3/market/markPrice': 2 / 3,
                        'v3/market/indexPrice': 2 / 3,
                        'v3/market/indexPriceComponents': 2 / 3,
                        'v3/market/fundingRate': 2 / 3,
                        'v3/market/openInterest': 2 / 3,
                        'v3/market/insurance': 2 / 3,
                        'v3/market/riskLimit': 2 / 3,
                    },
                },
                'swapPrivate': {
                    'get': {
                        'v3/account/balance': 4,
                        'v3/account/bills': 20,
                        'v3/trade/order/opens': 20,
                        'v3/trade/order/trades': 20,
                        'v3/trade/order/history': 20,
                        'v3/trade/position/opens': 20,
                        'v3/trade/position/history': 20,  # todo: method for self
                        'v3/position/leverages': 20,
                        'v3/position/mode': 20,
                    },
                    'post': {
                        'v3/trade/order': 4,
                        'v3/trade/orders': 40,
                        'v3/trade/position': 20,
                        'v3/trade/positionAll': 100,
                        'v3/position/leverage': 20,
                        'v3/position/mode': 20,
                        'v3/trade/position/margin': 20,
                    },
                    'delete': {
                        'v3/trade/order': 2,
                        'v3/trade/batchOrders': 20,
                        'v3/trade/allOrders': 20,
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    # starting from Jan 8 2020
                    'maker': self.parse_number('0.0009'),
                    'taker': self.parse_number('0.0009'),
                },
                'funding': {},
            },
            'commonCurrencies': {
                'AIR': 'AirCoin',
                'APH': 'AphroditeCoin',
                'BCC': 'BTCtalkcoin',
                'BCHABC': 'BCHABC',
                'BDG': 'Badgercoin',
                'BTM': 'Bitmark',
                'CON': 'Coino',
                'ETHTRON': 'ETH',
                'GOLD': 'GoldEagles',
                'GPUC': 'GPU',
                'HOT': 'Hotcoin',
                'ITC': 'Information Coin',
                'KEY': 'KEYCoin',
                'MASK': 'NFTX Hashmasks Index',  # conflict with Mask Network
                'MEME': 'Degenerator Meme',  # Degenerator Meme migrated to Meme Inu, self exchange still has the old price
                'PLX': 'ParallaxCoin',
                'REPV2': 'REP',
                'STR': 'XLM',
                'SOC': 'SOCC',
                'TRADE': 'Unitrade',
                'TRXETH': 'TRX',
                'XAP': 'API Coin',
                # self is not documented in the API docs for Poloniex
                # https://github.com/ccxt/ccxt/issues/7084
                # when the user calls withdraw('USDT', amount, address, tag, params)
                # with params = {'currencyToWithdrawAs': 'USDTTRON'}
                # or params = {'currencyToWithdrawAs': 'USDTETH'}
                # fetchWithdrawals('USDT') returns the corresponding withdrawals
                # with a USDTTRON or a USDTETH currency id, respectfully
                # therefore we have map them back to the original code USDT
                # otherwise the returned withdrawals are filtered out
                'USDTBSC': 'USDT',
                'USDTTRON': 'USDT',
                'USDTETH': 'USDT',
                'UST': 'USTC',
            },
            'options': {
                'defaultType': 'spot',
                'createMarketBuyOrderRequiresPrice': True,
                'networks': {
                    'BEP20': 'BSC',
                    'ERC20': 'ETH',
                    'TRC20': 'TRON',
                },
                'limits': {
                    'cost': {
                        'min': {
                            'BTC': 0.0001,
                            'ETH': 0.0001,
                            'USDT': 1.0,
                            'TRX': 100,
                            'BNB': 0.06,
                            'USDC': 1.0,
                            'USDJ': 1.0,
                            'TUSD': 0.0001,
                            'DAI': 1.0,
                            'PAX': 1.0,
                            'BUSD': 1.0,
                        },
                    },
                },
                'accountsByType': {
                    'spot': 'spot',
                    'future': 'futures',
                },
                'accountsById': {
                    'exchange': 'spot',
                    'futures': 'future',
                },
            },
            'features': {
                'default': {
                    'sandbox': True,
                    'createOrder': {
                        'marginMode': True,  # todo
                        'triggerPrice': True,
                        'triggerPriceType': None,
                        'triggerDirection': False,
                        'stopLossPrice': False,  # todo
                        'takeProfitPrice': False,  # todo
                        'attachedStopLossTakeProfit': None,
                        'timeInForce': {
                            'IOC': True,
                            'FOK': True,
                            'PO': True,
                            'GTD': False,
                        },
                        'hedged': False,
                        'leverage': False,
                        'marketBuyByCost': True,
                        'marketBuyRequiresPrice': False,
                        'selfTradePrevention': True,  # todo, only for non-trigger orders
                        'trailing': False,
                        'iceberg': False,
                    },
                    'createOrders': {
                        'max': 20,
                    },
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': 1000,
                        'daysBack': 100000,
                        'untilDays': 100000,
                        'symbolRequired': False,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': 2000,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOrders': None,
                    'fetchClosedOrders': None,  # todo implement
                    'fetchOHLCV': {
                        'limit': 500,
                    },
                },
                'spot': {
                    'extends': 'default',
                },
                'forContracts': {
                    'extends': 'default',
                    'createOrder': {
                        'marginMode': True,
                        'triggerPrice': False,
                        'hedged': True,
                        'stpMode': True,  # todo
                        'marketBuyByCost': False,
                    },
                    'createOrders': {
                        'max': 10,
                    },
                    'fetchOpenOrders': {
                        'limit': 100,
                    },
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': 100,
                        'daysBack': None,
                        'daysBackCanceled': 1 / 6,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchMyTrades': {
                        'limit': 100,
                        'untilDays': 90,
                    },
                },
                'swap': {
                    'linear': {
                        'extends': 'forContracts',
                    },
                    'inverse': {
                        'extends': 'forContracts',
                    },
                },
                'future': {
                    'linear': {
                        'extends': 'forContracts',
                    },
                    'inverse': {
                        'extends': 'forContracts',
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    # General
                    '500': ExchangeNotAvailable,  # Internal System Error
                    '603': RequestTimeout,  # Internal Request Timeout
                    '601': BadRequest,  # Invalid Parameter
                    '415': ExchangeError,  # System Error
                    '602': ArgumentsRequired,  # Missing Required Parameters
                    # Accounts
                    '21604': BadRequest,  # Invalid UserId
                    '21600': AuthenticationError,  # Account Not Found
                    '21605': AuthenticationError,  # Invalid Account Type
                    '21102': ExchangeError,  # Invalid Currency
                    '21100': AuthenticationError,  # Invalid account
                    '21704': AuthenticationError,  # Missing UserId and/or AccountId
                    '21700': BadRequest,  # Error updating accounts
                    '21705': BadRequest,  # Invalid currency type
                    '21707': ExchangeError,  # Internal accounts Error
                    '21708': BadRequest,  # Currency not available to User
                    '21601': AccountSuspended,  # Account locked. Contact support
                    '21711': ExchangeError,  # Currency locked. Contact support
                    '21709': InsufficientFunds,  # Insufficient balance
                    '250000': ExchangeError,  # Transfer error. Try again later
                    '250001': BadRequest,  # Invalid toAccount for transfer
                    '250002': BadRequest,  # Invalid fromAccount for transfer
                    '250003': BadRequest,  # Invalid transfer amount
                    '250004': BadRequest,  # Transfer is not supported
                    '250005': InsufficientFunds,  # Insufficient transfer balance
                    '250008': BadRequest,  # Invalid transfer currency
                    '250012': ExchangeError,  # Futures account is not valid
                    # Trading
                    '21110': BadRequest,  # Invalid quote currency
                    '10040': BadSymbol,  # Invalid symbol
                    '10060': ExchangeError,  # Symbol setup error
                    '10020': BadSymbol,  # Invalid currency
                    '10041': BadSymbol,  # Symbol frozen for trading
                    '21340': OnMaintenance,  # No order creation/cancelation is allowed is in Maintenane Mode
                    '21341': InvalidOrder,  # Post-only orders type allowed is in Post Only Mode
                    '21342': InvalidOrder,  # Price is higher than highest bid is in Maintenance Mode
                    '21343': InvalidOrder,  # Price is lower than lowest bid is in Maintenance Mode
                    '21351': AccountSuspended,  # Trading for self account is frozen. Contact support
                    '21352': BadSymbol,  # Trading for self currency is frozen
                    '21353': PermissionDenied,  # Trading for US customers is not supported
                    '21354': PermissionDenied,  # Account needs to be verified via email before trading is enabled. Contact support
                    '21359': OrderNotFound,  # {"code" : 21359, "message" : "Order was already canceled or filled."}
                    '21360': InvalidOrder,  # {"code" : 21360, "message" : "Order size exceeds the limit.Please enter a smaller amount and try again."}
                    '24106': BadRequest,  # Invalid market depth
                    '24201': ExchangeNotAvailable,  # Service busy. Try again later
                    # Orders
                    '21301': OrderNotFound,  # Order not found
                    '21302': ExchangeError,  # Batch cancel order error
                    '21304': ExchangeError,  # Order is filled
                    '21305': OrderNotFound,  # Order is canceled
                    '21307': ExchangeError,  # Error during Order Cancelation
                    '21309': InvalidOrder,  # Order price must be greater than 0
                    '21310': InvalidOrder,  # Order price must be less than max price
                    '21311': InvalidOrder,  # Order price must be greater than min price
                    '21312': InvalidOrder,  # Client orderId already exists
                    '21314': InvalidOrder,  # Max limit of open orders(2000) exceeded
                    '21315': InvalidOrder,  # Client orderId exceeded max length of 17 digits
                    '21317': InvalidOrder,  # Amount must be greater than 0
                    '21319': InvalidOrder,  # Invalid order side
                    '21320': InvalidOrder,  # Invalid order type
                    '21321': InvalidOrder,  # Invalid timeInForce value
                    '21322': InvalidOrder,  # Amount is less than minAmount trade limit
                    '21324': BadRequest,  # Invalid account type
                    '21327': InvalidOrder,  # Order pice must be greater than 0
                    '21328': InvalidOrder,  # Order quantity must be greater than 0
                    '21330': InvalidOrder,  # Quantity is less than minQuantity trade limit
                    '21335': InvalidOrder,  # Invalid priceScale for self symbol
                    '21336': InvalidOrder,  # Invalid quantityScale for self symbol
                    '21337': InvalidOrder,  # Invalid amountScale for self symbol
                    '21344': InvalidOrder,  # Value of limit param is greater than max value of 100
                    '21345': InvalidOrder,  # Value of limit param value must be greater than 0
                    '21346': InvalidOrder,  # Order Id must be of type Long
                    '21348': InvalidOrder,  # Order type must be LIMIT_MAKER
                    '21347': InvalidOrder,  # Stop price must be greater than 0
                    '21349': InvalidOrder,  # Order value is too large
                    '21350': InvalidOrder,  # Amount must be greater than 1 USDT
                    '21355': ExchangeError,  # Interval between startTime and endTime in trade/order history has exceeded 7 day limit
                    '21356': BadRequest,  # Order size would cause too much price movement. Reduce order size.
                    '21721': InsufficientFunds,
                    '24101': BadSymbol,  # Invalid symbol
                    '24102': InvalidOrder,  # Invalid K-line type
                    '24103': InvalidOrder,  # Invalid endTime
                    '24104': InvalidOrder,  # Invalid amount
                    '24105': InvalidOrder,  # Invalid startTime
                    '25020': InvalidOrder,  # No active kill switch
                    # Smartorders
                    '25000': InvalidOrder,  # Invalid userId
                    '25001': InvalidOrder,  # Invalid parameter
                    '25002': InvalidOrder,  # Invalid userId.
                    '25003': ExchangeError,  # Unable to place order
                    '25004': InvalidOrder,  # Client orderId already exists
                    '25005': ExchangeError,  # Unable to place smart order
                    '25006': InvalidOrder,  # OrderId and clientOrderId already exists
                    '25007': InvalidOrder,  # Invalid orderid
                    '25008': InvalidOrder,  # Both orderId and clientOrderId are required
                    '25009': ExchangeError,  # Failed to cancel order
                    '25010': PermissionDenied,  # Unauthorized to cancel order
                    '25011': InvalidOrder,  # Failed to cancel due to invalid paramters
                    '25012': ExchangeError,  # Failed to cancel
                    '25013': OrderNotFound,  # Failed to cancel were not found
                    '25014': OrderNotFound,  # Failed to cancel were not found
                    '25015': OrderNotFound,  # Failed to cancel orders exist
                    '25016': ExchangeError,  # Failed to cancel to release funds
                    '25017': ExchangeError,  # No orders were canceled
                    '25018': BadRequest,  # Invalid accountType
                    '25019': BadSymbol,  # Invalid symbol
                },
                'broad': {
                },
            },
        })

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        # spot:
        #
        #     [
        #         [
        #             "22814.01",
        #             "22937.42",
        #             "22832.57",
        #             "22937.42",
        #             "3916.58764051",
        #             "0.171199",
        #             "2982.64647063",
        #             "0.130295",
        #             33,
        #             0,
        #             "22877.449915304470460711",
        #             "MINUTE_5",
        #             1659664800000,
        #             1659665099999
        #         ]
        #     ]
        #
        # contract:
        #
        #           [
        #             "84207.02",
        #             "84320.85",
        #             "84207.02",
        #             "84253.83",
        #             "3707.5395",
        #             "44",
        #             "14",
        #             "1740770040000",
        #             "1740770099999",
        #           ],
        #
        ohlcvLength = len(ohlcv)
        isContract = ohlcvLength == 9
        if isContract:
            return [
                self.safe_integer(ohlcv, 7),
                self.safe_number(ohlcv, 2),
                self.safe_number(ohlcv, 1),
                self.safe_number(ohlcv, 0),
                self.safe_number(ohlcv, 3),
                self.safe_number(ohlcv, 5),
            ]
        return [
            self.safe_integer(ohlcv, 12),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 0),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 5),
        ]

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://api-docs.poloniex.com/spot/api/public/market-data#candles
        https://api-docs.poloniex.com/v3/futures/api/market/get-kline-data

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
        if paginate:
            return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 500)
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'interval': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        keyStart = 'startTime' if market['spot'] else 'sTime'
        keyEnd = 'endTime' if market['spot'] else 'eTime'
        if since is not None:
            request[keyStart] = since
        if limit is not None:
            # limit should in between 100 and 500
            request['limit'] = limit
        request, params = self.handle_until_option(keyEnd, request, params)
        if market['contract']:
            if self.in_array(timeframe, ['10m', '1M']):
                raise NotSupported(self.id + ' ' + timeframe + ' ' + market['type'] + ' fetchOHLCV is not supported')
            responseRaw = self.swapPublicGetV3MarketCandles(self.extend(request, params))
            #
            #     {
            #         code: "200",
            #         msg: "Success",
            #         data: [
            #           [
            #             "84207.02",
            #             "84320.85",
            #             "84207.02",
            #             "84253.83",
            #             "3707.5395",
            #             "44",
            #             "14",
            #             "1740770040000",
            #             "1740770099999",
            #           ],
            #
            data = self.safe_list(responseRaw, 'data')
            return self.parse_ohlcvs(data, market, timeframe, since, limit)
        response = self.publicGetMarketsSymbolCandles(self.extend(request, params))
        #
        #     [
        #         [
        #             "22814.01",
        #             "22937.42",
        #             "22832.57",
        #             "22937.42",
        #             "3916.58764051",
        #             "0.171199",
        #             "2982.64647063",
        #             "0.130295",
        #             33,
        #             0,
        #             "22877.449915304470460711",
        #             "MINUTE_5",
        #             1659664800000,
        #             1659665099999
        #         ]
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def load_markets(self, reload=False, params={}):
        markets = super(poloniex, self).load_markets(reload, params)
        currenciesByNumericId = self.safe_value(self.options, 'currenciesByNumericId')
        if (currenciesByNumericId is None) or reload:
            self.options['currenciesByNumericId'] = self.index_by(self.currencies, 'numericId')
        return markets

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for poloniex

        https://api-docs.poloniex.com/spot/api/public/reference-data#symbol-information
        https://api-docs.poloniex.com/v3/futures/api/market/get-all-product-info

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        promises = [self.fetch_spot_markets(params), self.fetch_swap_markets(params)]
        results = promises
        return self.array_concat(results[0], results[1])

    def fetch_spot_markets(self, params={}) -> List[Market]:
        markets = self.publicGetMarkets(params)
        #
        #     [
        #         {
        #             "symbol" : "BTS_BTC",
        #             "baseCurrencyName" : "BTS",
        #             "quoteCurrencyName" : "BTC",
        #             "displayName" : "BTS/BTC",
        #             "state" : "NORMAL",
        #             "visibleStartTime" : 1659018816626,
        #             "tradableStartTime" : 1659018816626,
        #             "symbolTradeLimit" : {
        #                 "symbol" : "BTS_BTC",
        #                 "priceScale" : 10,
        #                 "quantityScale" : 0,
        #                 "amountScale" : 8,
        #                 "minQuantity" : "100",
        #                 "minAmount" : "0.00001",
        #                 "highestBid" : "0",
        #                 "lowestAsk" : "0"
        #             }
        #         }
        #     ]
        #
        return self.parse_markets(markets)

    def fetch_swap_markets(self, params={}) -> List[Market]:
        # do similar per https://api-docs.poloniex.com/v3/futures/api/market/get-product-info
        response = self.swapPublicGetV3MarketAllInstruments(params)
        #
        #    {
        #        "code": "200",
        #        "msg": "Success",
        #        "data": [
        #            {
        #                "symbol": "BNB_USDT_PERP",
        #                "bAsset": ".PBNBUSDT",
        #                "bCcy": "BNB",
        #                "qCcy": "USDT",
        #                "visibleStartTime": "1620390600000",
        #                "tradableStartTime": "1620390600000",
        #                "sCcy": "USDT",
        #                "tSz": "0.001",
        #                "pxScale": "0.001,0.01,0.1,1,10",
        #                "lotSz": "1",
        #                "minSz": "1",
        #                "ctVal": "0.1",
        #                "status": "OPEN",
        #                "oDate": "1620287590000",
        #                "maxPx": "1000000",
        #                "minPx": "0.001",
        #                "maxQty": "1000000",
        #                "minQty": "1",
        #                "maxLever": "50",
        #                "lever": "10",
        #                "ctType": "LINEAR",
        #                "alias": "",
        #                "iM": "0.02",
        #                "mM": "0.0115",
        #                "mR": "2000",
        #                "buyLmt": "",
        #                "sellLmt": "",
        #                "ordPxRange": "0.05",
        #                "marketMaxQty": "2800",
        #                "limitMaxQty": "1000000"
        #            },
        #
        markets = self.safe_list(response, 'data')
        return self.parse_markets(markets)

    def parse_market(self, market: dict) -> Market:
        if 'ctType' in market:
            return self.parse_swap_market(market)
        else:
            return self.parse_spot_market(market)

    def parse_spot_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'symbol')
        baseId = self.safe_string(market, 'baseCurrencyName')
        quoteId = self.safe_string(market, 'quoteCurrencyName')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        state = self.safe_string(market, 'state')
        active = state == 'NORMAL'
        symbolTradeLimit = self.safe_value(market, 'symbolTradeLimit')
        # these are known defaults
        return {
            'id': id,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': active,
            'contract': False,
            'linear': None,
            'inverse': None,
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.parse_number(self.parse_precision(self.safe_string(symbolTradeLimit, 'quantityScale'))),
                'price': self.parse_number(self.parse_precision(self.safe_string(symbolTradeLimit, 'priceScale'))),
            },
            'limits': {
                'amount': {
                    'min': self.safe_number(symbolTradeLimit, 'minQuantity'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_number(symbolTradeLimit, 'minAmount'),
                    'max': None,
                },
            },
            'created': self.safe_integer(market, 'tradableStartTime'),
            'info': market,
        }

    def parse_swap_market(self, market: dict) -> Market:
        #
        #            {
        #                "symbol": "BNB_USDT_PERP",
        #                "bAsset": ".PBNBUSDT",
        #                "bCcy": "BNB",
        #                "qCcy": "USDT",
        #                "visibleStartTime": "1620390600000",
        #                "tradableStartTime": "1620390600000",
        #                "sCcy": "USDT",
        #                "tSz": "0.001",
        #                "pxScale": "0.001,0.01,0.1,1,10",
        #                "lotSz": "1",
        #                "minSz": "1",
        #                "ctVal": "0.1",
        #                "status": "OPEN",
        #                "oDate": "1620287590000",
        #                "maxPx": "1000000",
        #                "minPx": "0.001",
        #                "maxQty": "1000000",
        #                "minQty": "1",
        #                "maxLever": "50",
        #                "lever": "10",
        #                "ctType": "LINEAR",
        #                "alias": "",
        #                "iM": "0.02",
        #                "mM": "0.0115",
        #                "mR": "2000",
        #                "buyLmt": "",
        #                "sellLmt": "",
        #                "ordPxRange": "0.05",
        #                "marketMaxQty": "2800",
        #                "limitMaxQty": "1000000"
        #            },
        #
        id = self.safe_string(market, 'symbol')
        baseId = self.safe_string(market, 'bCcy')
        quoteId = self.safe_string(market, 'qCcy')
        settleId = self.safe_string(market, 'sCcy')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        settle = self.safe_currency_code(settleId)
        status = self.safe_string(market, 'status')
        active = status == 'OPEN'
        linear = market['ctType'] == 'LINEAR'
        symbol = base + '/' + quote
        if linear:
            symbol += ':' + settle
        else:
            # actually, exchange does not have any inverse future now
            symbol += ':' + base
        alias = self.safe_string(market, 'alias')
        type = 'swap'
        if alias is not None:
            type = 'future'
        return {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': settle,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleId,
            'type': 'future' if (type == 'future') else 'swap',
            'spot': False,
            'margin': False,
            'swap': type == 'swap',
            'future': type == 'future',
            'option': False,
            'active': active,
            'contract': True,
            'linear': linear,
            'inverse': not linear,
            'contractSize': self.safe_number(market, 'ctVal'),
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'taker': self.safe_number(market, 'tFee'),
            'maker': self.safe_number(market, 'mFee'),
            'precision': {
                'amount': self.safe_number(market, 'lotSz'),
                'price': self.safe_number(market, 'tSz'),
            },
            'limits': {
                'amount': {
                    'min': self.safe_number(market, 'minSz'),
                    'max': self.safe_number(market, 'limitMaxQty'),
                },
                'price': {
                    'min': self.safe_number(market, 'minPx'),
                    'max': self.safe_number(market, 'maxPx'),
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
                'leverage': {
                    'max': self.safe_number(market, 'maxLever'),
                    'min': None,
                },
            },
            'created': self.safe_integer(market, 'oDate'),
            'info': market,
        }

    def fetch_time(self, params={}) -> Int:
        """
        fetches the current integer timestamp in milliseconds from the exchange server

        https://api-docs.poloniex.com/spot/api/public/reference-data#system-timestamp

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetTimestamp(params)
        return self.safe_integer(response, 'serverTime')

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #  spot:
        #
        #     {
        #         "symbol" : "BTC_USDT",
        #         "open" : "26053.33",
        #         "low" : "26053.33",
        #         "high" : "26798.02",
        #         "close" : "26447.58",
        #         "quantity" : "6116.210188",
        #         "amount" : "161082122.88450926",
        #         "tradeCount" : "134709",
        #         "startTime" : "1692784440000",
        #         "closeTime" : "1692870839630",
        #         "displayName" : "BTC/USDT",
        #         "dailyChange" : "0.0151",
        #         "bid" : "26447.57",
        #         "bidQuantity" : "0.016313",
        #         "ask" : "26447.58",
        #         "askQuantity" : "0.068307",
        #         "ts" : "1692870845446",
        #         "markPrice" : "26444.11"
        #     }
        #
        #  swap:
        #
        #            {
        #                "s": "XRP_USDT_PERP",
        #                "o": "2.0503",
        #                "l": "2.0066",
        #                "h": "2.216",
        #                "c": "2.1798",
        #                "qty": "21090",
        #                "amt": "451339.65",
        #                "tC": "3267",
        #                "sT": "1740736380000",
        #                "cT": "1740822777559",
        #                "dN": "XRP/USDT/PERP",
        #                "dC": "0.0632",
        #                "bPx": "2.175",
        #                "bSz": "3",
        #                "aPx": "2.1831",
        #                "aSz": "111",
        #                "mPx": "2.1798",
        #                "iPx": "2.1834"
        #            },
        #
        timestamp = self.safe_integer_2(ticker, 'ts', 'cT')
        marketId = self.safe_string_2(ticker, 'symbol', 's')
        market = self.safe_market(marketId)
        relativeChange = self.safe_string_2(ticker, 'dailyChange', 'dc')
        percentage = Precise.string_mul(relativeChange, '100')
        return self.safe_ticker({
            'id': marketId,
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string_2(ticker, 'high', 'h'),
            'low': self.safe_string_2(ticker, 'low', 'l'),
            'bid': self.safe_string_2(ticker, 'bid', 'bPx'),
            'bidVolume': self.safe_string_2(ticker, 'bidQuantity', 'bSz'),
            'ask': self.safe_string_2(ticker, 'ask', 'aPx'),
            'askVolume': self.safe_string_2(ticker, 'askQuantity', 'aSz'),
            'vwap': None,
            'open': self.safe_string_2(ticker, 'open', 'o'),
            'close': self.safe_string_2(ticker, 'close', 'c'),
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_string_2(ticker, 'quantity', 'qty'),
            'quoteVolume': self.safe_string_2(ticker, 'amount', 'amt'),
            'markPrice': self.safe_string_2(ticker, 'markPrice', 'mPx'),
            'indexPrice': self.safe_string(ticker, 'iPx'),
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market

        https://api-docs.poloniex.com/spot/api/public/market-data#ticker
        https://api-docs.poloniex.com/v3/futures/api/market/get-market-info

        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = None
        request: dict = {}
        if symbols is not None:
            symbols = self.market_symbols(symbols, None, True, True, False)
            symbolsLength = len(symbols)
            if symbolsLength > 0:
                market = self.market(symbols[0])
                if symbolsLength == 1:
                    request['symbol'] = market['id']
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchTickers', market, params)
        if marketType == 'swap':
            responseRaw = self.swapPublicGetV3MarketTickers(self.extend(request, params))
            #
            #    {
            #        "code": "200",
            #        "msg": "Success",
            #        "data": [
            #            {
            #                "s": "XRP_USDT_PERP",
            #                "o": "2.0503",
            #                "l": "2.0066",
            #                "h": "2.216",
            #                "c": "2.1798",
            #                "qty": "21090",
            #                "amt": "451339.65",
            #                "tC": "3267",
            #                "sT": "1740736380000",
            #                "cT": "1740822777559",
            #                "dN": "XRP/USDT/PERP",
            #                "dC": "0.0632",
            #                "bPx": "2.175",
            #                "bSz": "3",
            #                "aPx": "2.1831",
            #                "aSz": "111",
            #                "mPx": "2.1798",
            #                "iPx": "2.1834"
            #            },
            #
            data = self.safe_list(responseRaw, 'data')
            return self.parse_tickers(data, symbols)
        response = self.publicGetMarketsTicker24h(params)
        #
        #     [
        #         {
        #              "symbol" : "BTC_USDT",
        #              "open" : "26053.33",
        #              "low" : "26053.33",
        #              "high" : "26798.02",
        #              "close" : "26447.58",
        #              "quantity" : "6116.210188",
        #              "amount" : "161082122.88450926",
        #              "tradeCount" : "134709",
        #              "startTime" : "1692784440000",
        #              "closeTime" : "1692870839630",
        #              "displayName" : "BTC/USDT",
        #              "dailyChange" : "0.0151",
        #              "bid" : "26447.57",
        #              "bidQuantity" : "0.016313",
        #              "ask" : "26447.58",
        #              "askQuantity" : "0.068307",
        #              "ts" : "1692870845446",
        #              "markPrice" : "26444.11"
        #         }
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange

        https://api-docs.poloniex.com/spot/api/public/reference-data#currency-information

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetCurrencies(self.extend(params, {'includeMultiChainCurrencies': True}))
        #
        #     [
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "walletDepositState": "DISABLED",
        #                 "walletWithdrawalState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             }
        #         }
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            item = self.safe_value(response, i)
            ids = list(item.keys())
            id = self.safe_value(ids, 0)
            currency = self.safe_value(item, id)
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            networkId = self.safe_string(currency, 'blockchain')
            networkCode = None
            if networkId is not None:
                networkCode = self.network_id_to_code(networkId, code)
            delisted = self.safe_value(currency, 'delisted')
            walletEnabled = self.safe_string(currency, 'walletState') == 'ENABLED'
            depositEnabled = self.safe_string(currency, 'walletDepositState') == 'ENABLED'
            withdrawEnabled = self.safe_string(currency, 'walletWithdrawalState') == 'ENABLED'
            active = not delisted and walletEnabled and depositEnabled and withdrawEnabled
            numericId = self.safe_integer(currency, 'id')
            feeString = self.safe_string(currency, 'withdrawalFee')
            parentChain = self.safe_value(currency, 'parentChain')
            noParentChain = parentChain is None
            if self.safe_value(result, code) is None:
                result[code] = {
                    'id': id,
                    'code': code,
                    'info': None,
                    'name': name,
                    'active': active,
                    'deposit': depositEnabled,
                    'withdraw': withdrawEnabled,
                    'fee': self.parse_number(feeString),
                    'precision': None,
                    'type': 'crypto',
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'deposit': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            minFeeString = self.safe_string(result[code], 'fee')
            if feeString is not None:
                minFeeString = feeString if (minFeeString is None) else Precise.string_min(feeString, minFeeString)
            depositAvailable = self.safe_value(result[code], 'deposit')
            depositAvailable = depositEnabled if (depositEnabled) else depositAvailable
            withdrawAvailable = self.safe_value(result[code], 'withdraw')
            withdrawAvailable = withdrawEnabled if (withdrawEnabled) else withdrawAvailable
            networks = self.safe_value(result[code], 'networks', {})
            if networkCode is not None:
                networks[networkCode] = {
                    'info': currency,
                    'id': networkId,
                    'network': networkCode,
                    'currencyId': id,
                    'numericId': numericId,
                    'deposit': depositEnabled,
                    'withdraw': withdrawEnabled,
                    'active': active,
                    'fee': self.parse_number(feeString),
                    'precision': None,
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                        'deposit': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            result[code]['networks'] = networks
            info = self.safe_value(result[code], 'info', [])
            rawInfo: dict = {}
            rawInfo[id] = currency
            info.append(rawInfo)
            result[code]['info'] = info
            if noParentChain:
                result[code]['id'] = id
                result[code]['name'] = name
            result[code]['active'] = depositAvailable and withdrawAvailable
            result[code]['deposit'] = depositAvailable
            result[code]['withdraw'] = withdrawAvailable
            result[code]['fee'] = self.parse_number(minFeeString)
        return result

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://api-docs.poloniex.com/spot/api/public/market-data#ticker
        https://api-docs.poloniex.com/v3/futures/api/market/get-market-info

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if market['contract']:
            tickers = self.fetch_tickers([market['symbol']], params)
            return self.safe_dict(tickers, symbol)
        response = self.publicGetMarketsSymbolTicker24h(self.extend(request, params))
        #
        #     {
        #         "symbol" : "BTC_USDT",
        #         "open" : "26053.33",
        #         "low" : "26053.33",
        #         "high" : "26798.02",
        #         "close" : "26447.58",
        #         "quantity" : "6116.210188",
        #         "amount" : "161082122.88450926",
        #         "tradeCount" : "134709",
        #         "startTime" : "1692784440000",
        #         "closeTime" : "1692870839630",
        #         "displayName" : "BTC/USDT",
        #         "dailyChange" : "0.0151",
        #         "bid" : "26447.57",
        #         "bidQuantity" : "0.016313",
        #         "ask" : "26447.58",
        #         "askQuantity" : "0.068307",
        #         "ts" : "1692870845446",
        #         "markPrice" : "26444.11"
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades
        #
        #  spot:
        #
        #     {
        #         "id" : "60014521",
        #         "price" : "23162.94",
        #         "quantity" : "0.00009",
        #         "amount" : "2.0846646",
        #         "takerSide" : "SELL",
        #         "ts" : 1659684602042,
        #         "createTime" : 1659684602036
        #     }
        #
        #   swap:
        #
        #     {
        #         "id": "105807376",
        #         "side": "buy",
        #         "px": "84410.57",
        #         "qty": "1",
        #         "amt": "84.41057",
        #         "cT": "1740777563557",
        #     }
        #
        # fetchMyTrades
        #
        #  spot:
        #
        #     {
        #         "id": "32164924331503616",
        #         "symbol": "LINK_USDT",
        #         "accountType": "SPOT",
        #         "orderId": "32164923987566592",
        #         "side": "SELL",
        #         "type": "MARKET",
        #         "matchRole": "TAKER",
        #         "createTime": 1648635115525,
        #         "price": "11",
        #         "quantity": "0.5",
        #         "amount": "5.5",
        #         "feeCurrency": "USDT",
        #         "feeAmount": "0.007975",
        #         "pageId": "32164924331503616",
        #         "clientOrderId": "myOwnId-321"
        #     }
        #
        #  swap:
        #
        #     {
        #         "symbol": "BTC_USDT_PERP",
        #         "trdId": "105813553",
        #         "side": "SELL",
        #         "type": "TRADE",
        #         "mgnMode": "CROSS",
        #         "ordType": "MARKET",
        #         "clOrdId": "polo418912106147315112",
        #         "role": "TAKER",
        #         "px": "84704.9",
        #         "qty": "1",
        #         "cTime": "1740842829430",
        #         "uTime": "1740842829450",
        #         "feeCcy": "USDT",
        #         "feeAmt": "0.04235245",
        #         "deductCcy": "",
        #         "deductAmt": "0",
        #         "feeRate": "0.0005",
        #         "id": "418912106342654592",
        #         "posSide": "BOTH",
        #         "ordId": "418912106147315112",
        #         "qCcy": "USDT",
        #         "value": "84.7049",
        #         "actType": "TRADING"
        #     },
        #
        # fetchOrderTrades(taker trades)
        #
        #     {
        #         "id": "30341456333942784",
        #         "symbol": "LINK_USDT",
        #         "accountType": "SPOT",
        #         "orderId": "30249408733945856",
        #         "side": "BUY",
        #         "type": "LIMIT",
        #         "matchRole": "MAKER",
        #         "createTime": 1648200366864,
        #         "price": "3.1",
        #         "quantity": "1",
        #         "amount": "3.1",
        #         "feeCurrency": "LINK",
        #         "feeAmount": "0.00145",
        #         "pageId": "30341456333942784",
        #         "clientOrderId": ""
        #     }
        #
        id = self.safe_string_n(trade, ['id', 'tradeID', 'trdId'])
        orderId = self.safe_string_2(trade, 'orderId', 'ordId')
        timestamp = self.safe_integer_n(trade, ['ts', 'createTime', 'cT', 'cTime'])
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        side = self.safe_string_lower_2(trade, 'side', 'takerSide')
        fee = None
        priceString = self.safe_string_2(trade, 'price', 'px')
        amountString = self.safe_string_2(trade, 'quantity', 'qty')
        costString = self.safe_string_2(trade, 'amount', 'amt')
        feeCurrencyId = self.safe_string_2(trade, 'feeCurrency', 'feeCcy')
        feeCostString = self.safe_string_2(trade, 'feeAmount', 'feeAmt')
        if feeCostString is not None:
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': self.safe_string_lower_2(trade, 'ordType', 'type'),  # ordType should take precedence
            'side': side,
            'takerOrMaker': self.safe_string_lower_2(trade, 'matchRole', 'role'),
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://api-docs.poloniex.com/spot/api/public/market-data#trades
        https://api-docs.poloniex.com/v3/futures/api/market/get-execution-info

        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # max 1000, for spot & swap
        if market['contract']:
            response = self.swapPublicGetV3MarketTrades(self.extend(request, params))
            #
            #     {
            #         code: "200",
            #         msg: "Success",
            #         data: [
            #         {
            #             id: "105807320",  # descending order
            #             side: "sell",
            #             px: "84383.93",
            #             qty: "1",
            #             amt: "84.38393",
            #             cT: "1740777074704",
            #         },
            #
            tradesList = self.safe_list(response, 'data')
            return self.parse_trades(tradesList, market, since, limit)
        trades = self.publicGetMarketsSymbolTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id" : "60014521",
        #             "price" : "23162.94",
        #             "quantity" : "0.00009",
        #             "amount" : "2.0846646",
        #             "takerSide" : "SELL",
        #             "ts" : 1659684602042,
        #             "createTime" : 1659684602036
        #         }
        #     ]
        #
        return self.parse_trades(trades, market, since, limit)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user

        https://api-docs.poloniex.com/spot/api/private/trade#trade-history
        https://api-docs.poloniex.com/v3/futures/api/trade/get-execution-details

        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch entries for
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
        if paginate:
            return self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
        isContract = self.in_array(marketType, ['swap', 'future'])
        request: dict = {
            # 'from': 12345678,  # A 'trade Id'. The query begins at ‘from'.
            # 'direction': 'PRE',  # PRE, NEXT The direction before or after ‘from'.
        }
        startKey = 'sTime' if isContract else 'startTime'
        endKey = 'eTime' if isContract else 'endTime'
        if since is not None:
            request[startKey] = since
        if limit is not None:
            request['limit'] = limit
        if isContract and symbol is not None:
            request['symbol'] = market['id']
        request, params = self.handle_until_option(endKey, request, params)
        if isContract:
            raw = self.swapPrivateGetV3TradeOrderTrades(self.extend(request, params))
            #
            #    {
            #        "code": "200",
            #        "msg": "",
            #        "data": [
            #            {
            #                "symbol": "BTC_USDT_PERP",
            #                "trdId": "105813553",
            #                "side": "SELL",
            #                "type": "TRADE",
            #                "mgnMode": "CROSS",
            #                "ordType": "MARKET",
            #                "clOrdId": "polo418912106147315112",
            #                "role": "TAKER",
            #                "px": "84704.9",
            #                "qty": "1",
            #                "cTime": "1740842829430",
            #                "uTime": "1740842829450",
            #                "feeCcy": "USDT",
            #                "feeAmt": "0.04235245",
            #                "deductCcy": "",
            #                "deductAmt": "0",
            #                "feeRate": "0.0005",
            #                "id": "418912106342654592",
            #                "posSide": "BOTH",
            #                "ordId": "418912106147315112",
            #                "qCcy": "USDT",
            #                "value": "84.7049",
            #                "actType": "TRADING"
            #            },
            #
            data = self.safe_list(raw, 'data')
            return self.parse_trades(data, market, since, limit)
        response = self.privateGetTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "32164924331503616",
        #             "symbol": "LINK_USDT",
        #             "accountType": "SPOT",
        #             "orderId": "32164923987566592",
        #             "side": "SELL",
        #             "type": "MARKET",
        #             "matchRole": "TAKER",
        #             "createTime": 1648635115525,
        #             "price": "11",
        #             "quantity": "0.5",
        #             "amount": "5.5",
        #             "feeCurrency": "USDT",
        #             "feeAmount": "0.007975",
        #             "pageId": "32164924331503616",
        #             "clientOrderId": "myOwnId-321"
        #         }
        #     ]
        #
        result = self.parse_trades(response, market, since, limit)
        return result

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'NEW': 'open',
            'PARTIALLY_FILLED': 'open',
            'FILLED': 'closed',
            'PENDING_CANCEL': 'canceled',
            'PARTIALLY_CANCELED': 'canceled',
            'CANCELED': 'canceled',
            'FAILED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # fetchOpenOrder
        #
        #     {
        #         "id" : "7xxxxxxxxxxxxxxx6",
        #         "clientOrderId" : "",
        #         "symbol" : "ETH_USDT",
        #         "state" : "NEW",
        #         "accountType" : "SPOT",
        #         "side" : "BUY",
        #         "type" : "LIMIT",
        #         "timeInForce" : "GTC",
        #         "quantity" : "0.001",
        #         "price" : "1600",
        #         "avgPrice" : "0",
        #         "amount" : "0",
        #         "filledQuantity" : "0",
        #         "filledAmount" : "0",
        #         "createTime" : 16xxxxxxxxx26,
        #         "updateTime" : 16xxxxxxxxx36
        #     }
        #
        # fetchOpenOrders(and fetchClosedOrders same for contracts)
        #
        #  spot:
        #
        #     {
        #         "id": "24993088082542592",
        #         "clientOrderId": "",
        #         "symbol": "ELON_USDC",
        #         "state": "NEW",
        #         "accountType": "SPOT",
        #         "side": "SELL",
        #         "type": "MARKET",
        #         "timeInForce": "GTC",
        #         "quantity": "1.00",
        #         "price": "0.00",
        #         "avgPrice": "0.00",
        #         "amount": "0.00",
        #         "filledQuantity": "0.00",
        #         "filledAmount": "0.00",
        #         "createTime": 1646925216548,
        #         "updateTime": 1646925216548
        #     }
        #
        #  contract:
        #
        #     {
        #         "symbol": "BTC_USDT_PERP",
        #         "side": "BUY",
        #         "type": "LIMIT",
        #         "ordId": "418890767248232148",
        #         "clOrdId": "polo418890767248232148",
        #         "mgnMode": "CROSS",
        #         "px": "81130.13",
        #         "reduceOnly": False,
        #         "lever": "20",
        #         "state": "NEW",
        #         "source": "WEB",
        #         "timeInForce": "GTC",
        #         "tpTrgPx": "",
        #         "tpPx": "",
        #         "tpTrgPxType": "",
        #         "slTrgPx": "",
        #         "slPx": "",
        #         "slTrgPxType": "",
        #         "avgPx": "0",
        #         "execQty": "0",
        #         "execAmt": "0",
        #         "feeCcy": "",
        #         "feeAmt": "0",
        #         "deductCcy": "0",
        #         "deductAmt": "0",
        #         "stpMode": "NONE",  # todo: selfTradePrevention
        #         "cTime": "1740837741523",
        #         "uTime": "1740840846882",
        #         "sz": "1",
        #         "posSide": "BOTH",
        #         "qCcy": "USDT"
        #         "cancelReason": "",  # self field can only be in closed orders
        #     },
        #
        # createOrder, editOrder
        #
        #  spot:
        #
        #     {
        #         "id": "29772698821328896",
        #         "clientOrderId": "1234Abc"
        #     }
        #
        #  contract:
        #
        #    {
        #        "ordId":"418876147745775616",
        #        "clOrdId":"polo418876147745775616"
        #    }
        #
        timestamp = self.safe_integer_n(order, ['timestamp', 'createTime', 'cTime'])
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(order, 'date'))
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        resultingTrades = self.safe_value(order, 'resultingTrades')
        if resultingTrades is not None:
            if not isinstance(resultingTrades, list):
                resultingTrades = self.safe_value(resultingTrades, self.safe_string(market, 'id', marketId))
        price = self.safe_string_n(order, ['price', 'rate', 'px'])
        amount = self.safe_string_2(order, 'quantity', 'sz')
        filled = self.safe_string_2(order, 'filledQuantity', 'execQty')
        status = self.parse_order_status(self.safe_string(order, 'state'))
        side = self.safe_string_lower(order, 'side')
        rawType = self.safe_string(order, 'type')
        type = self.parse_order_type(rawType)
        id = self.safe_string_n(order, ['orderNumber', 'id', 'orderId', 'ordId'])
        fee = None
        feeCurrency = self.safe_string_2(order, 'tokenFeeCurrency', 'feeCcy')
        feeCost: Str = None
        feeCurrencyCode: Str = None
        rate = self.safe_string(order, 'fee')
        if feeCurrency is None:
            feeCurrencyCode = market['base'] if (side == 'buy') else market['quote']
        else:
            # poloniex accepts a 30% discount to pay fees in TRX
            feeCurrencyCode = self.safe_currency_code(feeCurrency)
            feeCost = self.safe_string_2(order, 'tokenFee', 'feeAmt')
        if feeCost is not None:
            fee = {
                'rate': rate,
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        clientOrderId = self.safe_string_2(order, 'clientOrderId', 'clOrdId')
        marginMode = self.safe_string_lower(order, 'mgnMode')
        reduceOnly = self.safe_bool(order, 'reduceOnly')
        leverage = self.safe_integer(order, 'lever')
        hedged = self.safe_string(order, 'posSide') != 'BOTH'
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': self.safe_integer(order, 'updateTime'),
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': self.safe_string(order, 'timeInForce'),
            'postOnly': rawType == 'LIMIT_MAKER',
            'side': side,
            'price': price,
            'triggerPrice': self.safe_string_2(order, 'triggerPrice', 'stopPrice'),
            'cost': self.safe_string(order, 'execAmt'),
            'average': self.safe_string_2(order, 'avgPrice', 'avgPx'),
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'trades': resultingTrades,
            'fee': fee,
            'marginMode': marginMode,
            'reduceOnly': reduceOnly,
            'leverage': leverage,
            'hedged': hedged,
        }, market)

    def parse_order_type(self, status):
        statuses: dict = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'LIMIT_MAKER': 'limit',
            'STOP-LIMIT': 'limit',
            'STOP-MARKET': 'market',
        }
        return self.safe_string(statuses, status, status)

    def parse_open_orders(self, orders, market, result):
        for i in range(0, len(orders)):
            order = orders[i]
            extended = self.extend(order, {
                'status': 'open',
                'type': 'limit',
                'side': order['type'],
                'price': order['rate'],
            })
            result.append(self.parse_order(extended, market))
        return result

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders

        https://api-docs.poloniex.com/spot/api/private/order#open-orders
        https://api-docs.poloniex.com/spot/api/private/smart-order#open-orders  # trigger orders
        https://api-docs.poloniex.com/v3/futures/api/trade/get-current-orders

        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: set True to fetch trigger orders instead of regular orders
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market: Market = None
        request: dict = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchOpenOrders', market, params)
        if limit is not None:
            max = 2000 if (marketType == 'spot') else 100
            request['limit'] = max(limit, max)
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        response = None
        if marketType != 'spot':
            raw = self.swapPrivateGetV3TradeOrderOpens(self.extend(request, params))
            #
            #    {
            #        "code": "200",
            #        "msg": "",
            #        "data": [
            #            {
            #                "symbol": "BTC_USDT_PERP",
            #                "side": "BUY",
            #                "type": "LIMIT",
            #                "ordId": "418890767248232148",
            #                "clOrdId": "polo418890767248232148",
            #                "mgnMode": "CROSS",
            #                "px": "81130.13",
            #                "reduceOnly": False,
            #                "lever": "20",
            #                "state": "NEW",
            #                "source": "WEB",
            #                "timeInForce": "GTC",
            #                "tpTrgPx": "",
            #                "tpPx": "",
            #                "tpTrgPxType": "",
            #                "slTrgPx": "",
            #                "slPx": "",
            #                "slTrgPxType": "",
            #                "avgPx": "0",
            #                "execQty": "0",
            #                "execAmt": "0",
            #                "feeCcy": "",
            #                "feeAmt": "0",
            #                "deductCcy": "0",
            #                "deductAmt": "0",
            #                "stpMode": "NONE",
            #                "cTime": "1740837741523",
            #                "uTime": "1740840846882",
            #                "sz": "1",
            #                "posSide": "BOTH",
            #                "qCcy": "USDT"
            #            },
            #
            response = self.safe_list(raw, 'data')
        elif isTrigger:
            response = self.privateGetSmartorders(self.extend(request, params))
        else:
            response = self.privateGetOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "id" : "7xxxxxxxxxxxxxxx6",
        #             "clientOrderId" : "",
        #             "symbol" : "ETH_USDT",
        #             "state" : "NEW",
        #             "accountType" : "SPOT",
        #             "side" : "BUY",
        #             "type" : "LIMIT",
        #             "timeInForce" : "GTC",
        #             "quantity" : "0.001",
        #             "price" : "1600",
        #             "avgPrice" : "0",
        #             "amount" : "0",
        #             "filledQuantity" : "0",
        #             "filledAmount" : "0",
        #             "stopPrice": "3750.00",              # for trigger orders
        #             "createTime" : 16xxxxxxxxx26,
        #             "updateTime" : 16xxxxxxxxx36
        #         }
        #     ]
        #
        extension: dict = {'status': 'open'}
        return self.parse_orders(response, market, since, limit, extension)

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """

        https://api-docs.poloniex.com/v3/futures/api/trade/get-order-history

        fetches information on multiple closed orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest entry
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = None
        request: dict = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchClosedOrders', market, params, 'swap')
        if marketType == 'spot':
            raise NotSupported(self.id + ' fetchClosedOrders() is not supported for spot markets yet')
        if limit is not None:
            request['limit'] = min(200, limit)
        if since is not None:
            request['sTime'] = since
        request, params = self.handle_until_option('eTime', request, params)
        response = self.swapPrivateGetV3TradeOrderHistory(self.extend(request, params))
        #
        #    {
        #        "code": "200",
        #        "msg": "",
        #        "data": [
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "side": "SELL",
        #                "type": "MARKET",
        #                "ordId": "418912106147315712",
        #                "clOrdId": "polo418912106147315712",
        #                "mgnMode": "CROSS",
        #                "px": "0",
        #                "sz": "2",
        #                "lever": "20",
        #                "state": "FILLED",
        #                "cancelReason": "",
        #                "source": "WEB",
        #                "reduceOnly": "true",
        #                "timeInForce": "GTC",
        #                "tpTrgPx": "",
        #                "tpPx": "",
        #                "tpTrgPxType": "",
        #                "slTrgPx": "",
        #                "slPx": "",
        #                "slTrgPxType": "",
        #                "avgPx": "84705.56",
        #                "execQty": "2",
        #                "execAmt": "169.41112",
        #                "feeCcy": "USDT",
        #                "feeAmt": "0.08470556",
        #                "deductCcy": "0",
        #                "deductAmt": "0",
        #                "stpMode": "NONE",
        #                "cTime": "1740842829116",
        #                "uTime": "1740842829130",
        #                "posSide": "BOTH",
        #                "qCcy": "USDT"
        #            },
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order

        https://api-docs.poloniex.com/spot/api/private/order#create-order
        https://api-docs.poloniex.com/spot/api/private/smart-order#create-order  # trigger orders

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: the price at which a trigger order is triggered at
        :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'side': side.upper(),  # uppercase, both for spot & swap
            # 'timeInForce': timeInForce,  # matches unified values
            # 'accountType': 'SPOT',
            # 'amount': amount,
        }
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        request, params = self.order_request(symbol, type, side, amount, request, price, params)
        response = None
        if market['swap'] or market['future']:
            responseInitial = self.swapPrivatePostV3TradeOrder(self.extend(request, params))
            #
            # {"code":200,"msg":"Success","data":{"ordId":"418876147745775616","clOrdId":"polo418876147745775616"}}
            #
            response = self.safe_dict(responseInitial, 'data')
        elif triggerPrice is not None:
            response = self.privatePostSmartorders(self.extend(request, params))
        else:
            response = self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "id" : "78923648051920896",
        #         "clientOrderId" : ""
        #     }
        #
        return self.parse_order(response, market)

    def order_request(self, symbol, type, side, amount, request, price=None, params={}):
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        market = self.market(symbol)
        if market['contract']:
            marginMode = None
            marginMode, params = self.handle_param_string(params, 'marginMode')
            if marginMode is not None:
                self.check_required_argument('createOrder', marginMode, 'marginMode', ['cross', 'isolated'])
                request['mgnMode'] = marginMode.upper()
            hedged = None
            hedged, params = self.handle_param_string(params, 'hedged')
            if hedged:
                if marginMode is None:
                    raise ArgumentsRequired(self.id + ' createOrder() requires a marginMode parameter "cross" or "isolated" for hedged orders')
                if not ('posSide' in params):
                    raise ArgumentsRequired(self.id + ' createOrder() requires a posSide parameter "LONG" or "SHORT" for hedged orders')
        upperCaseType = type.upper()
        isMarket = upperCaseType == 'MARKET'
        isPostOnly = self.is_post_only(isMarket, upperCaseType == 'LIMIT_MAKER', params)
        params = self.omit(params, ['postOnly', 'triggerPrice', 'stopPrice'])
        if triggerPrice is not None:
            if not market['spot']:
                raise InvalidOrder(self.id + ' createOrder() does not support trigger orders for ' + market['type'] + ' markets')
            upperCaseType = 'STOP' if (price is None) else 'STOP_LIMIT'
            request['stopPrice'] = triggerPrice
        elif isPostOnly:
            upperCaseType = 'LIMIT_MAKER'
        request['type'] = upperCaseType
        if isMarket:
            if side == 'buy':
                quoteAmount = None
                createMarketBuyOrderRequiresPrice = True
                createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
                cost = self.safe_number(params, 'cost')
                params = self.omit(params, 'cost')
                if cost is not None:
                    quoteAmount = self.cost_to_precision(symbol, cost)
                elif createMarketBuyOrderRequiresPrice and market['spot']:
                    if price is None:
                        raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
                    else:
                        amountString = self.number_to_string(amount)
                        priceString = self.number_to_string(price)
                        costRequest = Precise.string_mul(amountString, priceString)
                        quoteAmount = self.cost_to_precision(symbol, costRequest)
                else:
                    quoteAmount = self.cost_to_precision(symbol, amount)
                amountKey = 'amount' if market['spot'] else 'sz'
                request[amountKey] = quoteAmount
            else:
                amountKey = 'quantity' if market['spot'] else 'sz'
                request[amountKey] = self.amount_to_precision(symbol, amount)
        else:
            amountKey = 'quantity' if market['spot'] else 'sz'
            request[amountKey] = self.amount_to_precision(symbol, amount)
            priceKey = 'price' if market['spot'] else 'px'
            request[priceKey] = self.price_to_precision(symbol, price)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['clientOrderId'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        # remember the timestamp before issuing the request
        return [request, params]

    def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        """
        edit a trade order

        https://api-docs.poloniex.com/spot/api/private/order#cancel-replace-order
        https://api-docs.poloniex.com/spot/api/private/smart-order#cancel-replace-order

        :param str id: order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float [amount]: how much of the currency you want to trade in units of the base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: The price at which a trigger order is triggered at
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request: dict = {
            'id': id,
            # 'timeInForce': timeInForce,
        }
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        request, params = self.order_request(symbol, type, side, amount, request, price, params)
        response = None
        if triggerPrice is not None:
            response = self.privatePutSmartordersId(self.extend(request, params))
        else:
            response = self.privatePutOrdersId(self.extend(request, params))
        #
        #     {
        #         "id" : "78923648051920896",
        #         "clientOrderId" : ""
        #     }
        #
        response = self.extend(response, {
            'side': side,
            'type': type,
        })
        return self.parse_order(response, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        #
        # @method
        # @name poloniex#cancelOrder
        # @description cancels an open order
        # @see https://api-docs.poloniex.com/spot/api/private/order#cancel-order-by-id
        # @see https://api-docs.poloniex.com/spot/api/private/smart-order#cancel-order-by-id  # trigger orders
        # @param {string} id order id
        # @param {string} symbol unified symbol of the market the order was made in
        # @param {object} [params] extra parameters specific to the exchange API endpoint
        # @param {boolean} [params.trigger] True if canceling a trigger order
        # @returns {object} An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        #
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        market = self.market(symbol)
        request: dict = {}
        if not market['spot']:
            request['symbol'] = market['id']
            request['ordId'] = id
            raw = self.swapPrivateDeleteV3TradeOrder(self.extend(request, params))
            #
            #    {
            #        "code": "200",
            #        "msg": "Success",
            #        "data": {
            #            "ordId": "418886099910612040",
            #            "clOrdId": "polo418886099910612040"
            #        }
            #    }
            #
            return self.parse_order(self.safe_dict(raw, 'data'))
        clientOrderId = self.safe_value(params, 'clientOrderId')
        if clientOrderId is not None:
            id = clientOrderId
        request['id'] = id
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['clientOrderId', 'trigger', 'stop'])
        response = None
        if isTrigger:
            response = self.privateDeleteSmartordersId(self.extend(request, params))
        else:
            response = self.privateDeleteOrdersId(self.extend(request, params))
        #
        #   {
        #       "orderId":"210832697138888704",
        #       "clientOrderId":"",
        #       "state":"PENDING_CANCEL",
        #       "code":200,
        #       "message":""
        #   }
        #
        return self.parse_order(response)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders

        https://api-docs.poloniex.com/spot/api/private/order#cancel-all-orders
        https://api-docs.poloniex.com/spot/api/private/smart-order#cancel-all-orders  # trigger orders
        https://api-docs.poloniex.com/v3/futures/api/trade/cancel-all-orders - contract markets

        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if canceling trigger orders
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            # 'accountTypes': 'SPOT',
            'symbols': [],
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbols'] = [
                market['id'],
            ]
        response = None
        marketType = None
        marketType, params = self.handle_market_type_and_params('cancelAllOrders', market, params)
        if marketType == 'swap' or marketType == 'future':
            raw = self.swapPrivateDeleteV3TradeAllOrders(self.extend(request, params))
            #
            #    {
            #        "code": "200",
            #        "msg": "Success",
            #        "data": [
            #            {
            #                "code": "200",
            #                "msg": "Success",
            #                "ordId": "418885787866388511",
            #                "clOrdId": "polo418885787866388511"
            #            }
            #        ]
            #    }
            #
            response = self.safe_list(raw, 'data')
            return self.parse_orders(response, market)
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        if isTrigger:
            response = self.privateDeleteSmartorders(self.extend(request, params))
        else:
            response = self.privateDeleteOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "orderId" : "78xxxxxxxx80",
        #             "clientOrderId" : "",
        #             "state" : "NEW",
        #             "code" : 200,
        #             "message" : ""
        #         }, {
        #             "orderId" : "78xxxxxxxxx80",
        #             "clientOrderId" : "",
        #             "state" : "NEW",
        #             "code" : 200,
        #             "message" : ""
        #         }
        #     ]
        #
        return self.parse_orders(response, market)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetch an order by it's id

        https://api-docs.poloniex.com/spot/api/private/order#order-details
        https://api-docs.poloniex.com/spot/api/private/smart-order#open-orders  # trigger orders

        :param str id: order id
        :param str symbol: unified market symbol, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if fetching a trigger order
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        id = str(id)
        request: dict = {
            'id': id,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchOrder', market, params)
        if marketType != 'spot':
            raise NotSupported(self.id + ' fetchOrder() is not supported for ' + marketType + ' markets yet')
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        response = None
        if isTrigger:
            response = self.privateGetSmartordersId(self.extend(request, params))
            response = self.safe_value(response, 0)
        else:
            response = self.privateGetOrdersId(self.extend(request, params))
        #
        #     {
        #         "id": "21934611974062080",
        #         "clientOrderId": "123",
        #         "symbol": "TRX_USDC",
        #         "state": "NEW",
        #         "accountType": "SPOT",
        #         "side": "SELL",
        #         "type": "LIMIT",
        #         "timeInForce": "GTC",
        #         "quantity": "1.00",
        #         "price": "10.00",
        #         "avgPrice": "0.00",
        #         "amount": "0.00",
        #         "filledQuantity": "0.00",
        #         "filledAmount": "0.00",
        #         "stopPrice": "3750.00",              # for trigger orders
        #         "createTime": 1646196019020,
        #         "updateTime": 1646196019020
        #     }
        #
        order = self.parse_order(response)
        order['id'] = id
        return order

    def fetch_order_status(self, id: str, symbol: Str = None, params={}):
        self.load_markets()
        orders = self.fetch_open_orders(symbol, None, None, params)
        indexed = self.index_by(orders, 'id')
        return 'open' if (id in indexed) else 'closed'

    def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order

        https://api-docs.poloniex.com/spot/api/private/trade#trades-by-order-id

        :param str id: order id
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        self.load_markets()
        request: dict = {
            'id': id,
        }
        trades = self.privateGetOrdersIdTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "30341456333942784",
        #             "symbol": "LINK_USDT",
        #             "accountType": "SPOT",
        #             "orderId": "30249408733945856",
        #             "side": "BUY",
        #             "type": "LIMIT",
        #             "matchRole": "MAKER",
        #             "createTime": 1648200366864,
        #             "price": "3.1",
        #             "quantity": "1",
        #             "amount": "3.1",
        #             "feeCurrency": "LINK",
        #             "feeAmount": "0.00145",
        #             "pageId": "30341456333942784",
        #             "clientOrderId": ""
        #         }
        #     ]
        #
        return self.parse_trades(trades)

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        # for swap
        if not isinstance(response, list):
            ts = self.safe_integer(response, 'uTime')
            result['timestamp'] = ts
            result['datetime'] = self.iso8601(ts)
            details = self.safe_list(response, 'details', [])
            for i in range(0, len(details)):
                balance = details[i]
                currencyId = self.safe_string(balance, 'ccy')
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['total'] = self.safe_string(balance, 'avail')
                account['used'] = self.safe_string(balance, 'im')
                result[code] = account
            return self.safe_balance(result)
        # for spot
        for i in range(0, len(response)):
            account = self.safe_value(response, i, {})
            balances = self.safe_value(account, 'balances')
            for j in range(0, len(balances)):
                balance = self.safe_value(balances, j)
                currencyId = self.safe_string(balance, 'currency')
                code = self.safe_currency_code(currencyId)
                newAccount = self.account()
                newAccount['free'] = self.safe_string(balance, 'available')
                newAccount['used'] = self.safe_string(balance, 'hold')
                result[code] = newAccount
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        https://api-docs.poloniex.com/spot/api/private/account#all-account-balances
        https://api-docs.poloniex.com/v3/futures/api/account/balance

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        self.load_markets()
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchBalance', None, params)
        if marketType != 'spot':
            responseRaw = self.swapPrivateGetV3AccountBalance(params)
            #
            #    {
            #        "code": "200",
            #        "msg": "",
            #        "data": {
            #            "state": "NORMAL",
            #            "eq": "9.98571622",
            #            "isoEq": "0",
            #            "im": "0",
            #            "mm": "0",
            #            "mmr": "0",
            #            "upl": "0",
            #            "availMgn": "9.98571622",
            #            "cTime": "1738093601775",
            #            "uTime": "1740829116236",
            #            "details": [
            #                {
            #                    "ccy": "USDT",
            #                    "eq": "9.98571622",
            #                    "isoEq": "0",
            #                    "avail": "9.98571622",
            #                    "trdHold": "0",
            #                    "upl": "0",
            #                    "isoAvail": "0",
            #                    "isoHold": "0",
            #                    "isoUpl": "0",
            #                    "im": "0",
            #                    "mm": "0",
            #                    "mmr": "0",
            #                    "imr": "0",
            #                    "cTime": "1740829116236",
            #                    "uTime": "1740829116236"
            #                }
            #            ]
            #        }
            #    }
            #
            data = self.safe_dict(responseRaw, 'data', {})
            return self.parse_balance(data)
        request: dict = {
            'accountType': 'SPOT',
        }
        response = self.privateGetAccountsBalances(self.extend(request, params))
        #
        #     [
        #         {
        #             "accountId" : "7xxxxxxxxxx8",
        #             "accountType" : "SPOT",
        #             "balances" : [
        #                 {
        #                     "currencyId" : "214",
        #                     "currency" : "USDT",
        #                     "available" : "2.00",
        #                     "hold" : "0.00"
        #                 }
        #             ]
        #         }
        #     ]
        #
        return self.parse_balance(response)

    def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets

        https://api-docs.poloniex.com/spot/api/private/account#fee-info

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
        """
        self.load_markets()
        response = self.privateGetFeeinfo(params)
        #
        #     {
        #         "trxDiscount" : False,
        #         "makerRate" : "0.00145",
        #         "takerRate" : "0.00155",
        #         "volume30D" : "0.00"
        #     }
        #
        result: dict = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': self.safe_number(response, 'makerRate'),
                'taker': self.safe_number(response, 'takerRate'),
                'percentage': True,
                'tierBased': True,
            }
        return result

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://api-docs.poloniex.com/spot/api/public/market-data#order-book
        https://api-docs.poloniex.com/v3/futures/api/market/get-order-book

        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # The default value of limit is 10. Valid limit values are: 5, 10, 20, 50, 100, 150.
            if market['contract']:
                request['limit'] = self.find_nearest_ceiling([5, 10, 20, 100, 150], limit)
        if market['contract']:
            responseRaw = self.swapPublicGetV3MarketOrderBook(self.extend(request, params))
            #
            #    {
            #       "code": 200,
            #       "data": {
            #         "asks": [["58700", "9934"], ..],
            #         "bids": [["58600", "9952"], ..],
            #         "s": "100",
            #         "ts": 1719974138333
            #       },
            #       "msg": "Success"
            #    }
            #
            data = self.safe_dict(responseRaw, 'data', {})
            ts = self.safe_integer(data, 'ts')
            return self.parse_order_book(data, symbol, ts)
        response = self.publicGetMarketsSymbolOrderBook(self.extend(request, params))
        #
        #     {
        #         "time" : 1659695219507,
        #         "scale" : "-1",
        #         "asks" : ["23139.82", "0.317981", "23140", "0.191091", "23170.06", "0.01", "23200", "0.107758", "23230.55", "0.01", "23247.2", "0.154", "23254", "0.005121", "23263", "0.038", "23285.4", "0.308", "23300", "0.108896"],
        #         "bids" : ["23139.74", "0.432092", "23139.73", "0.198592", "23123.21", "0.000886", "23123.2", "0.308", "23121.4", "0.154", "23105", "0.000789", "23100", "0.078175", "23069.1", "0.026276", "23068.83", "0.001329", "23051", "0.000048"],
        #         "ts" : 1659695219513
        #     }
        #
        timestamp = self.safe_integer(response, 'time')
        asks = self.safe_value(response, 'asks')
        bids = self.safe_value(response, 'bids')
        asksResult = []
        bidsResult = []
        for i in range(0, len(asks)):
            if (i % 2) < 1:
                price = self.safe_number(asks, i)
                amount = self.safe_number(asks, self.sum(i, 1))
                asksResult.append([price, amount])
        for i in range(0, len(bids)):
            if (i % 2) < 1:
                price = self.safe_number(bids, i)
                amount = self.safe_number(bids, self.sum(i, 1))
                bidsResult.append([price, amount])
        return {
            'symbol': market['symbol'],
            'bids': self.sort_by(bidsResult, 0, True),
            'asks': self.sort_by(asksResult, 0),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'nonce': None,
        }

    def create_deposit_address(self, code: str, params={}) -> DepositAddress:
        """
        create a currency deposit address

        https://api-docs.poloniex.com/spot/api/private/wallet#deposit-addresses

        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] = request['currency'] + network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        else:
            if currency['id'] == 'USDT':
                raise ArgumentsRequired(self.id + ' createDepositAddress requires a network parameter for ' + code + '.')
        response = self.privatePostWalletsAddress(self.extend(request, params))
        #
        #     {
        #         "address" : "0xfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxf"
        #     }
        #
        address = self.safe_string(response, 'address')
        tag: Str = None
        self.check_address(address)
        if currency is not None:
            depositAddress = self.safe_string(currency['info'], 'depositAddress')
            if depositAddress is not None:
                tag = address
                address = depositAddress
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': response,
        }

    def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
        """
        fetch the deposit address for a currency associated with self account

        https://api-docs.poloniex.com/spot/api/private/wallet#deposit-addresses

        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] = request['currency'] + network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        else:
            if currency['id'] == 'USDT':
                raise ArgumentsRequired(self.id + ' fetchDepositAddress requires a network parameter for ' + code + '.')
        response = self.privateGetWalletsAddresses(self.extend(request, params))
        #
        #     {
        #         "USDTTRON" : "Txxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxp"
        #     }
        #
        address = self.safe_string(response, request['currency'])
        tag: Str = None
        self.check_address(address)
        if currency is not None:
            depositAddress = self.safe_string(currency['info'], 'depositAddress')
            if depositAddress is not None:
                tag = address
                address = depositAddress
        return {
            'info': response,
            'currency': code,
            'network': network,
            'address': address,
            'tag': tag,
        }

    def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account

        https://api-docs.poloniex.com/spot/api/private/account#accounts-transfer

        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, fromAccount)
        request: dict = {
            'amount': self.currency_to_precision(code, amount),
            'currency': currency['id'],
            'fromAccount': fromId,
            'toAccount': toId,
        }
        response = self.privatePostAccountsTransfer(self.extend(request, params))
        #
        #    {
        #        "transferId" : "168041074"
        #    }
        #
        return self.parse_transfer(response, currency)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        #    {
        #        "transferId" : "168041074"
        #    }
        #
        return {
            'info': transfer,
            'id': self.safe_string(transfer, 'transferId'),
            'timestamp': None,
            'datetime': None,
            'currency': self.safe_string(currency, 'id'),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': None,
        }

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}) -> Transaction:
        """
        make a withdrawal

        https://api-docs.poloniex.com/spot/api/private/wallet#withdraw-currency

        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
            'amount': amount,
            'address': address,
        }
        if tag is not None:
            request['paymentId'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] = request['currency'] + network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        response = self.privatePostWalletsWithdraw(self.extend(request, params))
        #
        #     {
        #         "response": "Withdrew 1.00000000 USDT.",
        #         "email2FA": False,
        #         "withdrawalNumber": 13449869
        #     }
        #
        return self.parse_transaction(response, currency)

    def fetch_transactions_helper(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        self.load_markets()
        year = 31104000  # 60 * 60 * 24 * 30 * 12 = one year of history, why not
        now = self.seconds()
        start = self.parse_to_int(since / 1000) if (since is not None) else now - 10 * year
        request: dict = {
            'start': start,  # UNIX timestamp, required
            'end': now,  # UNIX timestamp, required
        }
        response = self.privateGetWalletsActivity(self.extend(request, params))
        #
        #     {
        #         "adjustments":[],
        #         "deposits":[
        #             {
        #                 "currency": "BTC",
        #                 "address": "1MEtiqJWru53FhhHrfJPPvd2tC3TPDVcmW",
        #                 "amount": "0.01063000",
        #                 "confirmations":  1,
        #                 "txid": "952b0e1888d6d491591facc0d37b5ebec540ac1efb241fdbc22bcc20d1822fb6",
        #                 "timestamp":  1507916888,
        #                 "status": "COMPLETE"
        #             },
        #             {
        #                 "currency": "ETH",
        #                 "address": "0x20108ba20b65c04d82909e91df06618107460197",
        #                 "amount": "4.00000000",
        #                 "confirmations": 38,
        #                 "txid": "0x4be260073491fe63935e9e0da42bd71138fdeb803732f41501015a2d46eb479d",
        #                 "timestamp": 1525060430,
        #                 "status": "COMPLETE"
        #             }
        #         ],
        #         "withdrawals":[
        #             {
        #                 "withdrawalNumber":13449869,
        #                 "currency":"USDTTRON",  # not documented in API docs, see commonCurrencies in describe()
        #                 "address":"TXGaqPW23JdRWhsVwS2mRsGsegbdnAd3Rw",
        #                 "amount":"1.00000000",
        #                 "fee":"0.00000000",
        #                 "timestamp":1591573420,
        #                 "status":"COMPLETE: dadf427224b3d44b38a2c13caa4395e4666152556ca0b2f67dbd86a95655150f",
        #                 "ipAddress":"x.x.x.x",
        #                 "canCancel":0,
        #                 "canResendEmail":0,
        #                 "paymentID":null,
        #                 "scope":"crypto"
        #             },
        #             {
        #                 "withdrawalNumber": 8224394,
        #                 "currency": "EMC2",
        #                 "address": "EYEKyCrqTNmVCpdDV8w49XvSKRP9N3EUyF",
        #                 "amount": "63.10796020",
        #                 "fee": "0.01000000",
        #                 "timestamp": 1510819838,
        #                 "status": "COMPLETE: d37354f9d02cb24d98c8c4fc17aa42f475530b5727effdf668ee5a43ce667fd6",
        #                 "ipAddress": "x.x.x.x"
        #             },
        #             {
        #                 "withdrawalNumber": 9290444,
        #                 "currency": "ETH",
        #                 "address": "0x191015ff2e75261d50433fbd05bd57e942336149",
        #                 "amount": "0.15500000",
        #                 "fee": "0.00500000",
        #                 "timestamp": 1514099289,
        #                 "status": "COMPLETE: 0x12d444493b4bca668992021fd9e54b5292b8e71d9927af1f076f554e4bea5b2d",
        #                 "ipAddress": "x.x.x.x"
        #             },
        #             {
        #                 "withdrawalNumber": 11518260,
        #                 "currency": "BTC",
        #                 "address": "8JoDXAmE1GY2LRK8jD1gmAmgRPq54kXJ4t",
        #                 "amount": "0.20000000",
        #                 "fee": "0.00050000",
        #                 "timestamp": 1527918155,
        #                 "status": "COMPLETE: 1864f4ebb277d90b0b1ff53259b36b97fa1990edc7ad2be47c5e0ab41916b5ff",
        #                 "ipAddress": "x.x.x.x"
        #             }
        #         ]
        #     }
        #
        return response

    def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals

        https://api-docs.poloniex.com/spot/api/private/wallet#wallets-activity-records

        :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        response = self.fetch_transactions_helper(code, since, limit, params)
        currency: Currency = None
        if code is not None:
            currency = self.currency(code)
        withdrawals = self.safe_value(response, 'withdrawals', [])
        deposits = self.safe_value(response, 'deposits', [])
        withdrawalTransactions = self.parse_transactions(withdrawals, currency, since, limit)
        depositTransactions = self.parse_transactions(deposits, currency, since, limit)
        transactions = self.array_concat(depositTransactions, withdrawalTransactions)
        return self.filter_by_currency_since_limit(self.sort_by(transactions, 'timestamp'), code, since, limit)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account

        https://api-docs.poloniex.com/spot/api/private/wallet#wallets-activity-records

        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        response = self.fetch_transactions_helper(code, since, limit, params)
        currency: Currency = None
        if code is not None:
            currency = self.currency(code)
        withdrawals = self.safe_value(response, 'withdrawals', [])
        transactions = self.parse_transactions(withdrawals, currency, since, limit)
        return self.filter_by_currency_since_limit(transactions, code, since, limit)

    def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees

        https://api-docs.poloniex.com/spot/api/public/reference-data#currency-information

        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        self.load_markets()
        response = self.publicGetCurrencies(self.extend(params, {'includeMultiChainCurrencies': True}))
        #
        #     [
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             }
        #         }
        #     ]
        #
        data: dict = {}
        for i in range(0, len(response)):
            entry = response[i]
            currencies = list(entry.keys())
            currencyId = self.safe_string(currencies, 0)
            data[currencyId] = entry[currencyId]
        return self.parse_deposit_withdraw_fees(data, codes)

    def parse_deposit_withdraw_fees(self, response, codes=None, currencyIdKey=None):
        #
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             },
        #         }
        #
        depositWithdrawFees: dict = {}
        codes = self.market_codes(codes)
        responseKeys = list(response.keys())
        for i in range(0, len(responseKeys)):
            currencyId = responseKeys[i]
            code = self.safe_currency_code(currencyId)
            feeInfo = response[currencyId]
            if (codes is None) or (self.in_array(code, codes)):
                currency = self.currency(code)
                depositWithdrawFees[code] = self.parse_deposit_withdraw_fee(feeInfo, currency)
                childChains = self.safe_value(feeInfo, 'childChains')
                chainsLength = len(childChains)
                if chainsLength > 0:
                    for j in range(0, len(childChains)):
                        networkId = childChains[j]
                        networkId = networkId.replace(code, '')
                        networkCode = self.network_id_to_code(networkId)
                        networkInfo = self.safe_value(response, networkId)
                        networkObject: dict = {}
                        withdrawFee = self.safe_number(networkInfo, 'withdrawalFee')
                        networkObject[networkCode] = {
                            'withdraw': {
                                'fee': withdrawFee,
                                'percentage': False if (withdrawFee is not None) else None,
                            },
                            'deposit': {
                                'fee': None,
                                'percentage': None,
                            },
                        }
                        depositWithdrawFees[code]['networks'] = self.extend(depositWithdrawFees[code]['networks'], networkObject)
        return depositWithdrawFees

    def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
        depositWithdrawFee = self.deposit_withdraw_fee({})
        depositWithdrawFee['info'][currency['code']] = fee
        networkId = self.safe_string(fee, 'blockchain')
        withdrawFee = self.safe_number(fee, 'withdrawalFee')
        withdrawResult: dict = {
            'fee': withdrawFee,
            'percentage': False if (withdrawFee is not None) else None,
        }
        depositResult: dict = {
            'fee': None,
            'percentage': None,
        }
        depositWithdrawFee['withdraw'] = withdrawResult
        depositWithdrawFee['deposit'] = depositResult
        networkCode = self.network_id_to_code(networkId)
        depositWithdrawFee['networks'][networkCode] = {
            'withdraw': withdrawResult,
            'deposit': depositResult,
        }
        return depositWithdrawFee

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account

        https://api-docs.poloniex.com/spot/api/private/wallet#wallets-activity-records

        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        response = self.fetch_transactions_helper(code, since, limit, params)
        currency = None
        if code is not None:
            currency = self.currency(code)
        deposits = self.safe_value(response, 'deposits', [])
        transactions = self.parse_transactions(deposits, currency, since, limit)
        return self.filter_by_currency_since_limit(transactions, code, since, limit)

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'COMPLETE': 'ok',
            'COMPLETED': 'ok',
            'AWAITING APPROVAL': 'pending',
            'AWAITING_APPROVAL': 'pending',
            'PENDING': 'pending',
            'PROCESSING': 'pending',
            'COMPLETE ERROR': 'failed',
            'COMPLETE_ERROR': 'failed',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # deposits
        #
        #     {
        #         "txid": "f49d489616911db44b740612d19464521179c76ebe9021af85b6de1e2f8d68cd",
        #         "amount": "49798.01987021",
        #         "status": "COMPLETE",
        #         "address": "DJVJZ58tJC8UeUv9Tqcdtn6uhWobouxFLT",
        #         "currency": "DOGE",
        #         "timestamp": 1524321838,
        #         "confirmations": 3371,
        #         "depositNumber": 134587098
        #     }
        #
        # withdrawals
        #
        #     {
        #         "withdrawalRequestsId": 7397527,
        #         "currency": "ETC",
        #         "address": "0x26419a62055af459d2cd69bb7392f5100b75e304",
        #         "amount": "13.19951600",
        #         "fee": "0.01000000",
        #         "timestamp": 1506010932,
        #         "status": "COMPLETED",
        #         "txid": "343346392f82ac16e8c2604f2a604b7b2382d0e9d8030f673821f8de4b5f5bk",
        #         "ipAddress": "1.2.3.4",
        #         "paymentID": null
        #     }
        #
        # withdraw
        #
        #     {
        #         "withdrawalRequestsId": 33485231
        #     }
        #
        timestamp = self.safe_timestamp(transaction, 'timestamp')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        status = self.safe_string(transaction, 'status', 'pending')
        status = self.parse_transaction_status(status)
        txid = self.safe_string(transaction, 'txid')
        type = 'withdrawal' if ('withdrawalRequestsId' in transaction) else 'deposit'
        id = self.safe_string_2(transaction, 'withdrawalRequestsId', 'depositNumber')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'paymentID')
        amountString = self.safe_string(transaction, 'amount')
        feeCostString = self.safe_string(transaction, 'fee')
        if type == 'withdrawal':
            amountString = Precise.string_sub(amountString, feeCostString)
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': self.parse_number(amountString),
            'network': None,
            'address': address,
            'addressTo': None,
            'addressFrom': None,
            'tag': tag,
            'tagTo': None,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'comment': None,
            'internal': None,
            'fee': {
                'currency': code,
                'cost': self.parse_number(feeCostString),
                'rate': None,
            },
        }

    def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market

        https://api-docs.poloniex.com/v3/futures/api/positions/set-leverage

        :param int leverage: the rate of leverage
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: 'cross' or 'isolated'
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('setLeverage', params)
        if marginMode is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a marginMode parameter "cross" or "isolated"')
        hedged: Bool = None
        hedged, params = self.handle_param_bool(params, 'hedged', False)
        if hedged:
            if not ('posSide' in params):
                raise ArgumentsRequired(self.id + ' setLeverage() requires a posSide parameter for hedged mode: "LONG" or "SHORT"')
        request: dict = {
            'lever': leverage,
            'mgnMode': marginMode.upper(),
            'symbol': market['id'],
        }
        response = self.swapPrivatePostV3PositionLeverage(self.extend(request, params))
        return response

    def fetch_leverage(self, symbol: str, params={}) -> Leverage:
        """
        fetch the set leverage for a market

        https://api-docs.poloniex.com/v3/futures/api/positions/get-leverages

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchLeverage', params)
        if marginMode is None:
            raise ArgumentsRequired(self.id + ' fetchLeverage() requires a marginMode parameter "cross" or "isolated"')
        request['mgnMode'] = marginMode.upper()
        response = self.swapPrivateGetV3PositionLeverages(self.extend(request, params))
        #
        #  for one-way mode:
        #
        #    {
        #        "code": "200",
        #        "msg": "",
        #        "data": [
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "lever": "10",
        #                "mgnMode": "CROSS",
        #                "posSide": "BOTH"
        #            }
        #        ]
        #    }
        #
        #  for hedge:
        #
        #    {
        #        "code": "200",
        #        "msg": "",
        #        "data": [
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "lever": "20",
        #                "mgnMode": "CROSS",
        #                "posSide": "SHORT"
        #            },
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "lever": "20",
        #                "mgnMode": "CROSS",
        #                "posSide": "LONG"
        #            }
        #        ]
        #    }
        #
        return self.parse_leverage(response, market)

    def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
        shortLeverage: Int = None
        longLeverage: Int = None
        marketId: Str = None
        marginMode: Str = None
        data = self.safe_list(leverage, 'data')
        for i in range(0, len(data)):
            entry = data[i]
            marketId = self.safe_string(entry, 'symbol')
            marginMode = self.safe_string(entry, 'mgnMode')
            lever = self.safe_integer(entry, 'lever')
            posSide = self.safe_string(entry, 'posSide')
            if posSide == 'LONG':
                longLeverage = lever
            elif posSide == 'SHORT':
                shortLeverage = lever
            else:
                longLeverage = lever
                shortLeverage = lever
        return {
            'info': leverage,
            'symbol': self.safe_symbol(marketId, market),
            'marginMode': marginMode,
            'longLeverage': longLeverage,
            'shortLeverage': shortLeverage,
        }

    def fetch_position_mode(self, symbol: Str = None, params={}):
        """
        fetchs the position mode, hedged or one way, hedged for binance is set identically for all linear markets or all inverse markets

        https://api-docs.poloniex.com/v3/futures/api/positions/position-mode-switch

        :param str symbol: unified symbol of the market to fetch the order book for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an object detailing whether the market is in hedged or one-way mode
        """
        response = self.swapPrivateGetV3PositionMode(params)
        #
        #    {
        #        "code": "200",
        #        "msg": "Success",
        #        "data": {
        #            "posMode": "ONE_WAY"
        #        }
        #    }
        #
        data = self.safe_dict(response, 'data', {})
        posMode = self.safe_string(data, 'posMode')
        hedged = posMode == 'HEDGE'
        return {
            'info': response,
            'hedged': hedged,
        }

    def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
        """
        set hedged to True or False for a market

        https://api-docs.poloniex.com/v3/futures/api/positions/position-mode-switch

        :param bool hedged: set to True to use dualSidePosition
        :param str symbol: not used by binance setPositionMode()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        mode = 'HEDGE' if hedged else 'ONE_WAY'
        request: dict = {
            'posMode': mode,
        }
        response = self.swapPrivatePostV3PositionMode(self.extend(request, params))
        #
        #    {
        #        "code": "200",
        #        "msg": "Success",
        #        "data": {}
        #    }
        #
        return response

    def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
        """
        fetch all open positions

        https://api-docs.poloniex.com/v3/futures/api/positions/get-current-position

        :param str[]|None symbols: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.standard]: whether to fetch standard contract positions
        :returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = self.swapPrivateGetV3TradePositionOpens(params)
        #
        #    {
        #        "code": "200",
        #        "msg": "",
        #        "data": [
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "posSide": "LONG",
        #                "side": "BUY",
        #                "mgnMode": "CROSS",
        #                "openAvgPx": "94193.42",
        #                "qty": "1",
        #                "availQty": "1",
        #                "lever": "20",
        #                "adl": "0.3007",
        #                "liqPx": "84918.201844064386317906",
        #                "im": "4.7047795",
        #                "mm": "0.56457354",
        #                "upl": "-0.09783",
        #                "uplRatio": "-0.0207",
        #                "pnl": "0",
        #                "markPx": "94095.59",
        #                "mgnRatio": "0.0582",
        #                "state": "NORMAL",
        #                "cTime": "1740950344401",
        #                "uTime": "1740950344401",
        #                "mgn": "4.7047795",
        #                "actType": "TRADING",
        #                "maxWAmt": "0",
        #                "tpTrgPx": "",
        #                "slTrgPx": ""
        #            }
        #        ]
        #    }
        #
        positions = self.safe_list(response, 'data', [])
        return self.parse_positions(positions, symbols)

    def parse_position(self, position: dict, market: Market = None):
        #
        #            {
        #                "symbol": "BTC_USDT_PERP",
        #                "posSide": "LONG",
        #                "side": "BUY",
        #                "mgnMode": "CROSS",
        #                "openAvgPx": "94193.42",
        #                "qty": "1",
        #                "availQty": "1",
        #                "lever": "20",
        #                "adl": "0.3007",
        #                "liqPx": "84918.201844064386317906",
        #                "im": "4.7047795",
        #                "mm": "0.56457354",
        #                "upl": "-0.09783",
        #                "uplRatio": "-0.0207",
        #                "pnl": "0",
        #                "markPx": "94095.59",
        #                "mgnRatio": "0.0582",
        #                "state": "NORMAL",
        #                "cTime": "1740950344401",
        #                "uTime": "1740950344401",
        #                "mgn": "4.7047795",
        #                "actType": "TRADING",
        #                "maxWAmt": "0",
        #                "tpTrgPx": "",
        #                "slTrgPx": ""
        #            }
        #
        marketId = self.safe_string(position, 'symbol')
        market = self.safe_market(marketId, market)
        timestamp = self.safe_integer(position, 'cTime')
        marginMode = self.safe_string_lower(position, 'mgnMode')
        leverage = self.safe_string(position, 'lever')
        initialMargin = self.safe_string(position, 'im')
        notional = Precise.string_mul(leverage, initialMargin)
        qty = self.safe_string(position, 'qty')
        avgPrice = self.safe_string(position, 'openAvgPx')
        collateral = Precise.string_mul(qty, avgPrice)
        # todo: some more fields
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': market['symbol'],
            'notional': notional,
            'marginMode': marginMode,
            'liquidationPrice': self.safe_number(position, 'liqPx'),
            'entryPrice': self.safe_number(position, 'openAvgPx'),
            'unrealizedPnl': self.safe_number(position, 'upl'),
            'percentage': None,
            'contracts': self.safe_number(position, 'qty'),
            'contractSize': None,
            'markPrice': self.safe_number(position, 'markPx'),
            'lastPrice': None,
            'side': self.safe_string_lower(position, 'posSide'),
            'hedged': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastUpdateTimestamp': None,
            'maintenanceMargin': self.safe_number(position, 'mm'),
            'maintenanceMarginPercentage': None,
            'collateral': collateral,
            'initialMargin': initialMargin,
            'initialMarginPercentage': None,
            'leverage': int(leverage),
            'marginRatio': self.safe_number(position, 'mgnRatio'),
            'stopLossPrice': self.safe_number(position, 'slTrgPx'),
            'takeProfitPrice': self.safe_number(position, 'tpTrgPx'),
        })

    def modify_margin_helper(self, symbol: str, amount, type, params={}) -> MarginModification:
        self.load_markets()
        market = self.market(symbol)
        amount = self.amount_to_precision(symbol, amount)
        request: dict = {
            'symbol': market['id'],
            'amt': Precise.string_abs(amount),
            'type': type.upper(),  # 'ADD' or 'REDUCE'
        }
        # todo: hedged handling, tricky
        if not ('posMode' in params):
            request['posMode'] = 'BOTH'
        response = self.swapPrivatePostV3TradePositionMargin(self.extend(request, params))
        #
        # {
        #     "code": 200,
        #     "data": {
        #       "amt": "50",
        #       "lever": "20",
        #       "symbol": "DOT_USDT_PERP",
        #       "posSide": "BOTH",
        #       "type": "ADD"
        #     },
        #     "msg": "Success"
        # }
        #
        if type == 'reduce':
            amount = Precise.string_abs(amount)
        data = self.safe_dict(response, 'data')
        return self.parse_margin_modification(data, market)

    def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId, market)
        rawType = self.safe_string(data, 'type')
        type = 'add' if (rawType == 'ADD') else 'reduce'
        return {
            'info': data,
            'symbol': market['symbol'],
            'type': type,
            'marginMode': None,
            'amount': self.safe_number(data, 'amt'),
            'total': None,
            'code': None,
            'status': 'ok',
            'timestamp': None,
            'datetime': None,
        }

    def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        remove margin from a position
        :param str symbol: unified market symbol
        :param float amount: the amount of margin to remove
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
        """
        return self.modify_margin_helper(symbol, -amount, 'reduce', params)

    def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        add margin
        :param str symbol: unified market symbol
        :param float amount: amount of margin to add
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
        """
        return self.modify_margin_helper(symbol, amount, 'add', params)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api']['spot']
        if self.in_array(api, ['swapPublic', 'swapPrivate']):
            url = self.urls['api']['swap']
        query = self.omit(params, self.extract_params(path))
        implodedPath = self.implode_params(path, params)
        if api == 'public' or api == 'swapPublic':
            url += '/' + implodedPath
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            timestamp = str(self.nonce())
            auth = method + "\n"  # eslint-disable-line quotes
            url += '/' + implodedPath
            auth += '/' + implodedPath
            if (method == 'POST') or (method == 'PUT') or (method == 'DELETE'):
                auth += "\n"  # eslint-disable-line quotes
                if query:
                    body = self.json(query)
                    auth += 'requestBody=' + body + '&'
                auth += 'signTimestamp=' + timestamp
            else:
                sortedQuery = self.extend({'signTimestamp': timestamp}, query)
                sortedQuery = self.keysort(sortedQuery)
                auth += "\n" + self.urlencode(sortedQuery)  # eslint-disable-line quotes
                if query:
                    url += '?' + self.urlencode(query)
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
            headers = {
                'Content-Type': 'application/json',
                'key': self.apiKey,
                'signTimestamp': timestamp,
                'signature': signature,
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None
        #
        #     {
        #         "code" : 21709,
        #         "message" : "Low available balance"
        #     }
        #
        responseCode = self.safe_string(response, 'code')
        if (responseCode is not None) and (responseCode != '200'):
            codeInner = response['code']
            message = self.safe_string(response, 'message')
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], codeInner, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None
