Files
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

7870 lines
351 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.gate import ImplicitAPI
import asyncio
import hashlib
from ccxt.base.types import Any, Balances, BorrowInterest, Bool, Currencies, Currency, DepositAddress, FundingHistory, Greeks, Int, LedgerEntry, Leverage, Leverages, LeverageTier, LeverageTiers, MarginModification, Market, Num, Option, OptionChain, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, Transaction, MarketInterface, 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 AccountNotEnabled
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 OrderImmediatelyFillable
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class gate(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(gate, self).describe(), {
'id': 'gate',
'name': 'Gate',
'countries': ['KR'],
'rateLimit': 50, # 200 requests per 10 second or 50ms
'version': 'v4',
'certified': True,
'pro': True,
'urls': {
'logo': 'https://github.com/user-attachments/assets/64f988c5-07b6-4652-b5c1-679a6bf67c85',
'doc': 'https://www.gate.com/docs/developers/apiv4/en/',
'www': 'https://gate.com',
'api': {
'public': {
'wallet': 'https://api.gateio.ws/api/v4',
'futures': 'https://api.gateio.ws/api/v4',
'margin': 'https://api.gateio.ws/api/v4',
'delivery': 'https://api.gateio.ws/api/v4',
'spot': 'https://api.gateio.ws/api/v4',
'options': 'https://api.gateio.ws/api/v4',
'sub_accounts': 'https://api.gateio.ws/api/v4',
'earn': 'https://api.gateio.ws/api/v4',
},
'private': {
'withdrawals': 'https://api.gateio.ws/api/v4',
'wallet': 'https://api.gateio.ws/api/v4',
'futures': 'https://api.gateio.ws/api/v4',
'margin': 'https://api.gateio.ws/api/v4',
'delivery': 'https://api.gateio.ws/api/v4',
'spot': 'https://api.gateio.ws/api/v4',
'options': 'https://api.gateio.ws/api/v4',
'subAccounts': 'https://api.gateio.ws/api/v4',
'unified': 'https://api.gateio.ws/api/v4',
'rebate': 'https://api.gateio.ws/api/v4',
'earn': 'https://api.gateio.ws/api/v4',
'account': 'https://api.gateio.ws/api/v4',
'loan': 'https://api.gateio.ws/api/v4',
},
},
'test': {
'public': {
'futures': 'https://api-testnet.gateapi.io/api/v4',
'delivery': 'https://api-testnet.gateapi.io/api/v4',
'options': 'https://api-testnet.gateapi.io/api/v4',
'spot': 'https://api-testnet.gateapi.io/api/v4',
'wallet': 'https://api-testnet.gateapi.io/api/v4',
'margin': 'https://api-testnet.gateapi.io/api/v4',
'sub_accounts': 'https://api-testnet.gateapi.io/api/v4',
'account': 'https://api-testnet.gateapi.io/api/v4',
},
'private': {
'futures': 'https://api-testnet.gateapi.io/api/v4',
'delivery': 'https://api-testnet.gateapi.io/api/v4',
'options': 'https://api-testnet.gateapi.io/api/v4',
'spot': 'https://api-testnet.gateapi.io/api/v4',
'wallet': 'https://api-testnet.gateapi.io/api/v4',
'margin': 'https://api-testnet.gateapi.io/api/v4',
'sub_accounts': 'https://api-testnet.gateapi.io/api/v4',
'account': 'https://api-testnet.gateapi.io/api/v4',
},
},
'referral': {
'url': 'https://www.gate.com/share/CCXTGATE',
'discount': 0.2,
},
},
'has': {
'CORS': None,
'spot': True,
'margin': True,
'swap': True,
'future': True,
'option': True,
'addMargin': True,
'borrowCrossMargin': True,
'borrowIsolatedMargin': True,
'cancelAllOrders': True,
'cancelOrder': True,
'cancelOrders': True,
'cancelOrdersForSymbols': True,
'createMarketBuyOrderWithCost': True,
'createMarketOrder': True,
'createMarketOrderWithCost': False,
'createMarketSellOrderWithCost': False,
'createOrder': True,
'createOrders': True,
'createPostOnlyOrder': True,
'createReduceOnlyOrder': True,
'createStopLimitOrder': True,
'createStopLossOrder': True,
'createStopMarketOrder': False,
'createStopOrder': True,
'createTakeProfitOrder': True,
'createTriggerOrder': True,
'editOrder': True,
'fetchBalance': True,
'fetchBorrowInterest': True,
'fetchBorrowRateHistories': False,
'fetchBorrowRateHistory': False,
'fetchClosedOrders': True,
'fetchCrossBorrowRate': False,
'fetchCrossBorrowRates': False,
'fetchCurrencies': True,
'fetchDepositAddress': True,
'fetchDepositAddresses': False,
'fetchDepositAddressesByNetwork': True,
'fetchDeposits': True,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': True,
'fetchFundingHistory': True,
'fetchFundingRate': True,
'fetchFundingRateHistory': True,
'fetchFundingRates': True,
'fetchGreeks': True,
'fetchIndexOHLCV': True,
'fetchIsolatedBorrowRate': False,
'fetchIsolatedBorrowRates': False,
'fetchLedger': True,
'fetchLeverage': True,
'fetchLeverages': True,
'fetchLeverageTiers': True,
'fetchLiquidations': True,
'fetchMarginAdjustmentHistory': False,
'fetchMarginMode': False,
'fetchMarketLeverageTiers': True,
'fetchMarkets': True,
'fetchMarkOHLCV': True,
'fetchMyLiquidations': True,
'fetchMySettlementHistory': True,
'fetchMyTrades': True,
'fetchNetworkDepositAddress': True,
'fetchOHLCV': True,
'fetchOpenInterest': False,
'fetchOpenInterestHistory': True,
'fetchOpenOrders': True,
'fetchOption': True,
'fetchOptionChain': True,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchPosition': True,
'fetchPositionHistory': 'emulated',
'fetchPositionMode': False,
'fetchPositions': True,
'fetchPositionsHistory': True,
'fetchPremiumIndexOHLCV': False,
'fetchSettlementHistory': True,
'fetchTicker': True,
'fetchTickers': True,
'fetchTime': True,
'fetchTrades': True,
'fetchTradingFee': True,
'fetchTradingFees': True,
'fetchTransactionFees': True,
'fetchUnderlyingAssets': True,
'fetchVolatilityHistory': False,
'fetchWithdrawals': True,
'reduceMargin': True,
'repayCrossMargin': True,
'repayIsolatedMargin': True,
'sandbox': True,
'setLeverage': True,
'setMarginMode': False,
'setPositionMode': True,
'signIn': False,
'transfer': True,
'withdraw': True,
},
'api': {
'public': {
# All public endpoints 200r/10s per endpoint
'wallet': {
'get': {
'currency_chains': 1,
},
},
'unified': {
'get': {
'currencies': 1,
'history_loan_rate': 1,
},
},
'spot': {
'get': {
'currencies': 1,
'currencies/{currency}': 1,
'currency_pairs': 1,
'currency_pairs/{currency_pair}': 1,
'tickers': 1,
'order_book': 1,
'trades': 1,
'candlesticks': 1,
'time': 1,
'insurance_history': 1,
},
},
'margin': {
'get': {
'uni/currency_pairs': 1,
'uni/currency_pairs/{currency_pair}': 1,
'loan_margin_tiers': 1,
'currency_pairs': 1, # deprecated
'currency_pairs/{currency_pair}': 1, # deprecated
'funding_book': 1, # deprecated
'cross/currencies': 1, # deprecated
'cross/currencies/{currency}': 1, # deprecated
},
},
'flash_swap': {
'get': {
'currency_pairs': 1,
'currencies': 1, # deprecated
},
},
'futures': {
'get': {
'{settle}/contracts': 1,
'{settle}/contracts/{contract}': 1,
'{settle}/order_book': 1,
'{settle}/trades': 1,
'{settle}/candlesticks': 1,
'{settle}/premium_index': 1,
'{settle}/tickers': 1,
'{settle}/funding_rate': 1,
'{settle}/insurance': 1,
'{settle}/contract_stats': 1,
'{settle}/index_constituents/{index}': 1,
'{settle}/liq_orders': 1,
'{settle}/risk_limit_tiers': 1,
},
},
'delivery': {
'get': {
'{settle}/contracts': 1,
'{settle}/contracts/{contract}': 1,
'{settle}/order_book': 1,
'{settle}/trades': 1,
'{settle}/candlesticks': 1,
'{settle}/tickers': 1,
'{settle}/insurance': 1,
'{settle}/risk_limit_tiers': 1,
},
},
'options': {
'get': {
'underlyings': 1,
'expirations': 1,
'contracts': 1,
'contracts/{contract}': 1,
'settlements': 1,
'settlements/{contract}': 1,
'order_book': 1,
'tickers': 1,
'underlying/tickers/{underlying}': 1,
'candlesticks': 1,
'underlying/candlesticks': 1,
'trades': 1,
},
},
'earn': {
'get': {
'uni/currencies': 1,
'uni/currencies/{currency}': 1,
'dual/investment_plan': 1,
'structured/products': 1,
},
},
'loan': {
'get': {
'collateral/currencies': 1,
'multi_collateral/currencies': 1,
'multi_collateral/ltv': 1,
'multi_collateral/fixed_rate': 1,
'multi_collateral/current_rate': 1,
},
},
},
'private': {
# private endpoints default is 150r/10s per endpoint
'withdrawals': {
'post': {
'withdrawals': 20, # 1r/s cost = 20 / 1 = 20
'push': 1,
},
'delete': {
'withdrawals/{withdrawal_id}': 1,
},
},
'wallet': {
'get': {
'deposit_address': 1,
'withdrawals': 1,
'deposits': 1,
'sub_account_transfers': 1,
'order_status': 1,
'withdraw_status': 1,
'sub_account_balances': 2.5,
'sub_account_margin_balances': 2.5,
'sub_account_futures_balances': 2.5,
'sub_account_cross_margin_balances': 2.5,
'saved_address': 1,
'fee': 1,
'total_balance': 2.5,
'small_balance': 1,
'small_balance_history': 1,
'push': 1,
},
'post': {
'transfers': 2.5, # 8r/s cost = 20 / 8 = 2.5
'sub_account_transfers': 2.5,
'sub_account_to_sub_account': 2.5,
'small_balance': 1,
},
},
'subAccounts': {
'get': {
'sub_accounts': 2.5,
'sub_accounts/{user_id}': 2.5,
'sub_accounts/{user_id}/keys': 2.5,
'sub_accounts/{user_id}/keys/{key}': 2.5,
},
'post': {
'sub_accounts': 2.5,
'sub_accounts/{user_id}/keys': 2.5,
'sub_accounts/{user_id}/lock': 2.5,
'sub_accounts/{user_id}/unlock': 2.5,
},
'put': {
'sub_accounts/{user_id}/keys/{key}': 2.5,
},
'delete': {
'sub_accounts/{user_id}/keys/{key}': 2.5,
},
},
'unified': {
'get': {
'accounts': 20 / 15,
'borrowable': 20 / 15,
'transferable': 20 / 15,
'transferables': 20 / 15,
'batch_borrowable': 20 / 15,
'loans': 20 / 15,
'loan_records': 20 / 15,
'interest_records': 20 / 15,
'risk_units': 20 / 15,
'unified_mode': 20 / 15,
'estimate_rate': 20 / 15,
'currency_discount_tiers': 20 / 15,
'loan_margin_tiers': 20 / 15,
'leverage/user_currency_config': 20 / 15,
'leverage/user_currency_setting': 20 / 15,
'account_mode': 20 / 15, # deprecated
},
'post': {
'loans': 200 / 15, # 15r/10s cost = 20 / 1.5 = 13.33
'portfolio_calculator': 20 / 15,
'leverage/user_currency_setting': 20 / 15,
'collateral_currencies': 20 / 15,
'account_mode': 20 / 15, # deprecated
},
'put': {
'unified_mode': 20 / 15,
},
},
'spot': {
# default is 200r/10s
'get': {
'fee': 1,
'batch_fee': 1,
'accounts': 1,
'account_book': 1,
'open_orders': 1,
'orders': 1,
'orders/{order_id}': 1,
'my_trades': 1,
'price_orders': 1,
'price_orders/{order_id}': 1,
},
'post': {
'batch_orders': 0.4,
'cross_liquidate_orders': 1,
'orders': 0.4,
'cancel_batch_orders': 20 / 75,
'countdown_cancel_all': 20 / 75,
'amend_batch_orders': 0.4,
'price_orders': 0.4,
},
'delete': {
'orders': 20 / 75,
'orders/{order_id}': 20 / 75,
'price_orders': 20 / 75,
'price_orders/{order_id}': 20 / 75,
},
'patch': {
'orders/{order_id}': 0.4,
},
},
'margin': {
'get': {
'accounts': 20 / 15,
'account_book': 20 / 15,
'funding_accounts': 20 / 15,
'auto_repay': 20 / 15,
'transferable': 20 / 15,
'uni/estimate_rate': 20 / 15,
'uni/loans': 20 / 15,
'uni/loan_records': 20 / 15,
'uni/interest_records': 20 / 15,
'uni/borrowable': 20 / 15,
'user/loan_margin_tiers': 20 / 15,
'user/account': 20 / 15,
'loans': 20 / 15, # deprecated
'loans/{loan_id}': 20 / 15, # deprecated
'loans/{loan_id}/repayment': 20 / 15, # deprecated
'loan_records': 20 / 15, # deprecated
'loan_records/{loan_record_id}': 20 / 15, # deprecated
'borrowable': 20 / 15, # deprecated
'cross/accounts': 20 / 15, # deprecated
'cross/account_book': 20 / 15, # deprecated
'cross/loans': 20 / 15, # deprecated
'cross/loans/{loan_id}': 20 / 15, # deprecated
'cross/repayments': 20 / 15, # deprecated
'cross/interest_records': 20 / 15, # deprecated
'cross/transferable': 20 / 15, # deprecated
'cross/estimate_rate': 20 / 15, # deprecated
'cross/borrowable': 20 / 15, # deprecated
},
'post': {
'auto_repay': 20 / 15,
'uni/loans': 20 / 15,
'leverage/user_market_setting': 20 / 15,
'loans': 20 / 15, # deprecated
'merged_loans': 20 / 15, # deprecated
'loans/{loan_id}/repayment': 20 / 15, # deprecated
'cross/loans': 20 / 15, # deprecated
'cross/repayments': 20 / 15, # deprecated
},
'patch': {
'loans/{loan_id}': 20 / 15, # deprecated
'loan_records/{loan_record_id}': 20 / 15, # deprecated
},
'delete': {
'loans/{loan_id}': 20 / 15, # deprecated
},
},
'flash_swap': {
'get': {
'orders': 1,
'orders/{order_id}': 1,
},
'post': {
'orders': 1,
'orders/preview': 1,
},
},
'futures': {
'get': {
'{settle}/accounts': 1,
'{settle}/account_book': 1,
'{settle}/positions': 1,
'{settle}/positions/{contract}': 1,
'{settle}/dual_comp/positions/{contract}': 1,
'{settle}/orders': 1,
'{settle}/orders_timerange': 1,
'{settle}/orders/{order_id}': 1,
'{settle}/my_trades': 1,
'{settle}/my_trades_timerange': 1,
'{settle}/position_close': 1,
'{settle}/liquidates': 1,
'{settle}/auto_deleverages': 1,
'{settle}/fee': 1,
'{settle}/risk_limit_table': 1,
'{settle}/price_orders': 1,
'{settle}/price_orders/{order_id}': 1,
},
'post': {
'{settle}/positions/{contract}/margin': 1,
'{settle}/positions/{contract}/leverage': 1,
'{settle}/positions/{contract}/risk_limit': 1,
'{settle}/positions/cross_mode': 1,
'{settle}/dual_comp/positions/cross_mode': 1,
'{settle}/dual_mode': 1,
'{settle}/dual_comp/positions/{contract}/margin': 1,
'{settle}/dual_comp/positions/{contract}/leverage': 1,
'{settle}/dual_comp/positions/{contract}/risk_limit': 1,
'{settle}/orders': 0.4,
'{settle}/batch_orders': 0.4,
'{settle}/countdown_cancel_all': 0.4,
'{settle}/batch_cancel_orders': 0.4,
'{settle}/batch_amend_orders': 0.4,
'{settle}/bbo_orders': 0.4,
'{settle}/price_orders': 0.4,
},
'put': {
'{settle}/orders/{order_id}': 1,
},
'delete': {
'{settle}/orders': 20 / 75,
'{settle}/orders/{order_id}': 20 / 75,
'{settle}/price_orders': 20 / 75,
'{settle}/price_orders/{order_id}': 20 / 75,
},
},
'delivery': {
'get': {
'{settle}/accounts': 20 / 15,
'{settle}/account_book': 20 / 15,
'{settle}/positions': 20 / 15,
'{settle}/positions/{contract}': 20 / 15,
'{settle}/orders': 20 / 15,
'{settle}/orders/{order_id}': 20 / 15,
'{settle}/my_trades': 20 / 15,
'{settle}/position_close': 20 / 15,
'{settle}/liquidates': 20 / 15,
'{settle}/settlements': 20 / 15,
'{settle}/price_orders': 20 / 15,
'{settle}/price_orders/{order_id}': 20 / 15,
},
'post': {
'{settle}/positions/{contract}/margin': 20 / 15,
'{settle}/positions/{contract}/leverage': 20 / 15,
'{settle}/positions/{contract}/risk_limit': 20 / 15,
'{settle}/orders': 20 / 15,
'{settle}/price_orders': 20 / 15,
},
'delete': {
'{settle}/orders': 20 / 15,
'{settle}/orders/{order_id}': 20 / 15,
'{settle}/price_orders': 20 / 15,
'{settle}/price_orders/{order_id}': 20 / 15,
},
},
'options': {
'get': {
'my_settlements': 20 / 15,
'accounts': 20 / 15,
'account_book': 20 / 15,
'positions': 20 / 15,
'positions/{contract}': 20 / 15,
'position_close': 20 / 15,
'orders': 20 / 15,
'orders/{order_id}': 20 / 15,
'my_trades': 20 / 15,
'mmp': 20 / 15,
},
'post': {
'orders': 20 / 15,
'countdown_cancel_all': 20 / 15,
'mmp': 20 / 15,
'mmp/reset': 20 / 15,
},
'delete': {
'orders': 20 / 15,
'orders/{order_id}': 20 / 15,
},
},
'earn': {
'get': {
'uni/lends': 20 / 15,
'uni/lend_records': 20 / 15,
'uni/interests/{currency}': 20 / 15,
'uni/interest_records': 20 / 15,
'uni/interest_status/{currency}': 20 / 15,
'uni/chart': 20 / 15,
'uni/rate': 20 / 15,
'staking/eth2/rate_records': 20 / 15,
'dual/orders': 20 / 15,
'structured/orders': 20 / 15,
'staking/coins': 20 / 15,
'staking/order_list': 20 / 15,
'staking/award_list': 20 / 15,
'staking/assets': 20 / 15,
'uni/currencies': 20 / 15, # deprecated
'uni/currencies/{currency}': 20 / 15, # deprecated
},
'post': {
'uni/lends': 20 / 15,
'staking/eth2/swap': 20 / 15,
'dual/orders': 20 / 15,
'structured/orders': 20 / 15,
'staking/swap': 20 / 15,
},
'put': {
'uni/interest_reinvest': 20 / 15, # deprecated
},
'patch': {
'uni/lends': 20 / 15,
},
},
'loan': {
'get': {
'collateral/orders': 20 / 15,
'collateral/orders/{order_id}': 20 / 15,
'collateral/repay_records': 20 / 15,
'collateral/collaterals': 20 / 15,
'collateral/total_amount': 20 / 15,
'collateral/ltv': 20 / 15,
'multi_collateral/orders': 20 / 15,
'multi_collateral/orders/{order_id}': 20 / 15,
'multi_collateral/repay': 20 / 15,
'multi_collateral/mortgage': 20 / 15,
'multi_collateral/currency_quota': 20 / 15,
'collateral/currencies': 20 / 15, # deprecated
'multi_collateral/currencies': 20 / 15, # deprecated
'multi_collateral/ltv': 20 / 15, # deprecated
'multi_collateral/fixed_rate': 20 / 15, # deprecated
'multi_collateral/current_rate': 20 / 15, # deprecated
},
'post': {
'collateral/orders': 20 / 15,
'collateral/repay': 20 / 15,
'collateral/collaterals': 20 / 15,
'multi_collateral/orders': 20 / 15,
'multi_collateral/repay': 20 / 15,
'multi_collateral/mortgage': 20 / 15,
},
},
'account': {
'get': {
'detail': 20 / 15,
'main_keys': 20 / 15,
'rate_limit': 20 / 15,
'stp_groups': 20 / 15,
'stp_groups/{stp_id}/users': 20 / 15,
'stp_groups/debit_fee': 20 / 15,
'debit_fee': 20 / 15,
},
'post': {
'stp_groups': 20 / 15,
'stp_groups/{stp_id}/users': 20 / 15,
'debit_fee': 20 / 15,
},
'delete': {
'stp_groups/{stp_id}/users': 20 / 15,
},
},
'rebate': {
'get': {
'agency/transaction_history': 20 / 15,
'agency/commission_history': 20 / 15,
'partner/transaction_history': 20 / 15,
'partner/commission_history': 20 / 15,
'partner/sub_list': 20 / 15,
'broker/commission_history': 20 / 15,
'broker/transaction_history': 20 / 15,
'user/info': 20 / 15,
'user/sub_relation': 20 / 15,
},
},
},
},
'timeframes': {
'10s': '10s',
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'8h': '8h',
'1d': '1d',
'7d': '7d',
'1w': '7d',
},
# copied from gatev2
'commonCurrencies': {
'ORT': 'XREATORS',
'ASS': 'ASSF',
'88MPH': 'MPH',
'AXIS': 'AXISDEFI',
'BIFI': 'BITCOINFILE',
'BOX': 'DEFIBOX',
'BYN': 'BEYONDFI',
'EGG': 'GOOSEFINANCE',
'GTC': 'GAMECOM', # conflict with Gitcoin and Gastrocoin
'GTC_HT': 'GAMECOM_HT',
'GTC_BSC': 'GAMECOM_BSC',
'HIT': 'HITCHAIN',
'MM': 'MILLION', # conflict with MilliMeter
'MPH': 'MORPHER', # conflict with 88MPH
'POINT': 'GATEPOINT',
'RAI': 'RAIREFLEXINDEX', # conflict with RAI Finance
'RED': 'RedLang',
'SBTC': 'SUPERBITCOIN',
'TNC': 'TRINITYNETWORKCREDIT',
'VAI': 'VAIOT',
'TRAC': 'TRACO', # conflict with OriginTrail(TRAC)
},
'requiredCredentials': {
'apiKey': True,
'secret': True,
},
'headers': {
'X-Gate-Channel-Id': 'ccxt',
},
'options': {
'timeDifference': 0, # the difference between system clock and exchange clock
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
'sandboxMode': False,
'unifiedAccount': None,
'createOrder': {
'expiration': 86400, # for conditional orders
},
'createMarketBuyOrderRequiresPrice': True,
'networks': {
'BTC': 'BTC',
'BRC20': 'BTCBRC', # for eg: ORDI, RATS, ...
'ETH': 'ETH',
'ERC20': 'ETH',
'TRX': 'TRX',
'TRC20': 'TRX',
'HECO': 'HT',
'HRC20': 'HT',
'BSC': 'BSC',
'BEP20': 'BSC',
'SOL': 'SOL',
'MATIC': 'MATIC',
'OPTIMISM': 'OPETH',
'ADA': 'ADA', # CARDANO
'AVAXC': 'AVAX_C',
'NEAR': 'NEAR',
'ARBONE': 'ARBEVM',
'BASE': 'BASEEVM',
'SUI': 'SUI',
'CRONOS': 'CRO',
'CRO': 'CRO',
'APT': 'APT',
'SCROLL': 'SCROLLETH',
'TAIKO': 'TAIKOETH',
'HYPE': 'HYPE',
'ALGO': 'ALGO',
# KAVA: ['KAVA', 'KAVAEVM']
# SEI: ['SEI', 'SEIEVM']
'LINEA': 'LINEAETH',
'BLAST': 'BLASTETH',
'XLM': 'XLM',
'RSK': 'RBTC',
'TON': 'TON',
'MNT': 'MNT',
# 'RUNE': 'BTCRUNES', probably, cant verify atm
'CELO': 'CELO',
'HBAR': 'HBAR',
# 'FTM': SONIC REBRAND, todo
'ZKSERA': 'ZKSERA',
'KLAY': 'KLAY',
'EOS': 'EOS',
'ACA': 'ACA',
# TLOS: ['TLOS', 'TLOSEVM']
# ASTR: ['ASTR', 'ASTREVM']
# CFX: ['CFX', 'CFXEVM']
'XTZ': 'XTZ',
'EGLD': 'EGLD',
'GLMR': 'GLMR',
'AURORA': 'AURORAEVM',
# others
'KON': 'KONET',
'GATECHAIN': 'GTEVM',
'KUSAMA': 'KSMSM',
'OKC': 'OKT',
'POLKADOT': 'DOTSM', # todo: DOT for main DOT
'LUNA': 'LUNC',
},
'networksById': {
'OPETH': 'OP',
'ETH': 'ERC20', # for GOlang
'ERC20': 'ERC20',
'TRX': 'TRC20',
'TRC20': 'TRC20',
'HT': 'HRC20',
'HECO': 'HRC20',
'BSC': 'BEP20',
'BEP20': 'BEP20',
'POLYGON': 'MATIC',
'POL': 'MATIC',
},
'timeInForce': {
'GTC': 'gtc',
'IOC': 'ioc',
'PO': 'poc',
'POC': 'poc',
'FOK': 'fok',
},
'accountsByType': {
'funding': 'spot',
'spot': 'spot',
'margin': 'margin',
'cross_margin': 'cross_margin',
'cross': 'cross_margin',
'isolated': 'margin',
'swap': 'futures',
'future': 'delivery',
'futures': 'futures',
'delivery': 'delivery',
'option': 'options',
'options': 'options',
},
'fetchMarkets': {
'types': ['spot', 'swap', 'future', 'option'],
},
'swap': {
'fetchMarkets': {
'settlementCurrencies': ['usdt', 'btc'],
},
},
'future': {
'fetchMarkets': {
'settlementCurrencies': ['usdt'],
},
},
},
'features': {
'default': {
'sandbox': True,
'createOrder': {
'marginMode': True,
'triggerPrice': True,
'triggerDirection': True, # todo: implementation edit needed
'triggerPriceType': None,
'stopLossPrice': True,
'takeProfitPrice': True,
'attachedStopLossTakeProfit': None,
'timeInForce': {
'IOC': True,
'FOK': True,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'iceberg': True, # todo implement
'selfTradePrevention': True, # todo implement
'leverage': False,
'marketBuyByCost': True,
'marketBuyRequiresPrice': True,
},
'createOrders': {
'max': 40, # NOTE! max 10 per symbol
},
'fetchMyTrades': {
'marginMode': True,
'limit': 1000,
'daysBack': None,
'untilDays': 30,
'symbolRequired': False,
},
'fetchOrder': {
'marginMode': False,
'trigger': True,
'trailing': False,
'symbolRequired': True,
},
'fetchOpenOrders': {
'marginMode': True,
'trigger': True,
'trailing': False,
'limit': 100,
'symbolRequired': False,
},
'fetchOrders': None,
'fetchClosedOrders': {
'marginMode': True,
'trigger': True,
'trailing': False,
'limit': 100,
'untilDays': 30,
'daysBack': None,
'daysBackCanceled': None,
'symbolRequired': False,
},
'fetchOHLCV': {
'limit': 1000,
},
},
'spot': {
'extends': 'default',
},
'forDerivatives': {
'extends': 'spot',
'createOrder': {
'marginMode': False,
'triggerPriceType': {
'last': True,
'mark': True,
'index': True,
},
},
'createOrders': {
'max': 10,
},
'fetchMyTrades': {
'marginMode': False,
'untilDays': None,
},
'fetchOpenOrders': {
'marginMode': False,
},
'fetchClosedOrders': {
'marginMode': False,
'untilDays': None,
'limit': 1000,
},
'fetchOHLCV': {
'limit': 1999,
},
},
'swap': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': {
'extends': 'forDerivatives',
},
},
'future': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': {
'extends': 'forDerivatives',
},
},
},
'precisionMode': TICK_SIZE,
'fees': {
'trading': {
'tierBased': True,
'feeSide': 'get',
'percentage': True,
'maker': self.parse_number('0.002'),
'taker': self.parse_number('0.002'),
'tiers': {
# volume is in BTC
'maker': [
[self.parse_number('0'), self.parse_number('0.002')],
[self.parse_number('1.5'), self.parse_number('0.00185')],
[self.parse_number('3'), self.parse_number('0.00175')],
[self.parse_number('6'), self.parse_number('0.00165')],
[self.parse_number('12.5'), self.parse_number('0.00155')],
[self.parse_number('25'), self.parse_number('0.00145')],
[self.parse_number('75'), self.parse_number('0.00135')],
[self.parse_number('200'), self.parse_number('0.00125')],
[self.parse_number('500'), self.parse_number('0.00115')],
[self.parse_number('1250'), self.parse_number('0.00105')],
[self.parse_number('2500'), self.parse_number('0.00095')],
[self.parse_number('3000'), self.parse_number('0.00085')],
[self.parse_number('6000'), self.parse_number('0.00075')],
[self.parse_number('11000'), self.parse_number('0.00065')],
[self.parse_number('20000'), self.parse_number('0.00055')],
[self.parse_number('40000'), self.parse_number('0.00055')],
[self.parse_number('75000'), self.parse_number('0.00055')],
],
'taker': [
[self.parse_number('0'), self.parse_number('0.002')],
[self.parse_number('1.5'), self.parse_number('0.00195')],
[self.parse_number('3'), self.parse_number('0.00185')],
[self.parse_number('6'), self.parse_number('0.00175')],
[self.parse_number('12.5'), self.parse_number('0.00165')],
[self.parse_number('25'), self.parse_number('0.00155')],
[self.parse_number('75'), self.parse_number('0.00145')],
[self.parse_number('200'), self.parse_number('0.00135')],
[self.parse_number('500'), self.parse_number('0.00125')],
[self.parse_number('1250'), self.parse_number('0.00115')],
[self.parse_number('2500'), self.parse_number('0.00105')],
[self.parse_number('3000'), self.parse_number('0.00095')],
[self.parse_number('6000'), self.parse_number('0.00085')],
[self.parse_number('11000'), self.parse_number('0.00075')],
[self.parse_number('20000'), self.parse_number('0.00065')],
[self.parse_number('40000'), self.parse_number('0.00065')],
[self.parse_number('75000'), self.parse_number('0.00065')],
],
},
},
'swap': {
'tierBased': True,
'feeSide': 'base',
'percentage': True,
'maker': self.parse_number('0.0'),
'taker': self.parse_number('0.0005'),
'tiers': {
'maker': [
[self.parse_number('0'), self.parse_number('0.0000')],
[self.parse_number('1.5'), self.parse_number('-0.00005')],
[self.parse_number('3'), self.parse_number('-0.00005')],
[self.parse_number('6'), self.parse_number('-0.00005')],
[self.parse_number('12.5'), self.parse_number('-0.00005')],
[self.parse_number('25'), self.parse_number('-0.00005')],
[self.parse_number('75'), self.parse_number('-0.00005')],
[self.parse_number('200'), self.parse_number('-0.00005')],
[self.parse_number('500'), self.parse_number('-0.00005')],
[self.parse_number('1250'), self.parse_number('-0.00005')],
[self.parse_number('2500'), self.parse_number('-0.00005')],
[self.parse_number('3000'), self.parse_number('-0.00008')],
[self.parse_number('6000'), self.parse_number('-0.01000')],
[self.parse_number('11000'), self.parse_number('-0.01002')],
[self.parse_number('20000'), self.parse_number('-0.01005')],
[self.parse_number('40000'), self.parse_number('-0.02000')],
[self.parse_number('75000'), self.parse_number('-0.02005')],
],
'taker': [
[self.parse_number('0'), self.parse_number('0.00050')],
[self.parse_number('1.5'), self.parse_number('0.00048')],
[self.parse_number('3'), self.parse_number('0.00046')],
[self.parse_number('6'), self.parse_number('0.00044')],
[self.parse_number('12.5'), self.parse_number('0.00042')],
[self.parse_number('25'), self.parse_number('0.00040')],
[self.parse_number('75'), self.parse_number('0.00038')],
[self.parse_number('200'), self.parse_number('0.00036')],
[self.parse_number('500'), self.parse_number('0.00034')],
[self.parse_number('1250'), self.parse_number('0.00032')],
[self.parse_number('2500'), self.parse_number('0.00030')],
[self.parse_number('3000'), self.parse_number('0.00030')],
[self.parse_number('6000'), self.parse_number('0.00030')],
[self.parse_number('11000'), self.parse_number('0.00030')],
[self.parse_number('20000'), self.parse_number('0.00030')],
[self.parse_number('40000'), self.parse_number('0.00030')],
[self.parse_number('75000'), self.parse_number('0.00030')],
],
},
},
},
# https://www.gate.com/docs/developers/apiv4/en/#label-list
'exceptions': {
'exact': {
'INVALID_PARAM_VALUE': BadRequest,
'INVALID_PROTOCOL': BadRequest,
'INVALID_ARGUMENT': BadRequest,
'INVALID_REQUEST_BODY': BadRequest,
'MISSING_REQUIRED_PARAM': ArgumentsRequired,
'BAD_REQUEST': BadRequest,
'INVALID_CONTENT_TYPE': BadRequest,
'NOT_ACCEPTABLE': BadRequest,
'METHOD_NOT_ALLOWED': BadRequest,
'NOT_FOUND': ExchangeError,
'AUTHENTICATION_FAILED': AuthenticationError,
'INVALID_CREDENTIALS': AuthenticationError,
'INVALID_KEY': AuthenticationError,
'IP_FORBIDDEN': AuthenticationError,
'READ_ONLY': PermissionDenied,
'INVALID_SIGNATURE': AuthenticationError,
'MISSING_REQUIRED_HEADER': AuthenticationError,
'REQUEST_EXPIRED': AuthenticationError,
'ACCOUNT_LOCKED': AccountSuspended,
'FORBIDDEN': PermissionDenied,
'SUB_ACCOUNT_NOT_FOUND': ExchangeError,
'SUB_ACCOUNT_LOCKED': AccountSuspended,
'MARGIN_BALANCE_EXCEPTION': ExchangeError,
'MARGIN_TRANSFER_FAILED': ExchangeError,
'TOO_MUCH_FUTURES_AVAILABLE': ExchangeError,
'FUTURES_BALANCE_NOT_ENOUGH': InsufficientFunds,
'ACCOUNT_EXCEPTION': ExchangeError,
'SUB_ACCOUNT_TRANSFER_FAILED': ExchangeError,
'ADDRESS_NOT_USED': ExchangeError,
'TOO_FAST': RateLimitExceeded,
'WITHDRAWAL_OVER_LIMIT': ExchangeError,
'API_WITHDRAW_DISABLED': ExchangeNotAvailable,
'INVALID_WITHDRAW_ID': ExchangeError,
'INVALID_WITHDRAW_CANCEL_STATUS': ExchangeError,
'INVALID_PRECISION': InvalidOrder,
'INVALID_CURRENCY': BadSymbol,
'INVALID_CURRENCY_PAIR': BadSymbol,
'POC_FILL_IMMEDIATELY': OrderImmediatelyFillable, # {"label":"POC_FILL_IMMEDIATELY","message":"Order would match and take immediately so its cancelled"}
'ORDER_NOT_FOUND': OrderNotFound,
'CLIENT_ID_NOT_FOUND': OrderNotFound,
'ORDER_CLOSED': InvalidOrder,
'ORDER_CANCELLED': InvalidOrder,
'QUANTITY_NOT_ENOUGH': InvalidOrder,
'BALANCE_NOT_ENOUGH': InsufficientFunds,
'MARGIN_NOT_SUPPORTED': InvalidOrder,
'MARGIN_BALANCE_NOT_ENOUGH': InsufficientFunds,
'AMOUNT_TOO_LITTLE': InvalidOrder,
'AMOUNT_TOO_MUCH': InvalidOrder,
'REPEATED_CREATION': InvalidOrder,
'LOAN_NOT_FOUND': OrderNotFound,
'LOAN_RECORD_NOT_FOUND': OrderNotFound,
'NO_MATCHED_LOAN': ExchangeError,
'NOT_MERGEABLE': ExchangeError,
'NO_CHANGE': ExchangeError,
'REPAY_TOO_MUCH': ExchangeError,
'TOO_MANY_CURRENCY_PAIRS': InvalidOrder,
'TOO_MANY_ORDERS': InvalidOrder,
'TOO_MANY_REQUESTS': RateLimitExceeded,
'MIXED_ACCOUNT_TYPE': InvalidOrder,
'AUTO_BORROW_TOO_MUCH': ExchangeError,
'TRADE_RESTRICTED': InsufficientFunds,
'USER_NOT_FOUND': AccountNotEnabled,
'CONTRACT_NO_COUNTER': ExchangeError,
'CONTRACT_NOT_FOUND': BadSymbol,
'RISK_LIMIT_EXCEEDED': ExchangeError,
'INSUFFICIENT_AVAILABLE': InsufficientFunds,
'LIQUIDATE_IMMEDIATELY': InvalidOrder,
'LEVERAGE_TOO_HIGH': InvalidOrder,
'LEVERAGE_TOO_LOW': InvalidOrder,
'ORDER_NOT_OWNED': ExchangeError,
'ORDER_FINISHED': ExchangeError,
'POSITION_CROSS_MARGIN': ExchangeError,
'POSITION_IN_LIQUIDATION': ExchangeError,
'POSITION_IN_CLOSE': ExchangeError,
'POSITION_EMPTY': InvalidOrder,
'REMOVE_TOO_MUCH': ExchangeError,
'RISK_LIMIT_NOT_MULTIPLE': ExchangeError,
'RISK_LIMIT_TOO_HIGH': ExchangeError,
'RISK_LIMIT_TOO_lOW': ExchangeError,
'PRICE_TOO_DEVIATED': InvalidOrder,
'SIZE_TOO_LARGE': InvalidOrder,
'SIZE_TOO_SMALL': InvalidOrder,
'PRICE_OVER_LIQUIDATION': InvalidOrder,
'PRICE_OVER_BANKRUPT': InvalidOrder,
'ORDER_POC_IMMEDIATE': OrderImmediatelyFillable, # {"label":"ORDER_POC_IMMEDIATE","detail":"order price 1700 while counter price 1793.55"}
'INCREASE_POSITION': InvalidOrder,
'CONTRACT_IN_DELISTING': ExchangeError,
'INTERNAL': ExchangeNotAvailable,
'SERVER_ERROR': ExchangeNotAvailable,
'TOO_BUSY': ExchangeNotAvailable,
'CROSS_ACCOUNT_NOT_FOUND': ExchangeError,
'RISK_LIMIT_TOO_LOW': BadRequest, # {"label":"RISK_LIMIT_TOO_LOW","detail":"limit 1000000"}
'AUTO_TRIGGER_PRICE_LESS_LAST': InvalidOrder, # {"label":"AUTO_TRIGGER_PRICE_LESS_LAST","message":"invalid argument: Trigger.Price must < last_price"}
'AUTO_TRIGGER_PRICE_GREATE_LAST': InvalidOrder, # {"label":"AUTO_TRIGGER_PRICE_GREATE_LAST","message":"invalid argument: Trigger.Price must > last_price"}
'POSITION_HOLDING': BadRequest,
'USER_LOAN_EXCEEDED': BadRequest, # {"label":"USER_LOAN_EXCEEDED","message":"Max loan amount per user would be exceeded"}
},
'broad': {},
},
})
def set_sandbox_mode(self, enable: bool):
super(gate, self).set_sandbox_mode(enable)
self.options['sandboxMode'] = enable
async def load_unified_status(self, params={}):
"""
:param dict [params]: extra parameters specific to the exchange API endpoint
returns unifiedAccount so the user can check if the unified account is enabled
https://www.gate.com/docs/developers/apiv4/#get-account-detail
:returns boolean: True or False if the enabled unified account is enabled or not and sets the unifiedAccount option if it is None
"""
unifiedAccount = self.safe_bool(self.options, 'unifiedAccount')
if unifiedAccount is None:
try:
#
# {
# "user_id": 10406147,
# "ip_whitelist": [],
# "currency_pairs": [],
# "key": {
# "mode": 1
# },
# "tier": 0,
# "tier_expire_time": "0001-01-01T00:00:00Z",
# "copy_trading_role": 0
# }
#
response = await self.privateAccountGetDetail(params)
result = self.safe_dict(response, 'key', {})
self.options['unifiedAccount'] = self.safe_integer(result, 'mode') == 2
except Exception as e:
# if the request fails, the unifiedAccount is disabled
self.options['unifiedAccount'] = False
return self.options['unifiedAccount']
async def upgrade_unified_trade_account(self, params={}):
return await self.privateUnifiedPutUnifiedMode(params)
async def fetch_time(self, params={}) -> Int:
"""
fetches the current integer timestamp in milliseconds from the exchange server
https://www.gate.com/docs/developers/apiv4/en/#get-server-current-time
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int: the current integer timestamp in milliseconds from the exchange server
"""
response = await self.publicSpotGetTime(params)
#
# {
# "server_time": 1731447921098
# }
#
return self.safe_integer(response, 'server_time')
def create_expired_option_market(self, symbol: str):
# support expired option contracts
quote = 'USDT'
settle = quote
optionParts = symbol.split('-')
symbolBase = symbol.split('/')
marketIdBase = symbol.split('_')
base = None
expiry = self.safe_string(optionParts, 1)
if symbol.find('/') > -1:
base = self.safe_string(symbolBase, 0)
else:
base = self.safe_string(marketIdBase, 0)
expiry = expiry[2:8] # convert 20230728 to 230728
strike = self.safe_string(optionParts, 2)
optionType = self.safe_string(optionParts, 3)
datetime = self.convert_expire_date(expiry)
timestamp = self.parse8601(datetime)
return {
'id': base + '_' + quote + '-' + '20' + expiry + '-' + strike + '-' + optionType,
'symbol': base + '/' + quote + ':' + settle + '-' + expiry + '-' + strike + '-' + optionType,
'base': base,
'quote': quote,
'settle': settle,
'baseId': base,
'quoteId': quote,
'settleId': settle,
'active': False,
'type': 'option',
'linear': None,
'inverse': None,
'spot': False,
'swap': False,
'future': False,
'option': True,
'margin': False,
'contract': True,
'contractSize': self.parse_number('1'),
'expiry': timestamp,
'expiryDatetime': datetime,
'optionType': 'call' if (optionType == 'C') else 'put',
'strike': self.parse_number(strike),
'precision': {
'amount': self.parse_number('1'),
'price': None,
},
'limits': {
'amount': {
'min': None,
'max': None,
},
'price': {
'min': None,
'max': None,
},
'cost': {
'min': None,
'max': None,
},
},
'info': None,
}
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
isOption = (marketId is not None) and ((marketId.find('-C') > -1) or (marketId.find('-P') > -1))
if isOption and not (marketId in self.markets_by_id):
# handle expired option contracts
return self.create_expired_option_market(marketId)
return super(gate, self).safe_market(marketId, market, delimiter, marketType)
async def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for gate
https://www.gate.com/docs/developers/apiv4/en/#list-all-currency-pairs-supported # spot
https://www.gate.com/docs/developers/apiv4/en/#list-all-supported-currency-pairs-supported-in-margin-trading # margin
https://www.gate.com/docs/developers/apiv4/en/#list-all-futures-contracts # swap
https://www.gate.com/docs/developers/apiv4/en/#list-all-futures-contracts-2 # future
https://www.gate.com/docs/developers/apiv4/en/#list-all-the-contracts-with-specified-underlying-and-expiration-time # option
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
if self.options['adjustForTimeDifference']:
await self.load_time_difference()
if self.check_required_credentials(False):
await self.load_unified_status()
rawPromises = []
fetchMarketsOptions = self.safe_dict(self.options, 'fetchMarkets')
types = self.safe_list(fetchMarketsOptions, 'types', ['spot', 'swap', 'future', 'option'])
for i in range(0, len(types)):
marketType = types[i]
if marketType == 'spot':
# if not sandboxMode:
# gate doesn't have a sandbox for spot markets
rawPromises.append(self.fetch_spot_markets(params))
# }
elif marketType == 'swap':
rawPromises.append(self.fetch_swap_markets(params))
elif marketType == 'future':
rawPromises.append(self.fetch_future_markets(params))
elif marketType == 'option':
rawPromises.append(self.fetch_option_markets(params))
results = await asyncio.gather(*rawPromises)
return self.arrays_concat(results)
async def fetch_spot_markets(self, params={}):
marginPromise = self.publicMarginGetCurrencyPairs(params)
spotMarketsPromise = self.publicSpotGetCurrencyPairs(params)
marginResponse, spotMarketsResponse = await asyncio.gather(*[marginPromise, spotMarketsPromise])
marginMarkets = self.index_by(marginResponse, 'id')
#
# Spot
#
# [
# {
# "id": "QTUM_ETH",
# "base": "QTUM",
# "base_name": "Quantum",
# "quote": "ETH",
# "quote_name": "Ethereum",
# "fee": "0.2",
# "min_base_amount": "0.01",
# "min_quote_amount": "0.001",
# "max_quote_amount": "50000",
# "amount_precision": 3,
# "precision": 6,
# "trade_status": "tradable",
# "sell_start": 1607313600,
# "buy_start": 1700492400,
# "type": "normal",
# "trade_url": "https://www.gate.com/trade/QTUM_ETH",
# }
#
# Margin
#
# [
# {
# "id": "ETH_USDT",
# "base": "ETH",
# "quote": "USDT",
# "leverage": 3,
# "min_base_amount": "0.01",
# "min_quote_amount": "100",
# "max_quote_amount": "1000000"
# }
# ]
#
result = []
for i in range(0, len(spotMarketsResponse)):
spotMarket = spotMarketsResponse[i]
id = self.safe_string(spotMarket, 'id')
marginMarket = self.safe_value(marginMarkets, id)
market = self.deep_extend(marginMarket, spotMarket)
baseId, quoteId = id.split('_')
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
takerPercent = self.safe_string(market, 'fee')
makerPercent = self.safe_string(market, 'maker_fee_rate', takerPercent)
amountPrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'amount_precision')))
tradeStatus = self.safe_string(market, 'trade_status')
leverage = self.safe_number(market, 'leverage')
margin = leverage is not None
buyStart = self.safe_integer_product(spotMarket, 'buy_start', 1000) # buy_start is the trading start time, while sell_start is offline orders start time
createdTs = buyStart if (buyStart != 0) else None
result.append({
'id': id,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': None,
'baseId': baseId,
'quoteId': quoteId,
'settleId': None,
'type': 'spot',
'spot': True,
'margin': margin,
'swap': False,
'future': False,
'option': False,
'active': (tradeStatus == 'tradable'),
'contract': False,
'linear': None,
'inverse': None,
# Fee is in %, so divide by 100
'taker': self.parse_number(Precise.string_div(takerPercent, '100')),
'maker': self.parse_number(Precise.string_div(makerPercent, '100')),
'contractSize': None,
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': amountPrecision,
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'precision'))),
},
'limits': {
'leverage': {
'min': self.parse_number('1'),
'max': self.safe_number(market, 'leverage', 1),
},
'amount': {
'min': self.safe_number(spotMarket, 'min_base_amount', amountPrecision),
'max': None,
},
'price': {
'min': None,
'max': None,
},
'cost': {
'min': self.safe_number(market, 'min_quote_amount'),
'max': self.safe_number(market, 'max_quote_amount') if margin else None,
},
},
'created': createdTs,
'info': market,
})
return result
async def fetch_swap_markets(self, params={}):
result = []
swapSettlementCurrencies = self.get_settlement_currencies('swap', 'fetchMarkets')
if self.options['sandboxMode']:
swapSettlementCurrencies = ['usdt'] # gate sandbox only has usdt-margined swaps
for c in range(0, len(swapSettlementCurrencies)):
settleId = swapSettlementCurrencies[c]
request: dict = {
'settle': settleId,
}
response = await self.publicFuturesGetSettleContracts(self.extend(request, params))
for i in range(0, len(response)):
parsedMarket = self.parse_contract_market(response[i], settleId)
result.append(parsedMarket)
return result
async def fetch_future_markets(self, params={}):
if self.options['sandboxMode']:
return [] # right now sandbox does not have inverse swaps
result = []
futureSettlementCurrencies = self.get_settlement_currencies('future', 'fetchMarkets')
for c in range(0, len(futureSettlementCurrencies)):
settleId = futureSettlementCurrencies[c]
request: dict = {
'settle': settleId,
}
response = await self.publicDeliveryGetSettleContracts(self.extend(request, params))
for i in range(0, len(response)):
parsedMarket = self.parse_contract_market(response[i], settleId)
result.append(parsedMarket)
return result
def parse_contract_market(self, market, settleId):
#
# Perpetual swap
#
# {
# "name": "BTC_USDT",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.5",
# "maintenance_rate": "0.005",
# "mark_type": "index",
# "last_price": "38026",
# "mark_price": "37985.6",
# "index_price": "37954.92",
# "funding_rate_indicative": "0.000219",
# "mark_price_round": "0.01",
# "funding_offset": 0,
# "in_delisting": False,
# "risk_limit_base": "1000000",
# "interest_rate": "0.0003",
# "order_price_round": "0.1",
# "order_size_min": 1,
# "ref_rebate_rate": "0.2",
# "funding_interval": 28800,
# "risk_limit_step": "1000000",
# "leverage_min": "1",
# "leverage_max": "100",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025", # not actual value for regular users
# "taker_fee_rate": "0.00075", # not actual value for regular users
# "funding_rate": "0.002053",
# "order_size_max": 1000000,
# "funding_next_apply": 1610035200,
# "short_users": 977,
# "config_change_time": 1609899548,
# "create_time": 1609800048,
# "trade_size": 28530850594,
# "position_size": 5223816,
# "long_users": 455,
# "funding_impact_value": "60000",
# "orders_limit": 50,
# "trade_id": 10851092,
# "orderbook_id": 2129638396
# }
#
# Delivery Futures
#
# {
# "name": "BTC_USDT_20200814",
# "underlying": "BTC_USDT",
# "cycle": "WEEKLY",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "mark_type": "index",
# "last_price": "9017",
# "mark_price": "9019",
# "index_price": "9005.3",
# "basis_rate": "0.185095",
# "basis_value": "13.7",
# "basis_impact_value": "100000",
# "settle_price": "0",
# "settle_price_interval": 60,
# "settle_price_duration": 1800,
# "settle_fee_rate": "0.0015",
# "expire_time": 1593763200,
# "order_price_round": "0.1",
# "mark_price_round": "0.1",
# "leverage_min": "1",
# "leverage_max": "100",
# "maintenance_rate": "1000000",
# "risk_limit_base": "140.726652109199",
# "risk_limit_step": "1000000",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025", # not actual value for regular users
# "taker_fee_rate": "0.00075", # not actual value for regular users
# "ref_discount_rate": "0",
# "ref_rebate_rate": "0.2",
# "order_price_deviate": "0.5",
# "order_size_min": 1,
# "order_size_max": 1000000,
# "orders_limit": 50,
# "orderbook_id": 63,
# "trade_id": 26,
# "trade_size": 435,
# "position_size": 130,
# "config_change_time": 1593158867,
# "in_delisting": False
# }
#
id = self.safe_string(market, 'name')
parts = id.split('_')
baseId = self.safe_string(parts, 0)
quoteId = self.safe_string(parts, 1)
date = self.safe_string(parts, 2)
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
settle = self.safe_currency_code(settleId)
expiry = self.safe_timestamp(market, 'expire_time')
symbol = ''
marketType = 'swap'
if date is not None:
symbol = base + '/' + quote + ':' + settle + '-' + self.yymmdd(expiry, '')
marketType = 'future'
else:
symbol = base + '/' + quote + ':' + settle
priceDeviate = self.safe_string(market, 'order_price_deviate')
markPrice = self.safe_string(market, 'mark_price')
minMultiplier = Precise.string_sub('1', priceDeviate)
maxMultiplier = Precise.string_add('1', priceDeviate)
minPrice = Precise.string_mul(minMultiplier, markPrice)
maxPrice = Precise.string_mul(maxMultiplier, markPrice)
isLinear = quote == settle
contractSize = self.safe_string(market, 'quanto_multiplier')
# exception only for one market: https://api.gateio.ws/api/v4/futures/btc/contracts
if contractSize == '0':
contractSize = '1' # 1 USD in WEB: https://i.imgur.com/MBBUI04.png
return {
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': marketType,
'spot': False,
'margin': False,
'swap': marketType == 'swap',
'future': marketType == 'future',
'option': marketType == 'option',
'active': True,
'contract': True,
'linear': isLinear,
'inverse': not isLinear,
'taker': None,
'maker': None,
'contractSize': self.parse_number(contractSize),
'expiry': expiry,
'expiryDatetime': self.iso8601(expiry),
'strike': None,
'optionType': None,
'precision': {
'amount': self.parse_number('1'), # all contracts have self step size
'price': self.safe_number(market, 'order_price_round'),
},
'limits': {
'leverage': {
'min': self.safe_number(market, 'leverage_min'),
'max': self.safe_number(market, 'leverage_max'),
},
'amount': {
'min': self.safe_number(market, 'order_size_min'),
'max': self.safe_number(market, 'order_size_max'),
},
'price': {
'min': self.parse_number(minPrice),
'max': self.parse_number(maxPrice),
},
'cost': {
'min': None,
'max': None,
},
},
'created': self.safe_integer_product(market, 'create_time', 1000),
'info': market,
}
async def fetch_option_markets(self, params={}):
result = []
underlyings = await self.fetch_option_underlyings()
for i in range(0, len(underlyings)):
underlying = underlyings[i]
query = self.extend({}, params)
query['underlying'] = underlying
response = await self.publicOptionsGetContracts(query)
#
# [
# {
# "orders_limit": "50",
# "order_size_max": "100000",
# "mark_price_round": "0.1",
# "order_size_min": "1",
# "position_limit": "1000000",
# "orderbook_id": "575967",
# "order_price_deviate": "0.9",
# "is_call": True, # True means Call False means Put
# "last_price": "93.9",
# "bid1_size": "0",
# "bid1_price": "0",
# "taker_fee_rate": "0.0004",
# "underlying": "BTC_USDT",
# "create_time": "1646381188",
# "price_limit_fee_rate": "0.1",
# "maker_fee_rate": "0.0004",
# "trade_id": "727",
# "order_price_round": "0.1",
# "settle_fee_rate": "0.0001",
# "trade_size": "1982",
# "ref_rebate_rate": "0",
# "name": "BTC_USDT-20220311-44000-C",
# "underlying_price": "39194.26",
# "strike_price": "44000",
# "multiplier": "0.0001",
# "ask1_price": "0",
# "ref_discount_rate": "0",
# "expiration_time": "1646985600",
# "mark_price": "12.15",
# "position_size": "4",
# "ask1_size": "0",
# "tag": "WEEK"
# }
# ]
#
for j in range(0, len(response)):
market = response[j]
id = self.safe_string(market, 'name')
parts = underlying.split('_')
baseId = self.safe_string(parts, 0)
quoteId = self.safe_string(parts, 1)
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
symbol = base + '/' + quote
expiry = self.safe_timestamp(market, 'expiration_time')
strike = self.safe_string(market, 'strike_price')
isCall = self.safe_value(market, 'is_call')
optionLetter = 'C' if isCall else 'P'
optionType = 'call' if isCall else 'put'
symbol = symbol + ':' + quote + '-' + self.yymmdd(expiry) + '-' + strike + '-' + optionLetter
priceDeviate = self.safe_string(market, 'order_price_deviate')
markPrice = self.safe_string(market, 'mark_price')
minMultiplier = Precise.string_sub('1', priceDeviate)
maxMultiplier = Precise.string_add('1', priceDeviate)
minPrice = Precise.string_mul(minMultiplier, markPrice)
maxPrice = Precise.string_mul(maxMultiplier, markPrice)
result.append({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': quote,
'baseId': baseId,
'quoteId': quoteId,
'settleId': quoteId,
'type': 'option',
'spot': False,
'margin': False,
'swap': False,
'future': False,
'option': True,
'active': True,
'contract': True,
'linear': True,
'inverse': False,
'taker': None,
'maker': None,
'contractSize': self.parse_number('1'),
'expiry': expiry,
'expiryDatetime': self.iso8601(expiry),
'strike': self.parse_number(strike),
'optionType': optionType,
'precision': {
'amount': self.parse_number('1'), # all options have self step size
'price': self.safe_number(market, 'order_price_round'),
},
'limits': {
'leverage': {
'min': None,
'max': None,
},
'amount': {
'min': self.safe_number(market, 'order_size_min'),
'max': self.safe_number(market, 'order_size_max'),
},
'price': {
'min': self.parse_number(minPrice),
'max': self.parse_number(maxPrice),
},
'cost': {
'min': None,
'max': None,
},
},
'created': self.safe_timestamp(market, 'create_time'),
'info': market,
})
return result
async def fetch_option_underlyings(self):
underlyingsResponse = await self.publicOptionsGetUnderlyings()
#
# [
# {
# "index_time": "1646915796",
# "name": "BTC_USDT",
# "index_price": "39142.73"
# }
# ]
#
underlyings = []
for i in range(0, len(underlyingsResponse)):
underlying = underlyingsResponse[i]
name = self.safe_string(underlying, 'name')
if name is not None:
underlyings.append(name)
return underlyings
def prepare_request(self, market=None, type=None, params={}):
"""
@ignore
Fills request params contract, settle, currency_pair, market and account where applicable
:param dict market: CCXT market, required when type is None
:param str type: 'spot', 'swap', or 'future', required when market is None
:param dict [params]: request parameters
:returns: the api request object, and the new params object with non-needed parameters removed
"""
# * Do not call for multi spot order methods like cancelAllOrders and fetchOpenOrders. Use multiOrderSpotPrepareRequest instead
request: dict = {}
if market is not None:
if market['contract']:
request['contract'] = market['id']
if not market['option']:
request['settle'] = market['settleId']
else:
request['currency_pair'] = market['id']
else:
swap = type == 'swap'
future = type == 'future'
if swap or future:
defaultSettle = 'usdt' if swap else 'btc'
settle = self.safe_string_lower(params, 'settle', defaultSettle)
params = self.omit(params, 'settle')
request['settle'] = settle
return [request, params]
def spot_order_prepare_request(self, market=None, trigger=False, params={}):
"""
@ignore
Fills request params currency_pair, market and account where applicable for spot order methods like fetchOpenOrders, cancelAllOrders
:param dict market: CCXT market
:param bool trigger: True if for a trigger order
:param dict [params]: request parameters
:returns: the api request object, and the new params object with non-needed parameters removed
"""
marginMode, query = self.get_margin_mode(trigger, params)
request: dict = {}
if not trigger:
if market is None:
raise ArgumentsRequired(self.id + ' spotOrderPrepareRequest() requires a market argument for non-trigger orders')
request['account'] = marginMode
request['currency_pair'] = market['id'] # Should always be set for non-trigger
return [request, query]
def multi_order_spot_prepare_request(self, market=None, trigger=False, params={}):
"""
@ignore
Fills request params currency_pair, market and account where applicable for spot order methods like fetchOpenOrders, cancelAllOrders
:param dict market: CCXT market
:param bool trigger: True if for a trigger order
:param dict [params]: request parameters
:returns: the api request object, and the new params object with non-needed parameters removed
"""
marginMode, query = self.get_margin_mode(trigger, params)
request: dict = {
'account': marginMode,
}
if market is not None:
if trigger:
# gate spot and margin trigger orders use the term market instead of currency_pair, and normal instead of spot. Neither parameter is used when fetching/cancelling a single order. They are used for creating a single trigger order, but createOrder does not call self method
request['market'] = market['id']
else:
request['currency_pair'] = market['id']
return [request, query]
def get_margin_mode(self, trigger, params):
"""
@ignore
Gets the margin type for self api call
:param bool trigger: True if for a trigger order
:param dict [params]: Request params
:returns: The marginMode and the updated request params with marginMode removed, marginMode value is the value that can be read by the "account" property specified in gates api docs
"""
defaultMarginMode = self.safe_string_lower_2(self.options, 'defaultMarginMode', 'marginMode', 'spot') # 'margin' is isolated margin on gate's api
marginMode = self.safe_string_lower_2(params, 'marginMode', 'account', defaultMarginMode)
params = self.omit(params, ['marginMode', 'account'])
if marginMode == 'cross':
marginMode = 'cross_margin'
elif marginMode == 'isolated':
marginMode = 'margin'
elif marginMode == '':
marginMode = 'spot'
if trigger:
if marginMode == 'spot':
# gate spot trigger orders use the term normal instead of spot
marginMode = 'normal'
if marginMode == 'cross_margin':
raise BadRequest(self.id + ' getMarginMode() does not support trigger orders for cross margin')
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'getMarginMode', 'unifiedAccount')
if isUnifiedAccount:
marginMode = 'unified'
return [marginMode, params]
def get_settlement_currencies(self, type, method):
options = self.safe_value(self.options, type, {}) # ['BTC', 'USDT'] unified codes
fetchMarketsContractOptions = self.safe_value(options, method, {})
defaultSettle = ['usdt'] if (type == 'swap') else ['btc']
return self.safe_value(fetchMarketsContractOptions, 'settlementCurrencies', defaultSettle)
async def fetch_currencies(self, params={}) -> Currencies:
"""
fetches all available currencies on an exchange
https://www.gate.com/docs/developers/apiv4/en/#list-all-currencies-details
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an associative dictionary of currencies
"""
# sandbox/testnet only supports future markets
apiBackup = self.safe_value(self.urls, 'apiBackup')
if apiBackup is not None:
return {}
response = await self.publicSpotGetCurrencies(params)
#
# [
# {
# "currency": "USDT",
# "name": "Tether",
# "delisted": False,
# "withdraw_disabled": False,
# "withdraw_delayed": False,
# "deposit_disabled": False,
# "trade_disabled": False,
# "fixed_rate": "",
# "chain": "ETH",
# "chains": [
# {
# "name": "ETH",
# "addr": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
# "withdraw_disabled": False,
# "withdraw_delayed": False,
# "deposit_disabled": False
# },
# {
# "name": "ARBEVM",
# "addr": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
# "withdraw_disabled": False,
# "withdraw_delayed": False,
# "deposit_disabled": False
# },
# {
# "name": "BSC",
# "addr": "0x55d398326f99059fF775485246999027B3197955",
# "withdraw_disabled": False,
# "withdraw_delayed": False,
# "deposit_disabled": False
# },
# ]
# },
# ]
#
indexedCurrencies = self.index_by(response, 'currency')
result: dict = {}
for i in range(0, len(response)):
entry = response[i]
currencyId = self.safe_string(entry, 'currency')
code = self.safe_currency_code(currencyId)
# check leveraged tokens(e.g. BTC3S, ETH5L)
type = 'leveraged' if self.is_leveraged_currency(currencyId, True, indexedCurrencies) else 'crypto'
chains = self.safe_list(entry, 'chains', [])
networks = {}
for j in range(0, len(chains)):
chain = chains[j]
networkId = self.safe_string(chain, 'name')
networkCode = self.network_id_to_code(networkId)
networks[networkCode] = {
'info': chain,
'id': networkId,
'network': networkCode,
'active': None,
'deposit': not self.safe_bool(chain, 'deposit_disabled'),
'withdraw': not self.safe_bool(chain, 'withdraw_disabled'),
'fee': None,
'precision': self.parse_number('0.0001'), # temporary safe default, because no value provided from API,
'limits': {
'deposit': {
'min': None,
'max': None,
},
'withdraw': {
'min': None,
'max': None,
},
},
}
result[code] = self.safe_currency_structure({
'id': currencyId,
'code': code,
'name': self.safe_string(entry, 'name'),
'type': type,
'active': not self.safe_bool(entry, 'delisted'),
'deposit': not self.safe_bool(entry, 'deposit_disabled'),
'withdraw': not self.safe_bool(entry, 'withdraw_disabled'),
'fee': None,
'networks': networks,
'precision': self.parse_number('0.0001'),
'info': entry,
})
return result
async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
"""
fetch the current funding rate
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-contract
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
"""
await self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' fetchFundingRate() supports swap contracts only')
request, query = self.prepare_request(market, None, params)
response = await self.publicFuturesGetSettleContractsContract(self.extend(request, query))
#
# [
# {
# "name": "BTC_USDT",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.5",
# "maintenance_rate": "0.005",
# "mark_type": "index",
# "last_price": "38026",
# "mark_price": "37985.6",
# "index_price": "37954.92",
# "funding_rate_indicative": "0.000219",
# "mark_price_round": "0.01",
# "funding_offset": 0,
# "in_delisting": False,
# "risk_limit_base": "1000000",
# "interest_rate": "0.0003",
# "order_price_round": "0.1",
# "order_size_min": 1,
# "ref_rebate_rate": "0.2",
# "funding_interval": 28800,
# "risk_limit_step": "1000000",
# "leverage_min": "1",
# "leverage_max": "100",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025",
# "taker_fee_rate": "0.00075",
# "funding_rate": "0.002053",
# "order_size_max": 1000000,
# "funding_next_apply": 1610035200,
# "short_users": 977,
# "config_change_time": 1609899548,
# "trade_size": 28530850594,
# "position_size": 5223816,
# "long_users": 455,
# "funding_impact_value": "60000",
# "orders_limit": 50,
# "trade_id": 10851092,
# "orderbook_id": 2129638396
# }
# ]
#
return self.parse_funding_rate(response)
async def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
"""
fetch the funding rate for multiple markets
https://www.gate.com/docs/developers/apiv4/en/#list-all-futures-contracts
:param str[]|None symbols: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rates-structure>`, indexed by market symbols
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
market = None
if symbols is not None:
firstSymbol = self.safe_string(symbols, 0)
market = self.market(firstSymbol)
request, query = self.prepare_request(market, 'swap', params)
response = await self.publicFuturesGetSettleContracts(self.extend(request, query))
#
# [
# {
# "name": "BTC_USDT",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.5",
# "maintenance_rate": "0.005",
# "mark_type": "index",
# "last_price": "38026",
# "mark_price": "37985.6",
# "index_price": "37954.92",
# "funding_rate_indicative": "0.000219",
# "mark_price_round": "0.01",
# "funding_offset": 0,
# "in_delisting": False,
# "risk_limit_base": "1000000",
# "interest_rate": "0.0003",
# "order_price_round": "0.1",
# "order_size_min": 1,
# "ref_rebate_rate": "0.2",
# "funding_interval": 28800,
# "risk_limit_step": "1000000",
# "leverage_min": "1",
# "leverage_max": "100",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025",
# "taker_fee_rate": "0.00075",
# "funding_rate": "0.002053",
# "order_size_max": 1000000,
# "funding_next_apply": 1610035200,
# "short_users": 977,
# "config_change_time": 1609899548,
# "trade_size": 28530850594,
# "position_size": 5223816,
# "long_users": 455,
# "funding_impact_value": "60000",
# "orders_limit": 50,
# "trade_id": 10851092,
# "orderbook_id": 2129638396
# }
# ]
#
return self.parse_funding_rates(response, symbols)
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
#
# {
# "name": "BTC_USDT",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.5",
# "maintenance_rate": "0.005",
# "mark_type": "index",
# "last_price": "38026",
# "mark_price": "37985.6",
# "index_price": "37954.92",
# "funding_rate_indicative": "0.000219",
# "mark_price_round": "0.01",
# "funding_offset": 0,
# "in_delisting": False,
# "risk_limit_base": "1000000",
# "interest_rate": "0.0003",
# "order_price_round": "0.1",
# "order_size_min": 1,
# "ref_rebate_rate": "0.2",
# "funding_interval": 28800,
# "risk_limit_step": "1000000",
# "leverage_min": "1",
# "leverage_max": "100",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025",
# "taker_fee_rate": "0.00075",
# "funding_rate": "0.002053",
# "order_size_max": 1000000,
# "funding_next_apply": 1610035200,
# "short_users": 977,
# "config_change_time": 1609899548,
# "trade_size": 28530850594,
# "position_size": 5223816,
# "long_users": 455,
# "funding_impact_value": "60000",
# "orders_limit": 50,
# "trade_id": 10851092,
# "orderbook_id": 2129638396
# }
#
marketId = self.safe_string(contract, 'name')
symbol = self.safe_symbol(marketId, market, '_', 'swap')
markPrice = self.safe_number(contract, 'mark_price')
indexPrice = self.safe_number(contract, 'index_price')
interestRate = self.safe_number(contract, 'interest_rate')
fundingRate = self.safe_number(contract, 'funding_rate')
fundingTime = self.safe_timestamp(contract, 'funding_next_apply')
fundingRateIndicative = self.safe_number(contract, 'funding_rate_indicative')
fundingInterval = Precise.string_mul('1000', self.safe_string(contract, 'funding_interval'))
return {
'info': contract,
'symbol': symbol,
'markPrice': markPrice,
'indexPrice': indexPrice,
'interestRate': interestRate,
'estimatedSettlePrice': None,
'timestamp': None,
'datetime': None,
'fundingRate': fundingRate,
'fundingTimestamp': fundingTime,
'fundingDatetime': self.iso8601(fundingTime),
'nextFundingRate': fundingRateIndicative,
'nextFundingTimestamp': None,
'nextFundingDatetime': None,
'previousFundingRate': None,
'previousFundingTimestamp': None,
'previousFundingDatetime': None,
'interval': self.parse_funding_interval(fundingInterval),
}
def parse_funding_interval(self, interval):
intervals: dict = {
'3600000': '1h',
'14400000': '4h',
'28800000': '8h',
'57600000': '16h',
'86400000': '24h',
}
return self.safe_string(intervals, interval, interval)
async def fetch_network_deposit_address(self, code: str, params={}):
await self.load_markets()
currency = self.currency(code)
request: dict = {
'currency': currency['id'], # todo: currencies have network-junctions
}
response = await self.privateWalletGetDepositAddress(self.extend(request, params))
addresses = self.safe_value(response, 'multichain_addresses')
currencyId = self.safe_string(response, 'currency')
code = self.safe_currency_code(currencyId)
result: dict = {}
for i in range(0, len(addresses)):
entry = addresses[i]
#
# {
# "chain": "ETH",
# "address": "0x359a697945E79C7e17b634675BD73B33324E9408",
# "payment_id": "",
# "payment_name": "",
# "obtain_failed": "0"
# }
#
obtainFailed = self.safe_integer(entry, 'obtain_failed')
if obtainFailed:
continue
network = self.safe_string(entry, 'chain')
address = self.safe_string(entry, 'address')
tag = self.safe_string(entry, 'payment_id')
result[network] = {
'info': entry,
'code': code, # kept here for backward-compatibility, but will be removed soon
'currency': code,
'address': address,
'tag': tag,
}
return result
async def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]:
"""
fetch a dictionary of addresses for a currency, indexed by network
:param str code: unified currency code of the currency for the deposit address
:param dict [params]: extra parameters specific to the api endpoint
:returns dict: a dictionary of `address structures <https://docs.ccxt.com/#/?id=address-structure>` indexed by the network
"""
await self.load_markets()
currency = self.currency(code)
request = {
'currency': currency['id'],
}
response = await self.privateWalletGetDepositAddress(self.extend(request, params))
chains = self.safe_value(response, 'multichain_addresses', [])
currencyId = self.safe_string(response, 'currency')
currency = self.safe_currency(currencyId, currency)
parsed = self.parse_deposit_addresses(chains, None, False)
return self.index_by(parsed, 'network')
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
"""
fetch the deposit address for a currency associated with self account
https://www.gate.com/docs/developers/apiv4/en/#generate-currency-deposit-address
:param str code: unified currency code
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.network]: unified network code(not used directly by gate.com but used by ccxt to filter the response)
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
"""
await self.load_markets()
networkCode = None
networkCode, params = self.handle_network_code_and_params(params)
chainsIndexedById = await self.fetch_deposit_addresses_by_network(code, params)
selectedNetworkIdOrCode = self.select_network_code_from_unified_networks(code, networkCode, chainsIndexedById)
return chainsIndexedById[selectedNetworkIdOrCode]
def parse_deposit_address(self, depositAddress, currency=None):
#
# {
# chain: "BTC",
# address: "1Nxu.......Ys",
# payment_id: "",
# payment_name: "",
# obtain_failed: "0",
# }
#
address = self.safe_string(depositAddress, 'address')
self.check_address(address)
return {
'info': depositAddress,
'currency': self.safe_string(currency, 'code'),
'address': address,
'tag': self.safe_string(depositAddress, 'payment_id'),
'network': self.network_id_to_code(self.safe_string(depositAddress, 'chain')),
}
async def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
"""
fetch the trading fees for a market
https://www.gate.com/docs/developers/apiv4/en/#retrieve-personal-trading-fee
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
"""
await self.load_markets()
market = self.market(symbol)
request: dict = {
'currency_pair': market['id'],
}
response = await self.privateWalletGetFee(self.extend(request, params))
#
# {
# "user_id": 1486602,
# "taker_fee": "0.002",
# "maker_fee": "0.002",
# "gt_discount": True,
# "gt_taker_fee": "0.0015",
# "gt_maker_fee": "0.0015",
# "loan_fee": "0.18",
# "point_type": "0",
# "futures_taker_fee": "0.0005",
# "futures_maker_fee": "0"
# }
#
return self.parse_trading_fee(response, market)
async def fetch_trading_fees(self, params={}) -> TradingFees:
"""
fetch the trading fees for multiple markets
https://www.gate.com/docs/developers/apiv4/en/#retrieve-personal-trading-fee
: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
"""
await self.load_markets()
response = await self.privateWalletGetFee(params)
#
# {
# "user_id": 1486602,
# "taker_fee": "0.002",
# "maker_fee": "0.002",
# "gt_discount": True,
# "gt_taker_fee": "0.0015",
# "gt_maker_fee": "0.0015",
# "loan_fee": "0.18",
# "point_type": "0",
# "futures_taker_fee": "0.0005",
# "futures_maker_fee": "0"
# }
#
return self.parse_trading_fees(response)
def parse_trading_fees(self, response):
result: dict = {}
for i in range(0, len(self.symbols)):
symbol = self.symbols[i]
market = self.market(symbol)
result[symbol] = self.parse_trading_fee(response, market)
return result
def parse_trading_fee(self, info, market: Market = None):
#
# {
# "user_id": 1486602,
# "taker_fee": "0.002",
# "maker_fee": "0.002",
# "gt_discount": True,
# "gt_taker_fee": "0.0015",
# "gt_maker_fee": "0.0015",
# "loan_fee": "0.18",
# "point_type": "0",
# "futures_taker_fee": "0.0005",
# "futures_maker_fee": "0"
# }
#
gtDiscount = self.safe_value(info, 'gt_discount')
taker = 'gt_taker_fee' if gtDiscount else 'taker_fee'
maker = 'gt_maker_fee' if gtDiscount else 'maker_fee'
contract = self.safe_value(market, 'contract')
takerKey = 'futures_taker_fee' if contract else taker
makerKey = 'futures_maker_fee' if contract else maker
return {
'info': info,
'symbol': self.safe_string(market, 'symbol'),
'maker': self.safe_number(info, makerKey),
'taker': self.safe_number(info, takerKey),
'percentage': None,
'tierBased': None,
}
async def fetch_transaction_fees(self, codes: Strings = None, params={}):
"""
@deprecated
please use fetchDepositWithdrawFees instead
https://www.gate.com/docs/developers/apiv4/en/#retrieve-withdrawal-status
: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 `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
"""
await self.load_markets()
response = await self.privateWalletGetWithdrawStatus(params)
#
# {
# "currency": "MTN",
# "name": "Medicalchain",
# "name_cn": "Medicalchain",
# "deposit": "0",
# "withdraw_percent": "0%",
# "withdraw_fix": "900",
# "withdraw_day_limit": "500000",
# "withdraw_day_limit_remain": "500000",
# "withdraw_amount_mini": "900.1",
# "withdraw_eachtime_limit": "90000000000",
# "withdraw_fix_on_chains": {
# "ETH": "900"
# }
# }
#
result: dict = {}
withdrawFees = {}
for i in range(0, len(response)):
withdrawFees = {}
entry = response[i]
currencyId = self.safe_string(entry, 'currency')
code = self.safe_currency_code(currencyId)
if (codes is not None) and not self.in_array(code, codes):
continue
withdrawFixOnChains = self.safe_value(entry, 'withdraw_fix_on_chains')
if withdrawFixOnChains is None:
withdrawFees = self.safe_number(entry, 'withdraw_fix')
else:
networkIds = list(withdrawFixOnChains.keys())
for j in range(0, len(networkIds)):
networkId = networkIds[j]
networkCode = self.network_id_to_code(networkId)
withdrawFees[networkCode] = self.parse_number(withdrawFixOnChains[networkId])
result[code] = {
'withdraw': withdrawFees,
'deposit': None,
'info': entry,
}
return result
async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
"""
fetch deposit and withdraw fees
https://www.gate.com/docs/developers/apiv4/en/#retrieve-withdrawal-status
: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 `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
"""
await self.load_markets()
response = await self.privateWalletGetWithdrawStatus(params)
#
# [
# {
# "currency": "MTN",
# "name": "Medicalchain",
# "name_cn": "Medicalchain",
# "deposit": "0",
# "withdraw_percent": "0%",
# "withdraw_fix": "900",
# "withdraw_day_limit": "500000",
# "withdraw_day_limit_remain": "500000",
# "withdraw_amount_mini": "900.1",
# "withdraw_eachtime_limit": "90000000000",
# "withdraw_fix_on_chains": {
# "ETH": "900"
# }
# }
# ]
#
return self.parse_deposit_withdraw_fees(response, codes, 'currency')
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
#
# {
# "currency": "MTN",
# "name": "Medicalchain",
# "name_cn": "Medicalchain",
# "deposit": "0",
# "withdraw_percent": "0%",
# "withdraw_fix": "900",
# "withdraw_day_limit": "500000",
# "withdraw_day_limit_remain": "500000",
# "withdraw_amount_mini": "900.1",
# "withdraw_eachtime_limit": "90000000000",
# "withdraw_fix_on_chains": {
# "ETH": "900"
# }
# }
#
withdrawFixOnChains = self.safe_value(fee, 'withdraw_fix_on_chains')
result: dict = {
'info': fee,
'withdraw': {
'fee': self.safe_number(fee, 'withdraw_fix'),
'percentage': False,
},
'deposit': {
'fee': self.safe_number(fee, 'deposit'),
'percentage': False,
},
'networks': {},
}
if withdrawFixOnChains is not None:
chainKeys = list(withdrawFixOnChains.keys())
for i in range(0, len(chainKeys)):
chainKey = chainKeys[i]
networkCode = self.network_id_to_code(chainKey, self.safe_string(fee, 'currency'))
result['networks'][networkCode] = {
'withdraw': {
'fee': self.parse_number(withdrawFixOnChains[chainKey]),
'percentage': False,
},
'deposit': {
'fee': None,
'percentage': None,
},
}
return result
async def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch the history of funding payments paid and received on self account
https://www.gate.com/docs/developers/apiv4/en/#query-account-book-2
https://www.gate.com/docs/developers/apiv4/en/#query-account-book-3
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch funding history for
:param int [limit]: the maximum number of funding history structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
"""
await self.load_markets()
# defaultType = 'future'
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
type, query = self.handle_market_type_and_params('fetchFundingHistory', market, params)
request, requestParams = self.prepare_request(market, type, query)
request['type'] = 'fund' # 'dnw' 'pnl' 'fee' 'refr' 'fund' 'point_dnw' 'point_fee' 'point_refr'
if since is not None:
# from should be integer
request['from'] = self.parse_to_int(since / 1000)
if limit is not None:
request['limit'] = limit
response = None
if type == 'swap':
response = await self.privateFuturesGetSettleAccountBook(self.extend(request, requestParams))
elif type == 'future':
response = await self.privateDeliveryGetSettleAccountBook(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' fetchFundingHistory() only support swap & future market type')
#
# [
# {
# "time": 1646899200,
# "change": "-0.027722",
# "balance": "11.653120591841",
# "text": "XRP_USDT",
# "type": "fund"
# },
# ...
# ]
#
return self.parse_funding_histories(response, symbol, since, limit)
def parse_funding_histories(self, response, symbol, since, limit) -> List[FundingHistory]:
result = []
for i in range(0, len(response)):
entry = response[i]
funding = self.parse_funding_history(entry)
result.append(funding)
sorted = self.sort_by(result, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
def parse_funding_history(self, info, market: Market = None):
#
# {
# "time": 1646899200,
# "change": "-0.027722",
# "balance": "11.653120591841",
# "text": "XRP_USDT",
# "type": "fund"
# }
#
timestamp = self.safe_timestamp(info, 'time')
marketId = self.safe_string(info, 'text')
market = self.safe_market(marketId, market, '_', 'swap')
return {
'info': info,
'symbol': self.safe_string(market, 'symbol'),
'code': self.safe_string(market, 'settle'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'id': None,
'amount': self.safe_number(info, 'change'),
}
async 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://www.gate.com/docs/developers/apiv4/en/#retrieve-order-book
https://www.gate.com/docs/developers/apiv4/en/#futures-order-book
https://www.gate.com/docs/developers/apiv4/en/#futures-order-book-2
https://www.gate.com/docs/developers/apiv4/en/#options-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
"""
await self.load_markets()
market = self.market(symbol)
#
# request: Dict = {
# 'currency_pair': market['id'],
# 'interval': '0', # depth, 0 means no aggregation is applied, default to 0
# 'limit': limit, # maximum number of order depth data in asks or bids
# 'with_id': True, # return order book ID
# }
#
request, query = self.prepare_request(market, market['type'], params)
if limit is not None:
if market['spot']:
limit = min(limit, 1000)
else:
limit = min(limit, 300)
request['limit'] = limit
request['with_id'] = True
response = None
if market['spot'] or market['margin']:
response = await self.publicSpotGetOrderBook(self.extend(request, query))
elif market['swap']:
response = await self.publicFuturesGetSettleOrderBook(self.extend(request, query))
elif market['future']:
response = await self.publicDeliveryGetSettleOrderBook(self.extend(request, query))
elif market['option']:
response = await self.publicOptionsGetOrderBook(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchOrderBook() not support self market type')
#
# spot
#
# {
# "id": 6358770031
# "current": 1634345973275,
# "update": 1634345973271,
# "asks": [
# ["2.2241","12449.827"],
# ["2.2242","200"],
# ["2.2244","826.931"],
# ["2.2248","3876.107"],
# ["2.225","2377.252"],
# ["2.22509","439.484"],
# ["2.2251","1489.313"],
# ["2.2253","714.582"],
# ["2.2254","1349.784"],
# ["2.2256","234.701"]],
# "bids": [
# ["2.2236","32.465"],
# ["2.2232","243.983"],
# ["2.2231","32.207"],
# ["2.223","449.827"],
# ["2.2228","7.918"],
# ["2.2227","12703.482"],
# ["2.2226","143.033"],
# ["2.2225","143.027"],
# ["2.2224","1369.352"],
# ["2.2223","756.063"]
# ]
# }
#
# swap, future and option
#
# {
# "id": 6358770031
# "current": 1634350208.745,
# "asks": [
# {"s": 24909, "p": "61264.8"},
# {"s": 81, "p": "61266.6"},
# {"s": 2000, "p": "61267.6"},
# {"s": 490, "p": "61270.2"},
# {"s": 12, "p": "61270.4"},
# {"s": 11782, "p": "61273.2"},
# {"s": 14666, "p": "61273.3"},
# {"s": 22541, "p": "61273.4"},
# {"s": 33, "p": "61273.6"},
# {"s": 11980, "p": "61274.5"}
# ],
# "bids": [
# {"s": 41844, "p": "61264.7"},
# {"s": 13783, "p": "61263.3"},
# {"s": 1143, "p": "61259.8"},
# {"s": 81, "p": "61258.7"},
# {"s": 2471, "p": "61257.8"},
# {"s": 2471, "p": "61257.7"},
# {"s": 2471, "p": "61256.5"},
# {"s": 3, "p": "61254.2"},
# {"s": 114, "p": "61252.4"},
# {"s": 14372, "p": "61248.6"}
# ],
# "update": 1634350208.724
# }
#
timestamp = self.safe_integer(response, 'current')
if not market['spot']:
timestamp = timestamp * 1000
priceKey = 0 if market['spot'] else 'p'
amountKey = 1 if market['spot'] else 's'
nonce = self.safe_integer(response, 'id')
result = self.parse_order_book(response, symbol, timestamp, 'bids', 'asks', priceKey, amountKey)
result['nonce'] = nonce
return result
async 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://www.gate.com/docs/developers/apiv4/en/#get-details-of-a-specifc-order
https://www.gate.com/docs/developers/apiv4/en/#list-futures-tickers
https://www.gate.com/docs/developers/apiv4/en/#list-futures-tickers-2
https://www.gate.com/docs/developers/apiv4/en/#list-tickers-of-options-contracts
: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>`
"""
await self.load_markets()
market = self.market(symbol)
request, query = self.prepare_request(market, None, params)
response = None
if market['spot'] or market['margin']:
response = await self.publicSpotGetTickers(self.extend(request, query))
elif market['swap']:
response = await self.publicFuturesGetSettleTickers(self.extend(request, query))
elif market['future']:
response = await self.publicDeliveryGetSettleTickers(self.extend(request, query))
elif market['option']:
marketId = market['id']
optionParts = marketId.split('-')
request['underlying'] = self.safe_string(optionParts, 0)
response = await self.publicOptionsGetTickers(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchTicker() not support self market type')
ticker = None
if market['option']:
for i in range(0, len(response)):
entry = response[i]
if entry['name'] == market['id']:
ticker = entry
break
else:
ticker = self.safe_value(response, 0)
return self.parse_ticker(ticker, market)
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
#
# SPOT
#
# {
# "currency_pair": "KFC_USDT",
# "last": "7.255",
# "lowest_ask": "7.298",
# "highest_bid": "7.218",
# "change_percentage": "-1.18",
# "base_volume": "1219.053687865",
# "quote_volume": "8807.40299875455",
# "high_24h": "7.262",
# "low_24h": "7.095"
# }
#
# LINEAR/DELIVERY
#
# {
# "contract": "BTC_USDT",
# "last": "6432",
# "low_24h": "6278",
# "high_24h": "6790",
# "change_percentage": "4.43",
# "total_size": "32323904",
# "volume_24h": "184040233284",
# "volume_24h_btc": "28613220",
# "volume_24h_usd": "184040233284",
# "volume_24h_base": "28613220",
# "volume_24h_quote": "184040233284",
# "volume_24h_settle": "28613220",
# "mark_price": "6534",
# "funding_rate": "0.0001",
# "funding_rate_indicative": "0.0001",
# "index_price": "6531"
# }
#
# bookTicker
# {
# "t": 1671363004228,
# "u": 9793320464,
# "s": "BTC_USDT",
# "b": "16716.8", # best bid price
# "B": "0.0134", # best bid size
# "a": "16716.9", # best ask price
# "A": "0.0353" # best ask size
# }
#
# option
#
# {
# "vega": "0.00002",
# "leverage": "12.277188268663",
# "ask_iv": "0",
# "delta": "-0.99999",
# "last_price": "0",
# "theta": "-0.00661",
# "bid1_price": "1096",
# "mark_iv": "0.7799",
# "name": "BTC_USDT-20230608-28500-P",
# "bid_iv": "0",
# "ask1_price": "2935",
# "mark_price": "2147.3",
# "position_size": 0,
# "bid1_size": 12,
# "ask1_size": -14,
# "gamma": "0"
# }
#
marketId = self.safe_string_n(ticker, ['currency_pair', 'contract', 'name'])
marketType = 'contract' if ('mark_price' in ticker) else 'spot'
symbol = self.safe_symbol(marketId, market, '_', marketType)
last = self.safe_string_2(ticker, 'last', 'last_price')
ask = self.safe_string_n(ticker, ['lowest_ask', 'a', 'ask1_price'])
bid = self.safe_string_n(ticker, ['highest_bid', 'b', 'bid1_price'])
high = self.safe_string(ticker, 'high_24h')
low = self.safe_string(ticker, 'low_24h')
bidVolume = self.safe_string_2(ticker, 'B', 'bid1_size')
askVolume = self.safe_string_2(ticker, 'A', 'ask1_size')
timestamp = self.safe_integer(ticker, 't')
baseVolume = self.safe_string_2(ticker, 'base_volume', 'volume_24h_base')
if baseVolume == 'nan':
baseVolume = '0'
quoteVolume = self.safe_string_2(ticker, 'quote_volume', 'volume_24h_quote')
if quoteVolume == 'nan':
quoteVolume = '0'
percentage = self.safe_string(ticker, 'change_percentage')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'high': high,
'low': low,
'bid': bid,
'bidVolume': bidVolume,
'ask': ask,
'askVolume': askVolume,
'vwap': None,
'open': None,
'close': last,
'last': last,
'previousClose': None,
'change': None,
'percentage': percentage,
'average': None,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'markPrice': self.safe_string(ticker, 'mark_price'),
'indexPrice': self.safe_string(ticker, 'index_price'),
'info': ticker,
}, market)
async 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://www.gate.com/docs/developers/apiv4/en/#get-details-of-a-specifc-order
https://www.gate.com/docs/developers/apiv4/en/#list-futures-tickers
https://www.gate.com/docs/developers/apiv4/en/#list-futures-tickers-2
https://www.gate.com/docs/developers/apiv4/en/#list-tickers-of-options-contracts
: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>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
first = self.safe_string(symbols, 0)
market = None
if first is not None:
market = self.market(first)
type, query = self.handle_market_type_and_params('fetchTickers', market, params)
request, requestParams = self.prepare_request(None, type, query)
response = None
request['timezone'] = 'utc0' # default to utc
if type == 'spot' or type == 'margin':
response = await self.publicSpotGetTickers(self.extend(request, requestParams))
elif type == 'swap':
response = await self.publicFuturesGetSettleTickers(self.extend(request, requestParams))
elif type == 'future':
response = await self.publicDeliveryGetSettleTickers(self.extend(request, requestParams))
elif type == 'option':
self.check_required_argument('fetchTickers', symbols, 'symbols')
marketId = market['id']
optionParts = marketId.split('-')
request['underlying'] = self.safe_string(optionParts, 0)
response = await self.publicOptionsGetTickers(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' fetchTickers() not support self market type, provide symbols or set params["defaultType"] to one from spot/margin/swap/future/option')
return self.parse_tickers(response, symbols)
def parse_balance_helper(self, entry):
account = self.account()
account['used'] = self.safe_string_2(entry, 'freeze', 'locked')
account['free'] = self.safe_string(entry, 'available')
account['total'] = self.safe_string(entry, 'total')
if 'borrowed' in entry:
account['debt'] = self.safe_string(entry, 'borrowed')
return account
async def fetch_balance(self, params={}) -> Balances:
"""
https://www.gate.com/docs/developers/apiv4/en/#margin-account-list
https://www.gate.com/docs/developers/apiv4/en/#get-unified-account-information
https://www.gate.com/docs/developers/apiv4/en/#list-spot-trading-accounts
https://www.gate.com/docs/developers/apiv4/en/#get-futures-account
https://www.gate.com/docs/developers/apiv4/en/#get-futures-account-2
https://www.gate.com/docs/developers/apiv4/en/#query-account-information
:param dict [params]: exchange specific parameters
:param str [params.type]: spot, margin, swap or future, if not provided self.options['defaultType'] is used
:param str [params.settle]: 'btc' or 'usdt' - settle currency for perpetual swap and future - default="usdt" for swap and "btc" for future
:param str [params.marginMode]: 'cross' or 'isolated' - marginMode for margin trading if not provided self.options['defaultMarginMode'] is used
:param str [params.symbol]: margin only - unified ccxt symbol
:param boolean [params.unifiedAccount]: default False, set to True for fetching the unified account balance
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.load_markets()
await self.load_unified_status()
symbol = self.safe_string(params, 'symbol')
params = self.omit(params, 'symbol')
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'fetchBalance', 'unifiedAccount')
type, query = self.handle_market_type_and_params('fetchBalance', None, params)
request, requestParams = self.prepare_request(None, type, query)
marginMode, requestQuery = self.get_margin_mode(False, requestParams)
if symbol is not None:
market = self.market(symbol)
request['currency_pair'] = market['id']
response = None
if isUnifiedAccount:
response = await self.privateUnifiedGetAccounts(self.extend(request, params))
elif type == 'spot':
if marginMode == 'spot':
response = await self.privateSpotGetAccounts(self.extend(request, requestQuery))
elif marginMode == 'margin':
response = await self.privateMarginGetAccounts(self.extend(request, requestQuery))
elif marginMode == 'cross_margin':
response = await self.privateMarginGetCrossAccounts(self.extend(request, requestQuery))
else:
raise NotSupported(self.id + ' fetchBalance() not support self marginMode')
elif type == 'funding':
response = await self.privateMarginGetFundingAccounts(self.extend(request, requestQuery))
elif type == 'swap':
response = await self.privateFuturesGetSettleAccounts(self.extend(request, requestQuery))
elif type == 'future':
response = await self.privateDeliveryGetSettleAccounts(self.extend(request, requestQuery))
elif type == 'option':
response = await self.privateOptionsGetAccounts(self.extend(request, requestQuery))
else:
raise NotSupported(self.id + ' fetchBalance() not support self market type')
contract = ((type == 'swap') or (type == 'future') or (type == 'option'))
if contract:
response = [response]
#
# Spot / margin funding
#
# [
# {
# "currency": "DBC",
# "available": "0",
# "locked": "0"
# "lent": "0", # margin funding only
# "total_lent": "0" # margin funding only
# },
# ...
# ]
#
# Margin
#
# [
# {
# "currency_pair": "DOGE_USDT",
# "locked": False,
# "risk": "9999.99",
# "base": {
# "currency": "DOGE",
# "available": "0",
# "locked": "0",
# "borrowed": "0",
# "interest": "0"
# },
# "quote": {
# "currency": "USDT",
# "available": "0.73402",
# "locked": "0",
# "borrowed": "0",
# "interest": "0"
# }
# },
# ...
# ]
#
# Cross margin
#
# {
# "user_id": 10406147,
# "locked": False,
# "balances": {
# "USDT": {
# "available": "1",
# "freeze": "0",
# "borrowed": "0",
# "interest": "0"
# }
# },
# "total": "1",
# "borrowed": "0",
# "interest": "0",
# "risk": "9999.99"
# }
#
# Perpetual Swap
#
# {
# "order_margin": "0",
# "point": "0",
# "bonus": "0",
# "history": {
# "dnw": "2.1321",
# "pnl": "11.5351",
# "refr": "0",
# "point_fee": "0",
# "fund": "-0.32340576684",
# "bonus_dnw": "0",
# "point_refr": "0",
# "bonus_offset": "0",
# "fee": "-0.20132775",
# "point_dnw": "0",
# },
# "unrealised_pnl": "13.315100000006",
# "total": "12.51345151332",
# "available": "0",
# "in_dual_mode": False,
# "currency": "USDT",
# "position_margin": "12.51345151332",
# "user": "6333333",
# }
#
# Delivery Future
#
# {
# "order_margin": "0",
# "point": "0",
# "history": {
# "dnw": "1",
# "pnl": "0",
# "refr": "0",
# "point_fee": "0",
# "point_dnw": "0",
# "settle": "0",
# "settle_fee": "0",
# "point_refr": "0",
# "fee": "0",
# },
# "unrealised_pnl": "0",
# "total": "1",
# "available": "1",
# "currency": "USDT",
# "position_margin": "0",
# "user": "6333333",
# }
#
# option
#
# {
# "order_margin": "0",
# "bid_order_margin": "0",
# "init_margin": "0",
# "history": {
# "dnw": "32",
# "set": "0",
# "point_fee": "0",
# "point_dnw": "0",
# "prem": "0",
# "point_refr": "0",
# "insur": "0",
# "fee": "0",
# "refr": "0"
# },
# "total": "32",
# "available": "32",
# "liq_triggered": False,
# "maint_margin": "0",
# "ask_order_margin": "0",
# "point": "0",
# "position_notional_limit": "2000000",
# "unrealised_pnl": "0",
# "equity": "32",
# "user": 5691076,
# "currency": "USDT",
# "short_enabled": False,
# "orders_limit": 10
# }
#
# unified
#
# {
# "user_id": 10001,
# "locked": False,
# "balances": {
# "ETH": {
# "available": "0",
# "freeze": "0",
# "borrowed": "0.075393666654",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "1016.1",
# "total_freeze": "0",
# "total_liab": "0"
# },
# "POINT": {
# "available": "9999999999.017023138734",
# "freeze": "0",
# "borrowed": "0",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "12016.1",
# "total_freeze": "0",
# "total_liab": "0"
# },
# "USDT": {
# "available": "0.00000062023",
# "freeze": "0",
# "borrowed": "0",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "16.1",
# "total_freeze": "0",
# "total_liab": "0"
# }
# },
# "total": "230.94621713",
# "borrowed": "161.66395521",
# "total_initial_margin": "1025.0524665088",
# "total_margin_balance": "3382495.944473949183",
# "total_maintenance_margin": "205.01049330176",
# "total_initial_margin_rate": "3299.827135672679",
# "total_maintenance_margin_rate": "16499.135678363399",
# "total_available_margin": "3381470.892007440383",
# "unified_account_total": "3381470.892007440383",
# "unified_account_total_liab": "0",
# "unified_account_total_equity": "100016.1",
# "leverage": "2"
# }
#
result: dict = {
'info': response,
}
isolated = marginMode == 'margin' and type == 'spot'
data = response
if 'balances' in data: # True for cross_margin and unified
flatBalances = []
balances = self.safe_value(data, 'balances', [])
# inject currency and create an artificial balance object
# so it can follow the existent flow
keys = list(balances.keys())
for i in range(0, len(keys)):
currencyId = keys[i]
content = balances[currencyId]
content['currency'] = currencyId
flatBalances.append(content)
data = flatBalances
for i in range(0, len(data)):
entry = data[i]
if isolated:
marketId = self.safe_string(entry, 'currency_pair')
symbolInner = self.safe_symbol(marketId, None, '_', 'margin')
base = self.safe_value(entry, 'base', {})
quote = self.safe_value(entry, 'quote', {})
baseCode = self.safe_currency_code(self.safe_string(base, 'currency'))
quoteCode = self.safe_currency_code(self.safe_string(quote, 'currency'))
subResult: dict = {}
subResult[baseCode] = self.parse_balance_helper(base)
subResult[quoteCode] = self.parse_balance_helper(quote)
result[symbolInner] = self.safe_balance(subResult)
else:
code = self.safe_currency_code(self.safe_string(entry, 'currency'))
result[code] = self.parse_balance_helper(entry)
returnResult = result if isolated else self.safe_balance(result)
return returnResult
async def fetch_ohlcv(self, symbol: str, timeframe: str = '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://www.gate.com/docs/developers/apiv4/en/#market-candlesticks # spot
https://www.gate.com/docs/developers/apiv4/en/#get-futures-candlesticks # swap
https://www.gate.com/docs/developers/apiv4/en/#market-candlesticks # future
https://www.gate.com/docs/developers/apiv4/en/#get-options-candlesticks # option
: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, limit is conflicted with since and params["until"], If either since and params["until"] is specified, request will be rejected
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.price]: "mark" or "index" for mark price and index price candles
:param int [params.until]: timestamp in ms of the latest candle to fetch
: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(units in quote currency)
"""
await self.load_markets()
market = self.market(symbol)
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
if paginate:
return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 1000)
if market['option']:
return await self.fetch_option_ohlcv(symbol, timeframe, since, limit, params)
price = self.safe_string(params, 'price')
request: dict = {}
request, params = self.prepare_request(market, None, params)
request['interval'] = self.safe_string(self.timeframes, timeframe, timeframe)
maxLimit = 1999 if market['contract'] else 1000
limit = maxLimit if (limit is None) else min(limit, maxLimit)
until = self.safe_integer(params, 'until')
if until is not None:
until = self.parse_to_int(until / 1000)
params = self.omit(params, 'until')
if since is not None:
duration = self.parse_timeframe(timeframe)
request['from'] = self.parse_to_int(since / 1000)
distance = (limit - 1) * duration
toTimestamp = self.sum(request['from'], distance)
currentTimestamp = self.seconds()
to = min(toTimestamp, currentTimestamp)
if until is not None:
request['to'] = min(to, until)
else:
request['to'] = to
else:
if until is not None:
request['to'] = until
request['limit'] = limit
response = None
if market['contract']:
isMark = (price == 'mark')
isIndex = (price == 'index')
if isMark or isIndex:
request['contract'] = price + '_' + market['id']
params = self.omit(params, 'price')
if market['future']:
response = await self.publicDeliveryGetSettleCandlesticks(self.extend(request, params))
elif market['swap']:
response = await self.publicFuturesGetSettleCandlesticks(self.extend(request, params))
else:
response = await self.publicSpotGetCandlesticks(self.extend(request, params))
return self.parse_ohlcvs(response, market, timeframe, since, limit)
async def fetch_option_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
# separated option logic because the from, to and limit parameters weren't functioning
await self.load_markets()
market = self.market(symbol)
request: dict = {}
request, params = self.prepare_request(market, None, params)
request['interval'] = self.safe_string(self.timeframes, timeframe, timeframe)
response = await self.publicOptionsGetCandlesticks(self.extend(request, params))
return self.parse_ohlcvs(response, market, timeframe, since, limit)
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical funding rate prices
https://www.gate.com/docs/developers/apiv4/en/#funding-rate-history
:param str symbol: unified symbol of the market to fetch the funding rate history for
:param int [since]: timestamp in ms of the earliest funding rate to fetch
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest funding rate to fetch
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
if paginate:
return await self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params)
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' fetchFundingRateHistory() supports swap contracts only')
request: dict = {}
request, params = self.prepare_request(market, None, params)
if limit is not None:
request['limit'] = limit
if since is not None:
request['from'] = self.parse_to_int(since / 1000)
until = self.safe_integer(params, 'until')
if until is not None:
params = self.omit(params, 'until')
request['to'] = self.parse_to_int(until / 1000)
response = await self.publicFuturesGetSettleFundingRate(self.extend(request, params))
#
# {
# "r": "0.00063521",
# "t": "1621267200000",
# }
#
rates = []
for i in range(0, len(response)):
entry = response[i]
timestamp = self.safe_timestamp(entry, 't')
rates.append({
'info': entry,
'symbol': symbol,
'fundingRate': self.safe_number(entry, 'r'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
})
sorted = self.sort_by(rates, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# Spot market candles
#
# [
# "1660957920", # timestamp
# "6227.070147198573", # quote volume
# "0.0000133485", # close
# "0.0000133615", # high
# "0.0000133347", # low
# "0.0000133468", # open
# "466641934.99" # base volume
# ]
#
#
# Swap, Future, Option, Mark and Index price candles
#
# {
# "t":1632873600, # Unix timestamp in seconds
# "o": "41025", # Open price
# "h": "41882.17", # Highest price
# "c": "41776.92", # Close price
# "l": "40783.94" # Lowest price
# }
#
if isinstance(ohlcv, list):
return [
self.safe_timestamp(ohlcv, 0), # unix timestamp in seconds
self.safe_number(ohlcv, 5), # open price
self.safe_number(ohlcv, 3), # highest price
self.safe_number(ohlcv, 4), # lowest price
self.safe_number(ohlcv, 2), # close price
self.safe_number(ohlcv, 6), # trading volume
]
else:
# Swap, Future, Option, Mark and Index price candles
return [
self.safe_timestamp(ohlcv, 't'), # unix timestamp in seconds
self.safe_number(ohlcv, 'o'), # open price
self.safe_number(ohlcv, 'h'), # highest price
self.safe_number(ohlcv, 'l'), # lowest price
self.safe_number(ohlcv, 'c'), # close price
self.safe_number(ohlcv, 'v'), # trading volume, None for mark or index price
]
async 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://www.gate.com/docs/developers/apiv4/en/#retrieve-market-trades
https://www.gate.com/docs/developers/apiv4/en/#futures-trading-history
https://www.gate.com/docs/developers/apiv4/en/#futures-trading-history-2
https://www.gate.com/docs/developers/apiv4/en/#options-trade-history
: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
:param int [params.until]: timestamp in ms of the latest trade to fetch
: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=public-trades>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
if paginate:
return await self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params)
market = self.market(symbol)
#
# spot
#
# request: Dict = {
# 'currency_pair': market['id'],
# 'limit': limit, # maximum number of records to be returned in a single list
# 'last_id': 'id', # specify list staring point using the id of last record in previous list-query results
# 'reverse': False, # True to retrieve records where id is smaller than the specified last_id, False to retrieve records where id is larger than the specified last_id
# }
#
# swap, future
#
# request: Dict = {
# 'settle': market['settleId'],
# 'contract': market['id'],
# 'limit': limit, # maximum number of records to be returned in a single list
# 'last_id': 'id', # specify list staring point using the id of last record in previous list-query results
# 'from': since / 1000), # starting time in seconds, if not specified, to and limit will be used to limit response items
# 'to': self.seconds(), # end time in seconds, default to current time
# }
#
request, query = self.prepare_request(market, None, params)
until = self.safe_integer_2(params, 'to', 'until')
if until is not None:
params = self.omit(params, ['until'])
request['to'] = self.parse_to_int(until / 1000)
if limit is not None:
request['limit'] = min(limit, 1000) # default 100, max 1000
if since is not None and (market['contract']):
request['from'] = self.parse_to_int(since / 1000)
response = None
if market['type'] == 'spot' or market['type'] == 'margin':
response = await self.publicSpotGetTrades(self.extend(request, query))
elif market['swap']:
response = await self.publicFuturesGetSettleTrades(self.extend(request, query))
elif market['future']:
response = await self.publicDeliveryGetSettleTrades(self.extend(request, query))
elif market['type'] == 'option':
response = await self.publicOptionsGetTrades(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchTrades() not support self market type.')
#
# spot
#
# [
# {
# "id": "1852958144",
# "create_time": "1634673259",
# "create_time_ms": "1634673259378.105000",
# "currency_pair": "ADA_USDT",
# "side": "sell",
# "amount": "307.078",
# "price": "2.104",
# }
# ]
#
# perpetual swap
#
# [
# {
# "size": "2",
# "id": "2522911",
# "create_time_ms": "1634673380.182",
# "create_time": "1634673380.182",
# "contract": "ADA_USDT",
# "price": "2.10486",
# }
# ]
#
# option
#
# [
# {
# "size": -5,
# "id": 25,
# "create_time": 1682378573,
# "contract": "ETH_USDT-20230526-2000-P",
# "price": "209.1"
# }
# ]
#
return self.parse_trades(response, market, since, limit)
async 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://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-2
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-3
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-4
: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>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchOrderTrades() requires a symbol argument')
await self.load_markets()
#
# [
# {
# "id":"3711449544",
# "create_time":"1655486040",
# "create_time_ms":"1655486040177.599900",
# "currency_pair":"SHIB_USDT",
# "side":"buy",
# "role":"taker",
# "amount":"1360039",
# "price":"0.0000081084",
# "order_id":"169717399644",
# "fee":"2720.078",
# "fee_currency":"SHIB",
# "point_fee":"0",
# "gt_fee":"0"
# }
# ]
#
response = await self.fetch_my_trades(symbol, since, limit, {'order_id': id})
return response
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
Fetch personal trading history
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-2
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-3
https://www.gate.com/docs/developers/apiv4/en/#list-personal-trading-history-4
: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 str [params.marginMode]: 'cross' or 'isolated' - marginMode for margin trading if not provided self.options['defaultMarginMode'] is used
:param str [params.type]: 'spot', 'swap', or 'future', if not provided self.options['defaultMarginMode'] is used
:param int [params.until]: The latest timestamp, in ms, that fetched trades were made
:param int [params.page]: *spot only* Page number
:param str [params.order_id]: *spot only* Filter trades with specified order ID. symbol is also required if self field is present
:param str [params.order]: *contract only* Futures order ID, return related data only if specified
:param int [params.offset]: *contract only* list offset, starting from 0
:param str [params.last_id]: *contract only* specify list staring point using the id of last record in previous list-query results
:param int [params.count_total]: *contract only* whether to return total number matched, default to 0(no return)
:param bool [params.unifiedAccount]: set to True for fetching trades in a unified account
:param bool [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
await self.load_unified_status()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
if paginate:
return await self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
type = None
marginMode = None
request: dict = {}
market = self.market(symbol) if (symbol is not None) else None
until = self.safe_integer(params, 'until')
params = self.omit(params, ['until'])
type, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
contract = (type == 'swap') or (type == 'future') or (type == 'option')
if contract:
request, params = self.prepare_request(market, type, params)
if type == 'option':
params = self.omit(params, 'order_id')
else:
if market is not None:
request['currency_pair'] = market['id'] # Should always be set for non-trigger
marginMode, params = self.get_margin_mode(False, params)
request['account'] = marginMode
if limit is not None:
request['limit'] = limit # default 100, max 1000
if since is not None:
request['from'] = self.parse_to_int(since / 1000)
if until is not None:
request['to'] = self.parse_to_int(until / 1000)
response = None
if type == 'spot' or type == 'margin':
response = await self.privateSpotGetMyTrades(self.extend(request, params))
elif type == 'swap':
response = await self.privateFuturesGetSettleMyTradesTimerange(self.extend(request, params))
elif type == 'future':
response = await self.privateDeliveryGetSettleMyTrades(self.extend(request, params))
elif type == 'option':
response = await self.privateOptionsGetMyTrades(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchMyTrades() not support self market type.')
#
# spot
#
# [
# {
# "id": "2876130500",
# "create_time": "1645464610",
# "create_time_ms": "1645464610777.399200",
# "currency_pair": "DOGE_USDT",
# "side": "sell",
# "role": "taker",
# "amount": "10.97",
# "price": "0.137384",
# "order_id": "125924049993",
# "fee": "0.00301420496",
# "fee_currency": "USDT",
# "point_fee": "0",
# "gt_fee": "0"
# }
# ]
#
# perpetual swap
#
# [
# {
# "size": -5,
# "order_id": "130264979823",
# "id": 26884791,
# "role": "taker",
# "create_time": 1645465199.5472,
# "contract": "DOGE_USDT",
# "price": "0.136888"
# }
# ]
#
# future
#
# [
# {
# "id": 121234231,
# "create_time": 1514764800.123,
# "contract": "BTC_USDT",
# "order_id": "21893289839",
# "size": 100,
# "price": "100.123",
# "role": "taker"
# }
# ]
#
# option
#
# [
# {
# "underlying_price": "26817.84",
# "size": -1,
# "contract": "BTC_USDT-20230602-26500-C",
# "id": 16,
# "role": "taker",
# "create_time": 1685594770,
# "order_id": 2611026125,
# "price": "333"
# }
# ]
#
return self.parse_trades(response, market, since, limit)
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
#
# public
#
# spot:
# {
# "id": "1334253759",
# "create_time": "1626342738",
# "create_time_ms": "1626342738331.497000",
# "currency_pair": "BTC_USDT",
# "side": "sell",
# "amount": "0.0022",
# "price": "32452.16"
# }
#
# swap:
#
# {
# "id": "442288327",
# "contract": "BTC_USDT",
# "create_time": "1739814676.707",
# "create_time_ms": "1739814676.707",
# "size": "-105",
# "price": "95594.8"
# }
#
#
# public ws
#
# {
# "id": 221994511,
# "time": 1580311438.618647,
# "price": "9309",
# "amount": "0.0019",
# "type": "sell"
# }
#
# spot rest
#
# {
# "id": "2876130500",
# "create_time": "1645464610",
# "create_time_ms": "1645464610777.399200",
# "currency_pair": "DOGE_USDT",
# "side": "sell",
# "role": "taker",
# "amount": "10.97",
# "price": "0.137384",
# "order_id": "125924049993",
# "fee": "0.00301420496",
# "fee_currency": "USDT",
# "point_fee": "1.1",
# "gt_fee":"2.2"
# }
#
# perpetual swap rest
#
# {
# "size": -5,
# "order_id": "130264979823",
# "id": 26884791,
# "role": "taker",
# "create_time": 1645465199.5472,
# "contract": "DOGE_USDT",
# "price": "0.136888"
# }
#
# future rest
#
# {
# "id": 121234231,
# "create_time": 1514764800.123,
# "contract": "BTC_USDT",
# "order_id": "21893289839",
# "size": 100,
# "price": "100.123",
# "role": "taker"
# }
#
# fetchTrades: option
#
# {
# "size": -5,
# "id": 25,
# "create_time": 1682378573,
# "contract": "ETH_USDT-20230526-2000-P",
# "price": "209.1"
# }
#
# fetchMyTrades: option
#
# {
# "underlying_price": "26817.84",
# "size": -1,
# "contract": "BTC_USDT-20230602-26500-C",
# "id": 16,
# "role": "taker",
# "create_time": 1685594770,
# "order_id": 2611026125,
# "price": "333"
# }
#
id = self.safe_string_2(trade, 'id', 'trade_id')
timestamp: Int = None
msString = self.safe_string(trade, 'create_time_ms')
if msString is not None:
msString = Precise.string_mul(msString, '1000')
msString = msString[0:13]
timestamp = self.parse_to_int(msString)
else:
timestamp = self.safe_timestamp_2(trade, 'time', 'create_time')
marketId = self.safe_string_2(trade, 'currency_pair', 'contract')
marketType = 'contract' if ('contract' in trade) else 'spot'
market = self.safe_market(marketId, market, '_', marketType)
amountString = self.safe_string_2(trade, 'amount', 'size')
priceString = self.safe_string(trade, 'price')
contractSide = 'sell' if Precise.string_lt(amountString, '0') else 'buy'
amountString = Precise.string_abs(amountString)
side = self.safe_string_2(trade, 'side', 'type', contractSide)
orderId = self.safe_string(trade, 'order_id')
feeAmount = self.safe_string(trade, 'fee')
gtFee = self.omit_zero(self.safe_string(trade, 'gt_fee'))
pointFee = self.omit_zero(self.safe_string(trade, 'point_fee'))
fees = []
if feeAmount is not None:
feeCurrencyId = self.safe_string(trade, 'fee_currency')
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
if feeCurrencyCode is None:
feeCurrencyCode = self.safe_string(market, 'settle')
fees.append({
'cost': feeAmount,
'currency': feeCurrencyCode,
})
if gtFee is not None:
fees.append({
'cost': gtFee,
'currency': 'GT',
})
if pointFee is not None:
fees.append({
'cost': pointFee,
'currency': 'GatePoint',
})
takerOrMaker = self.safe_string(trade, 'role')
return self.safe_trade({
'info': trade,
'id': id,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': market['symbol'],
'order': orderId,
'type': None,
'side': side,
'takerOrMaker': takerOrMaker,
'price': priceString,
'amount': amountString,
'cost': None,
'fee': None,
'fees': fees,
}, market)
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all deposits made to an account
https://www.gate.com/docs/developers/apiv4/en/#retrieve-deposit-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
:param int [params.until]: end time 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 dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
if paginate:
return await self.fetch_paginated_call_dynamic('fetchDeposits', code, since, limit, params)
request: dict = {}
currency = None
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id'] # todo: currencies have network-junctions
if limit is not None:
request['limit'] = limit
if since is not None:
start = self.parse_to_int(since / 1000)
request['from'] = start
request['to'] = self.sum(start, 30 * 24 * 60 * 60)
request, params = self.handle_until_option('to', request, params, 0.001)
response = await self.privateWalletGetDeposits(self.extend(request, params))
return self.parse_transactions(response, currency)
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all withdrawals made from an account
https://www.gate.com/docs/developers/apiv4/en/#retrieve-withdrawal-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
:param int [params.until]: end time 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 dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
if paginate:
return await self.fetch_paginated_call_dynamic('fetchWithdrawals', code, since, limit, params)
request: dict = {}
currency = None
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id'] # todo: currencies have network-junctions
if limit is not None:
request['limit'] = limit
if since is not None:
start = self.parse_to_int(since / 1000)
request['from'] = start
request['to'] = self.sum(start, 30 * 24 * 60 * 60)
request, params = self.handle_until_option('to', request, params, 0.001)
response = await self.privateWalletGetWithdrawals(self.extend(request, params))
return self.parse_transactions(response, currency)
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
"""
make a withdrawal
https://www.gate.com/docs/developers/apiv4/en/#withdraw
: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)
await self.load_markets()
currency = self.currency(code)
request: dict = {
'currency': currency['id'], # todo: currencies have network-junctions
'address': address,
'amount': self.currency_to_precision(code, amount),
}
if tag is not None:
request['memo'] = tag
networkCode = None
networkCode, params = self.handle_network_code_and_params(params)
if networkCode is not None:
request['chain'] = self.network_code_to_id(networkCode)
response = await self.privateWithdrawalsPostWithdrawals(self.extend(request, params))
#
# {
# "id": "w13389675",
# "currency": "USDT",
# "amount": "50",
# "address": "TUu2rLFrmzUodiWfYki7QCNtv1akL682p1",
# "memo": null
# }
#
return self.parse_transaction(response, currency)
def parse_transaction_status(self, status: Str):
statuses: dict = {
'PEND': 'pending',
'REQUEST': 'pending',
'DMOVE': 'pending',
'MANUAL': 'pending',
'VERIFY': 'pending',
'PROCES': 'pending',
'EXTPEND': 'pending',
'SPLITPEND': 'pending',
'CANCEL': 'canceled',
'FAIL': 'failed',
'INVALID': 'failed',
'DONE': 'ok',
'BCODE': 'ok', # GateCode withdrawal
}
return self.safe_string(statuses, status, status)
def parse_transaction_type(self, type):
types: dict = {
'd': 'deposit',
'w': 'withdrawal',
}
return self.safe_string(types, type, type)
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
#
# fetchDeposits
#
# {
# "id": "d33361395",
# "currency": "USDT_TRX",
# "address": "TErdnxenuLtXfnMafLbfappYdHtnXQ5U4z",
# "amount": "100",
# "txid": "ae9374de34e558562fe18cbb1bf9ab4d9eb8aa7669d65541c9fa2a532c1474a0",
# "timestamp": "1626345819",
# "status": "DONE",
# "memo": ""
# }
#
# withdraw
#
# {
# "id":"w64413318",
# "currency":"usdt",
# "amount":"10150",
# "address":"0x0ab891497116f7f5532a4c2f4f7b1784488628e1",
# "memo":null,
# "status":"REQUEST",
# "chain":"eth",
# "withdraw_order_id":"",
# "fee_amount":"4.15000000"
# }
#
# fetchWithdrawals
#
# {
# "id": "210496",
# "timestamp": "1542000000",
# "withdraw_order_id": "order_123456",
# "currency": "USDT",
# "address": "1HkxtBAMrA3tP5ENnYY2CZortjZvFDH5Cs",
# "txid": "128988928203223323290",
# "block_number": "41575382",
# "amount": "222.61",
# "fee": "0.01",
# "memo": "",
# "status": "DONE",
# "chain": "TRX"
# }
#
# {
# "id": "w13389675",
# "currency": "USDT",
# "amount": "50",
# "address": "TUu2rLFrmzUodiWfYki7QCNtv1akL682p1",
# "memo": null
# }
#
# {
# "currency":"usdt",
# "address":"0x01c0A9b7b4CdE774AF0f3E47CB4f1c2CCdBa0806",
# "amount":"1880",
# "chain":"eth"
# }
#
id = self.safe_string(transaction, 'id')
type = None
amountString = self.safe_string(transaction, 'amount')
if id is not None:
if id[0] == 'b':
# GateCode handling
type = 'deposit' if Precise.string_gt(amountString, '0') else 'withdrawal'
amountString = Precise.string_abs(amountString)
else:
type = self.parse_transaction_type(id[0])
feeCostString = self.safe_string_2(transaction, 'fee', 'fee_amount')
if type == 'withdrawal':
amountString = Precise.string_sub(amountString, feeCostString)
networkId = self.safe_string_upper(transaction, 'chain')
currencyId = self.safe_string(transaction, 'currency')
code = self.safe_currency_code(currencyId)
txid = self.safe_string(transaction, 'txid')
rawStatus = self.safe_string(transaction, 'status')
status = self.parse_transaction_status(rawStatus)
address = self.safe_string(transaction, 'address')
tag = self.safe_string(transaction, 'memo')
timestamp = self.safe_timestamp(transaction, 'timestamp')
return {
'info': transaction,
'id': id,
'txid': txid,
'currency': code,
'amount': self.parse_number(amountString),
'network': self.network_id_to_code(networkId),
'address': address,
'addressTo': None,
'addressFrom': None,
'tag': tag,
'tagTo': None,
'tagFrom': None,
'status': status,
'type': type,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'updated': None,
'internal': None,
'comment': None,
'fee': {
'currency': code,
'cost': self.parse_number(feeCostString),
},
}
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
Create an order on the exchange
https://www.gate.com/docs/developers/apiv4/en/#create-an-order
https://www.gate.com/docs/developers/apiv4/en/#create-a-price-triggered-order
https://www.gate.com/docs/developers/apiv4/en/#create-a-futures-order
https://www.gate.com/docs/developers/apiv4/en/#create-a-price-triggered-order-2
https://www.gate.com/docs/developers/apiv4/en/#create-a-futures-order-2
https://www.gate.com/docs/developers/apiv4/en/#create-a-price-triggered-order-3
https://www.gate.com/docs/developers/apiv4/en/#create-an-options-order
:param str symbol: Unified CCXT market symbol
:param str type: 'limit' or 'market' *"market" is contract only*
:param str side: 'buy' or 'sell'
:param float amount: the amount of currency to trade
: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 str [params.timeInForce]: "GTC", "IOC", or "PO"
:param float [params.stopLossPrice]: The price at which a stop loss order is triggered at
:param float [params.takeProfitPrice]: The price at which a take profit order is triggered at
:param str [params.marginMode]: 'cross' or 'isolated' - marginMode for margin trading if not provided self.options['defaultMarginMode'] is used
:param int [params.iceberg]: Amount to display for the iceberg order, Null or 0 for normal orders, Set to -1 to hide the order completely
:param str [params.text]: User defined information
:param str [params.account]: *spot and margin only* "spot", "margin" or "cross_margin"
:param bool [params.auto_borrow]: *margin only* Used in margin or cross margin trading to allow automatic loan of insufficient amount if balance is not enough
:param str [params.settle]: *contract only* Unified Currency Code for settle currency
:param bool [params.reduceOnly]: *contract only* Indicates if self order is to reduce the size of a position
:param bool [params.close]: *contract only* Set to close the position, with size set to 0
:param bool [params.auto_size]: *contract only* Set side to close dual-mode position, close_long closes the long side, while close_short the short one, size also needs to be set to 0
:param int [params.price_type]: *contract only* 0 latest deal price, 1 mark price, 2 index price
:param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
:param bool [params.unifiedAccount]: set to True for creating an order in the unified account
:returns dict|None: `An order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = self.market(symbol)
trigger = self.safe_value(params, 'trigger')
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
stopLossPrice = self.safe_value(params, 'stopLossPrice', triggerPrice)
takeProfitPrice = self.safe_value(params, 'takeProfitPrice')
isStopLossOrder = stopLossPrice is not None
isTakeProfitOrder = takeProfitPrice is not None
isTpsl = isStopLossOrder or isTakeProfitOrder
nonTriggerOrder = not isTpsl and (trigger is None)
orderRequest = self.create_order_request(symbol, type, side, amount, price, params)
response = None
if market['spot'] or market['margin']:
if nonTriggerOrder:
response = await self.privateSpotPostOrders(orderRequest)
else:
response = await self.privateSpotPostPriceOrders(orderRequest)
elif market['swap']:
if nonTriggerOrder:
response = await self.privateFuturesPostSettleOrders(orderRequest)
else:
response = await self.privateFuturesPostSettlePriceOrders(orderRequest)
elif market['future']:
if nonTriggerOrder:
response = await self.privateDeliveryPostSettleOrders(orderRequest)
else:
response = await self.privateDeliveryPostSettlePriceOrders(orderRequest)
else:
response = await self.privateOptionsPostOrders(orderRequest)
# response = await getattr(self, method)(self.deep_extend(request, params))
#
# spot
#
# {
# "id": "95282841887",
# "text": "apiv4",
# "create_time": "1637383156",
# "update_time": "1637383156",
# "create_time_ms": 1637383156017,
# "update_time_ms": 1637383156017,
# "status": "open",
# "currency_pair": "ETH_USDT",
# "type": "limit",
# "account": "spot",
# "side": "buy",
# "amount": "0.01",
# "price": "3500",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "0.01",
# "fill_price": "0",
# "filled_total": "0",
# "fee": "0",
# "fee_currency": "ETH",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_discount": False,
# "rebated_fee": "0",
# "rebated_fee_currency": "USDT"
# }
#
# spot conditional
#
# {"id": 5891843}
#
# futures, perpetual swaps and options
#
# {
# "id": 95938572327,
# "contract": "ETH_USDT",
# "mkfr": "0",
# "tkfr": "0.0005",
# "tif": "gtc",
# "is_reduce_only": False,
# "create_time": 1637384600.08,
# "price": "3000",
# "size": 1,
# "refr": "0",
# "left": 1,
# "text": "api",
# "fill_price": "0",
# "user": 2436035,
# "status": "open",
# "is_liq": False,
# "refu": 0,
# "is_close": False,
# "iceberg": 0
# }
#
# futures and perpetual swaps conditionals
#
# {"id": 7615567}
#
return self.parse_order(response, market)
def create_orders_request(self, orders: List[OrderRequest], params={}):
ordersRequests = []
orderSymbols = []
ordersLength = len(orders)
if ordersLength == 0:
raise BadRequest(self.id + ' createOrders() requires at least one order')
if ordersLength > 10:
raise BadRequest(self.id + ' createOrders() accepts a maximum of 10 orders at a time')
for i in range(0, len(orders)):
rawOrder = orders[i]
marketId = self.safe_string(rawOrder, 'symbol')
orderSymbols.append(marketId)
type = self.safe_string(rawOrder, 'type')
side = self.safe_string(rawOrder, 'side')
amount = self.safe_value(rawOrder, 'amount')
price = self.safe_value(rawOrder, 'price')
orderParams = self.safe_value(rawOrder, 'params', {})
extendedParams = self.extend(orderParams, params) # the request does not accept extra params since it's a list, so we're extending each order with the common params
triggerValue = self.safe_value_n(orderParams, ['triggerPrice', 'stopPrice', 'takeProfitPrice', 'stopLossPrice'])
if triggerValue is not None:
raise NotSupported(self.id + ' createOrders() does not support advanced order properties(stopPrice, takeProfitPrice, stopLossPrice)')
extendedParams['textIsRequired'] = True # the exchange requires a text parameter for each order here
orderRequest = self.create_order_request(marketId, type, side, amount, price, extendedParams)
ordersRequests.append(orderRequest)
symbols = self.market_symbols(orderSymbols, None, False, True, True)
market = self.market(symbols[0])
if market['future'] or market['option']:
raise NotSupported(self.id + ' createOrders() does not support futures or options markets')
return ordersRequests
async def create_orders(self, orders: List[OrderRequest], params={}):
"""
create a list of trade orders
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-order-2
https://www.gate.com/docs/developers/apiv4/en/#create-a-batch-of-orders
https://www.gate.com/docs/developers/apiv4/en/#create-a-batch-of-futures-orders
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
ordersRequests = self.create_orders_request(orders, params)
firstOrder = orders[0]
market = self.market(firstOrder['symbol'])
response = None
if market['spot']:
response = await self.privateSpotPostBatchOrders(ordersRequests)
elif market['swap']:
response = await self.privateFuturesPostSettleBatchOrders(ordersRequests)
return self.parse_orders(response)
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
market = self.market(symbol)
contract = market['contract']
trigger = self.safe_value(params, 'trigger')
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
stopLossPrice = self.safe_value(params, 'stopLossPrice', triggerPrice)
takeProfitPrice = self.safe_value(params, 'takeProfitPrice')
isStopLossOrder = stopLossPrice is not None
isTakeProfitOrder = takeProfitPrice is not None
isTpsl = isStopLossOrder or isTakeProfitOrder
if isStopLossOrder and isTakeProfitOrder:
raise ExchangeError(self.id + ' createOrder() stopLossPrice and takeProfitPrice cannot both be defined')
reduceOnly = self.safe_value(params, 'reduceOnly')
exchangeSpecificTimeInForce = self.safe_string_lower_n(params, ['timeInForce', 'tif', 'time_in_force'])
postOnly = None
postOnly, params = self.handle_post_only(type == 'market', exchangeSpecificTimeInForce == 'poc', params)
timeInForce = self.handle_time_in_force(params)
if postOnly:
timeInForce = 'poc'
# we only omit the unified params here
# self is because the other params will get extended into the request
params = self.omit(params, ['stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'reduceOnly', 'timeInForce', 'postOnly'])
isLimitOrder = (type == 'limit')
isMarketOrder = (type == 'market')
if isLimitOrder and price is None:
raise ArgumentsRequired(self.id + ' createOrder() requires a price argument for ' + type + ' orders')
if isMarketOrder:
if (timeInForce == 'poc') or (timeInForce == 'gtc'):
raise ExchangeError(self.id + ' createOrder() timeInForce for market order can only be "FOK" or "IOC"')
else:
if timeInForce is None:
defaultTif = self.safe_string(self.options, 'defaultTimeInForce', 'IOC')
exchangeSpecificTif = self.safe_string(self.options['timeInForce'], defaultTif, 'ioc')
timeInForce = exchangeSpecificTif
if contract:
price = 0
if contract:
isClose = self.safe_value(params, 'close')
if isClose:
amount = 0
else:
amountToPrecision = self.amount_to_precision(symbol, amount)
signedAmount = Precise.string_neg(amountToPrecision) if (side == 'sell') else amountToPrecision
amount = int(signedAmount)
request = None
nonTriggerOrder = not isTpsl and (trigger is None)
if nonTriggerOrder:
if contract:
# contract order
request = {
'contract': market['id'], # filled in prepareRequest above
'size': amount, # int64, positive = bid, negative = ask
# 'iceberg': 0, # int64, display size for iceberg order, 0 for non-iceberg, note that you will have to pay the taker fee for the hidden size
# 'close': False, # True to close the position, with size set to 0
# 'reduce_only': False, # St to be reduce-only order
# 'tif': 'gtc', # gtc, ioc, poc PendingOrCancelled == postOnly order
# 'text': clientOrderId, # 't-abcdef1234567890',
# 'auto_size': '', # close_long, close_short, note size also needs to be set to 0
}
if not market['option']:
request['settle'] = market['settleId'] # filled in prepareRequest above
if isMarketOrder:
request['price'] = '0' # set to 0 for market orders
else:
request['price'] = '0' if (price == 0) else self.price_to_precision(symbol, price)
if reduceOnly is not None:
request['reduce_only'] = reduceOnly
if timeInForce is not None:
request['tif'] = timeInForce
else:
marginMode = None
marginMode, params = self.get_margin_mode(False, params)
# spot order
request = {
# 'text': clientOrderId, # 't-abcdef1234567890',
'currency_pair': market['id'], # filled in prepareRequest above
'type': type,
'account': marginMode, # spot, margin, cross_margin, unified
'side': side,
# 'time_in_force': 'gtc', # gtc, ioc, poc PendingOrCancelled == postOnly order
# 'iceberg': 0, # amount to display for the iceberg order, null or 0 for normal orders, set to -1 to hide the order completely
# 'auto_borrow': False, # used in margin or cross margin trading to allow automatic loan of insufficient amount if balance is not enough
# 'auto_repay': False, # automatic repayment for automatic borrow loan generated by cross margin order, diabled by default
}
if isMarketOrder and (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:
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)
request['amount'] = quoteAmount
else:
request['amount'] = self.amount_to_precision(symbol, amount)
if isLimitOrder:
request['price'] = self.price_to_precision(symbol, price)
if timeInForce is not None:
request['time_in_force'] = timeInForce
clientOrderId = self.safe_string_2(params, 'text', 'clientOrderId')
textIsRequired = self.safe_bool(params, 'textIsRequired', False)
if clientOrderId is not None:
# user-defined, must follow the rules if not empty
# prefixed with t-
# no longer than 28 bytes without t- prefix
# can only include 0-9, A-Z, a-z, underscores(_), hyphens(-) or dots(.)
if len(clientOrderId) > 28:
raise BadRequest(self.id + ' createOrder() clientOrderId or text param must be up to 28 characters')
params = self.omit(params, ['text', 'clientOrderId', 'textIsRequired'])
if clientOrderId[0] != 't':
clientOrderId = 't-' + clientOrderId
request['text'] = clientOrderId
else:
if textIsRequired:
# batchOrders requires text in the request
request['text'] = 't-' + self.uuid16()
else:
if market['option']:
raise NotSupported(self.id + ' createOrder() conditional option orders are not supported')
if contract:
# contract conditional order
request = {
'initial': {
'contract': market['id'],
'size': amount, # positive = buy, negative = sell, set to 0 to close the position
# 'price': '0' if (price == 0) else self.price_to_precision(symbol, price), # set to 0 to use market price
# 'close': False, # set to True if trying to close the position
# 'tif': 'gtc', # gtc, ioc, if using market price, only ioc is supported
# 'text': clientOrderId, # web, api, app
# 'reduce_only': False,
},
'settle': market['settleId'],
}
if type == 'market':
request['initial']['price'] = '0'
else:
request['initial']['price'] = '0' if (price == 0) else self.price_to_precision(symbol, price)
if trigger is None:
rule = None
triggerOrderPrice = None
if isStopLossOrder:
# we trigger orders be aliases for stopLoss orders because
# gateio doesn't accept conventional trigger orders for spot markets
rule = 1 if (side == 'buy') else 2
triggerOrderPrice = self.price_to_precision(symbol, stopLossPrice)
elif isTakeProfitOrder:
rule = 2 if (side == 'buy') else 1
triggerOrderPrice = self.price_to_precision(symbol, takeProfitPrice)
priceType = self.safe_integer(params, 'price_type', 0)
if priceType < 0 or priceType > 2:
raise BadRequest(self.id + ' createOrder() price_type should be 0 latest deal price, 1 mark price, 2 index price')
params = self.omit(params, ['price_type'])
request['trigger'] = {
# 'strategy_type': 0, # 0 = by price, 1 = by price gap, only 0 is supported currently
'price_type': priceType, # 0 latest deal price, 1 mark price, 2 index price
'price': self.price_to_precision(symbol, triggerOrderPrice), # price or gap
'rule': rule, # 1 means price_type >= price, 2 means price_type <= price
# 'expiration': expiration, how many seconds to wait for the condition to be triggered before cancelling the order
}
if reduceOnly is not None:
request['initial']['reduce_only'] = reduceOnly
if timeInForce is not None:
request['initial']['tif'] = timeInForce
else:
# spot conditional order
options = self.safe_value(self.options, 'createOrder', {})
marginMode = None
marginMode, params = self.get_margin_mode(True, params)
if timeInForce is None:
timeInForce = 'gtc'
request = {
'put': {
'type': type,
'side': side,
'price': self.price_to_precision(symbol, price),
'amount': self.amount_to_precision(symbol, amount),
'account': marginMode,
'time_in_force': timeInForce, # gtc, ioc(ioc is for taker only, so shouldnt't be in conditional order)
},
'market': market['id'],
}
if trigger is None:
defaultExpiration = self.safe_integer(options, 'expiration')
expiration = self.safe_integer(params, 'expiration', defaultExpiration)
rule = None
triggerOrderPrice = None
if isStopLossOrder:
# we trigger orders be aliases for stopLoss orders because
# gateio doesn't accept conventional trigger orders for spot markets
rule = '>=' if (side == 'buy') else '<='
triggerOrderPrice = self.price_to_precision(symbol, stopLossPrice)
elif isTakeProfitOrder:
rule = '<=' if (side == 'buy') else '>='
triggerOrderPrice = self.price_to_precision(symbol, takeProfitPrice)
request['trigger'] = {
'price': self.price_to_precision(symbol, triggerOrderPrice),
'rule': rule, # >= triggered when market price larger than or equal to price field, <= triggered when market price less than or equal to price field
'expiration': expiration, # required, how long(in seconds) to wait for the condition to be triggered before cancelling the order
}
return self.extend(request, params)
async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
"""
create a market buy order by providing the symbol and cost
https://www.gate.com/docs/developers/apiv4/en/#create-an-order
:param str symbol: unified symbol of the market to create an order in
:param float cost: how much you want to trade in units of the quote currency
:param dict [params]: extra parameters specific to the exchange API endpoint
:param bool [params.unifiedAccount]: set to True for creating a unified account order
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = self.market(symbol)
if not market['spot']:
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
params['createMarketBuyOrderRequiresPrice'] = False
return await self.create_order(symbol, 'market', 'buy', cost, None, params)
def edit_order_request(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
market = self.market(symbol)
marketType = None
marketType, params = self.handle_market_type_and_params('editOrder', market, params)
account = self.convert_type_to_account(marketType)
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'editOrder', 'unifiedAccount')
if isUnifiedAccount:
account = 'unified'
isLimitOrder = (type == 'limit')
if account == 'spot':
if not isLimitOrder:
# exchange doesn't have market orders for spot
raise InvalidOrder(self.id + ' editOrder() does not support ' + type + ' orders for ' + marketType + ' markets')
request: dict = {
'order_id': str(id),
'currency_pair': market['id'],
'account': account,
}
if amount is not None:
if market['spot']:
request['amount'] = self.amount_to_precision(symbol, amount)
else:
if side == 'sell':
request['size'] = self.parse_to_numeric(Precise.string_neg(self.amount_to_precision(symbol, amount)))
else:
request['size'] = self.parse_to_numeric(self.amount_to_precision(symbol, amount))
if price is not None:
request['price'] = self.price_to_precision(symbol, price)
if not market['spot']:
request['settle'] = market['settleId']
return self.extend(request, params)
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
"""
edit a trade order, gate currently only supports the modification of the price or amount fields
https://www.gate.com/docs/developers/apiv4/en/#amend-an-order
https://www.gate.com/docs/developers/apiv4/en/#amend-an-order-2
: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 bool [params.unifiedAccount]: set to True for editing an order in a unified account
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = self.market(symbol)
extendedRequest = self.edit_order_request(id, symbol, type, side, amount, price, params)
response = None
if market['spot']:
response = await self.privateSpotPatchOrdersOrderId(extendedRequest)
else:
response = await self.privateFuturesPutSettleOrdersOrderId(extendedRequest)
#
# {
# "id": "243233276443",
# "text": "apiv4",
# "create_time": "1670908873",
# "update_time": "1670914102",
# "create_time_ms": 1670908873077,
# "update_time_ms": 1670914102241,
# "status": "open",
# "currency_pair": "ADA_USDT",
# "type": "limit",
# "account": "spot",
# "side": "sell",
# "amount": "10",
# "price": "0.6",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "10",
# "fill_price": "0",
# "filled_total": "0",
# "fee": "0",
# "fee_currency": "USDT",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_maker_fee": "0",
# "gt_taker_fee": "0",
# "gt_discount": False,
# "rebated_fee": "0",
# "rebated_fee_currency": "ADA"
# }
#
return self.parse_order(response, market)
def parse_order_status(self, status: Str):
statuses: dict = {
'open': 'open',
'_new': 'open',
'filled': 'closed',
'cancelled': 'canceled',
'liquidated': 'closed',
'ioc': 'canceled',
'failed': 'canceled',
'expired': 'canceled',
'finished': 'closed',
'finish': 'closed',
'succeeded': 'closed',
}
return self.safe_string(statuses, status, status)
def parse_order(self, order: dict, market: Market = None) -> Order:
#
# SPOT
# createOrder/cancelOrder/fetchOrder/editOrder
#
# {
# "id": "62364648575",
# "text": "apiv4",
# "create_time": "1626354834",
# "update_time": "1626354834",
# "create_time_ms": "1626354833544",
# "update_time_ms": "1626354833544",
# "status": "open",
# "currency_pair": "BTC_USDT",
# "type": "limit",
# "account": "spot",
# "side": "buy",
# "amount": "0.0001",
# "price": "30000",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "0.0001",
# "fill_price": "0",
# "filled_total": "0",
# "fee": "0",
# "fee_currency": "BTC",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_discount": True,
# "rebated_fee": "0",
# "rebated_fee_currency": "USDT"
# }
#
# SPOT TRIGGER ORDERS
# createOrder
#
# {
# "id": 12604556
# }
#
# fetchOrder/cancelOrder
#
# {
# "market": "ADA_USDT",
# "user": 6392049,
# "trigger": {
# "price": "1.08", # stopPrice
# "rule": "\u003e=",
# "expiration": 86400
# },
# "put": {
# "type": "limit",
# "side": "buy",
# "price": "1.08", # order price
# "amount": "1.00000000000000000000",
# "account": "normal",
# "time_in_force": "gtc"
# },
# "id": 71639298,
# "ctime": 1643945985,
# "status": "open"
# }
#
# FUTURE, SWAP AND OPTION
# createOrder/cancelOrder/fetchOrder
#
# {
# "id": 123028481731,
# "contract": "ADA_USDT",
# "mkfr": "-0.00005",
# "tkfr": "0.00048",
# "tif": "ioc",
# "is_reduce_only": False,
# "create_time": 1643950262.68,
# "finish_time": 1643950262.68,
# "price": "0",
# "size": 1,
# "refr": "0",
# "left":0,
# "text": "api",
# "fill_price": "1.05273",
# "user":6329238,
# "finish_as": "filled",
# "status": "finished",
# "is_liq": False,
# "refu":0,
# "is_close": False,
# "iceberg": 0
# }
#
# TRIGGER ORDERS(FUTURE AND SWAP)
# createOrder
#
# {
# "id": 12604556
# }
#
# fetchOrder/cancelOrder
#
# {
# "user": 6320300,
# "trigger": {
# "strategy_type": 0,
# "price_type": 0,
# "price": "1.03", # stopPrice
# "rule": 2,
# "expiration": 0
# },
# "initial": {
# "contract": "ADA_USDT",
# "size": -1,
# "price": "1.02",
# "tif": "gtc",
# "text": "",
# "iceberg": 0,
# "is_close": False,
# "is_reduce_only": False,
# "auto_size": ""
# },
# "id": 126393906,
# "trade_id": 0,
# "status": "open",
# "reason": "",
# "create_time": 1643953482,
# "finish_time": 1643953482,
# "is_stop_order": False,
# "stop_trigger": {
# "rule": 0,
# "trigger_price": "",
# "order_price": ""
# },
# "me_order_id": 0,
# "order_type": ""
# }
#
# {
# "text": "t-d18baf9ac44d82e2",
# "succeeded": False,
# "label": "BALANCE_NOT_ENOUGH",
# "message": "Not enough balance"
# }
#
# {"user_id":10406147,"id":"id","succeeded":false,"message":"INVALID_PROTOCOL","label":"INVALID_PROTOCOL"}
#
succeeded = self.safe_bool(order, 'succeeded', True)
if not succeeded:
# cancelOrders response
return self.safe_order({
'clientOrderId': self.safe_string(order, 'text'),
'info': order,
'status': 'rejected',
'id': self.safe_string(order, 'id'),
})
put = self.safe_value_2(order, 'put', 'initial', {})
trigger = self.safe_value(order, 'trigger', {})
contract = self.safe_string(put, 'contract')
type = self.safe_string(put, 'type')
timeInForce = self.safe_string_upper_2(put, 'time_in_force', 'tif')
amount = self.safe_string_2(put, 'amount', 'size')
side = self.safe_string(put, 'side')
price = self.safe_string(put, 'price')
contract = self.safe_string(order, 'contract', contract)
type = self.safe_string(order, 'type', type)
timeInForce = self.safe_string_upper_2(order, 'time_in_force', 'tif', timeInForce)
if timeInForce == 'POC':
timeInForce = 'PO'
postOnly = (timeInForce == 'PO')
amount = self.safe_string_2(order, 'amount', 'size', amount)
side = self.safe_string(order, 'side', side)
price = self.safe_string(order, 'price', price)
remainingString = self.safe_string(order, 'left')
cost = self.safe_string(order, 'filled_total')
triggerPrice = self.safe_number(trigger, 'price')
average = self.safe_number_2(order, 'avg_deal_price', 'fill_price')
if triggerPrice:
remainingString = amount
cost = '0'
if contract:
isMarketOrder = Precise.string_equals(price, '0') and (timeInForce == 'IOC')
type = 'market' if isMarketOrder else 'limit'
side = 'buy' if Precise.string_gt(amount, '0') else 'sell'
rawStatus = self.safe_string_n(order, ['finish_as', 'status', 'open'])
timestamp = self.safe_integer(order, 'create_time_ms')
if timestamp is None:
timestamp = self.safe_timestamp_2(order, 'create_time', 'ctime')
lastTradeTimestamp = self.safe_integer(order, 'update_time_ms')
if lastTradeTimestamp is None:
lastTradeTimestamp = self.safe_timestamp_2(order, 'update_time', 'finish_time')
marketType = 'contract'
if ('currency_pair' in order) or ('market' in order):
marketType = 'spot'
exchangeSymbol = self.safe_string_2(order, 'currency_pair', 'market', contract)
symbol = self.safe_symbol(exchangeSymbol, market, '_', marketType)
# Everything below self(above return) is related to fees
fees = []
gtFee = self.safe_string(order, 'gt_fee')
if gtFee is not None:
fees.append({
'currency': 'GT',
'cost': gtFee,
})
fee = self.safe_string(order, 'fee')
if fee is not None:
fees.append({
'currency': self.safe_currency_code(self.safe_string(order, 'fee_currency')),
'cost': fee,
})
rebate = self.safe_string(order, 'rebated_fee')
if rebate is not None:
fees.append({
'currency': self.safe_currency_code(self.safe_string(order, 'rebated_fee_currency')),
'cost': Precise.string_neg(rebate),
})
numFeeCurrencies = len(fees)
multipleFeeCurrencies = numFeeCurrencies > 1
status = self.parse_order_status(rawStatus)
remaining = Precise.string_abs(remainingString)
# handle spot market buy
account = self.safe_string(order, 'account') # using self instead of market type because of the conflicting ids
if account == 'spot':
averageString = self.safe_string(order, 'avg_deal_price')
average = self.parse_number(averageString)
if (type == 'market') and (side == 'buy'):
remaining = Precise.string_div(remainingString, averageString)
price = None # arrives
cost = amount
amount = Precise.string_div(amount, averageString)
return self.safe_order({
'id': self.safe_string(order, 'id'),
'clientOrderId': self.safe_string(order, 'text'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'reduceOnly': self.safe_value(order, 'is_reduce_only'),
'side': side,
'price': price,
'triggerPrice': triggerPrice,
'average': average,
'amount': Precise.string_abs(amount),
'cost': Precise.string_abs(cost),
'filled': None,
'remaining': remaining,
'fee': None if multipleFeeCurrencies else self.safe_value(fees, 0),
'fees': fees if multipleFeeCurrencies else [],
'trades': None,
'info': order,
}, market)
def fetch_order_request(self, id: str, symbol: Str = None, params={}):
market = None if (symbol is None) else self.market(symbol)
trigger = self.safe_bool_n(params, ['trigger', 'is_stop_order', 'stop'], False)
params = self.omit(params, ['is_stop_order', 'stop', 'trigger'])
clientOrderId = self.safe_string_2(params, 'text', 'clientOrderId')
orderId = id
if clientOrderId is not None:
params = self.omit(params, ['text', 'clientOrderId'])
if clientOrderId[0] != 't':
clientOrderId = 't-' + clientOrderId
orderId = clientOrderId
type, query = self.handle_market_type_and_params('fetchOrder', market, params)
contract = (type == 'swap') or (type == 'future') or (type == 'option')
request, requestParams = self.prepare_request(market, type, query) if contract else self.spot_order_prepare_request(market, trigger, query)
request['order_id'] = str(orderId)
return [request, requestParams]
async def fetch_order(self, id: str, symbol: Str = None, params={}):
"""
Retrieves information on an order
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-order
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-order-2
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-order-3
https://www.gate.com/docs/developers/apiv4/en/#get-a-single-order-4
:param str id: Order id
:param str symbol: Unified market symbol, *required for spot and margin*
:param dict [params]: Parameters specified by the exchange api
:param bool [params.trigger]: True if the order being fetched is a trigger order
:param str [params.marginMode]: 'cross' or 'isolated' - marginMode for margin trading if not provided self.options['defaultMarginMode'] is used
:param str [params.type]: 'spot', 'swap', or 'future', if not provided self.options['defaultMarginMode'] is used
:param str [params.settle]: 'btc' or 'usdt' - settle currency for perpetual swap and future - market settle currency is used if symbol is not None, default="usdt" for swap and "btc" for future
:param bool [params.unifiedAccount]: set to True for fetching a unified account order
:returns: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = None if (symbol is None) else self.market(symbol)
result = self.handle_market_type_and_params('fetchOrder', market, params)
type = self.safe_string(result, 0)
trigger = self.safe_bool_n(params, ['trigger', 'is_stop_order', 'stop'], False)
request, requestParams = self.fetch_order_request(id, symbol, params)
response = None
if type == 'spot' or type == 'margin':
if trigger:
response = await self.privateSpotGetPriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateSpotGetOrdersOrderId(self.extend(request, requestParams))
elif type == 'swap':
if trigger:
response = await self.privateFuturesGetSettlePriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateFuturesGetSettleOrdersOrderId(self.extend(request, requestParams))
elif type == 'future':
if trigger:
response = await self.privateDeliveryGetSettlePriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateDeliveryGetSettleOrdersOrderId(self.extend(request, requestParams))
elif type == 'option':
response = await self.privateOptionsGetOrdersOrderId(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' fetchOrder() not support self market type')
return self.parse_order(response, market)
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently open orders
https://www.gate.com/docs/developers/apiv4/en/#list-all-open-orders
https://www.gate.com/docs/developers/apiv4/en/#retrieve-running-auto-order-list
: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 bool [params.trigger]: True for fetching trigger orders
:param str [params.type]: spot, margin, swap or future, if not provided self.options['defaultType'] is used
:param str [params.marginMode]: 'cross' or 'isolated' - marginMode for type='margin', if not provided self.options['defaultMarginMode'] is used
:param bool [params.unifiedAccount]: set to True for fetching unified account orders
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
return await self.fetch_orders_by_status('open', symbol, since, limit, params)
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetches information on multiple closed orders made by the user
https://www.gate.com/docs/developers/apiv4/en/#list-orders
https://www.gate.com/docs/developers/apiv4/en/#retrieve-running-auto-order-list
https://www.gate.com/docs/developers/apiv4/en/#list-futures-orders
https://www.gate.com/docs/developers/apiv4/en/#list-all-auto-orders
https://www.gate.com/docs/developers/apiv4/en/#list-futures-orders-2
https://www.gate.com/docs/developers/apiv4/en/#list-all-auto-orders-2
https://www.gate.com/docs/developers/apiv4/en/#list-options-orders
https://www.gate.com/docs/developers/apiv4/en/#list-futures-orders-by-time-range
: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 bool [params.trigger]: True for fetching trigger orders
:param str [params.type]: spot, swap or future, if not provided self.options['defaultType'] is used
:param str [params.marginMode]: 'cross' or 'isolated' - marginMode for margin trading if not provided self.options['defaultMarginMode'] is used
:param boolean [params.historical]: *swap only* True for using historical endpoint
:param bool [params.unifiedAccount]: set to True for fetching unified account orders
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
until = self.safe_integer(params, 'until')
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
res = self.handle_market_type_and_params('fetchClosedOrders', market, params)
type = self.safe_string(res, 0)
useHistorical = False
useHistorical, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'historical', False)
if not useHistorical and ((since is None and until is None) or (type != 'swap')):
return await self.fetch_orders_by_status('finished', symbol, since, limit, params)
params = self.omit(params, 'type')
request = {}
request, params = self.prepare_request(market, type, params)
if since is not None:
request['from'] = self.parse_to_int(since / 1000)
if until is not None:
params = self.omit(params, 'until')
request['to'] = self.parse_to_int(until / 1000)
if limit is not None:
request['limit'] = limit
response = await self.privateFuturesGetSettleOrdersTimerange(self.extend(request, params))
return self.parse_orders(response, market, since, limit)
def prepare_orders_by_status_request(self, status, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
trigger: Bool = None
trigger, params = self.handle_param_bool_2(params, 'trigger', 'stop')
type: Str = None
type, params = self.handle_market_type_and_params('fetchOrdersByStatus', market, params)
spot = (type == 'spot') or (type == 'margin')
request: dict = {}
request, params = self.multi_order_spot_prepare_request(market, trigger, params) if spot else self.prepare_request(market, type, params)
if spot and trigger:
request = self.omit(request, 'account')
if status == 'closed':
status = 'finished'
request['status'] = status
if limit is not None:
request['limit'] = limit
if spot:
if since is not None:
request['from'] = self.parse_to_int(since / 1000)
until = self.safe_integer(params, 'until')
if until is not None:
params = self.omit(params, 'until')
request['to'] = self.parse_to_int(until / 1000)
lastId, finalParams = self.handle_param_string_2(params, 'lastId', 'last_id')
if lastId is not None:
request['last_id'] = lastId
return [request, finalParams]
async def fetch_orders_by_status(self, status, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
await self.load_markets()
await self.load_unified_status()
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
# don't omit here, omits done in prepareOrdersByStatusRequest
trigger: Bool = self.safe_bool_2(params, 'trigger', 'stop')
res = self.handle_market_type_and_params('fetchOrdersByStatus', market, params)
type = self.safe_string(res, 0)
request, requestParams = self.prepare_orders_by_status_request(status, symbol, since, limit, params)
spot = (type == 'spot') or (type == 'margin')
openStatus = (status == 'open')
openSpotOrders = spot and openStatus and not trigger
response = None
if spot:
if not trigger:
if openStatus:
response = await self.privateSpotGetOpenOrders(self.extend(request, requestParams))
else:
response = await self.privateSpotGetOrders(self.extend(request, requestParams))
else:
response = await self.privateSpotGetPriceOrders(self.extend(request, requestParams))
elif type == 'swap':
if trigger:
response = await self.privateFuturesGetSettlePriceOrders(self.extend(request, requestParams))
else:
response = await self.privateFuturesGetSettleOrders(self.extend(request, requestParams))
elif type == 'future':
if trigger:
response = await self.privateDeliveryGetSettlePriceOrders(self.extend(request, requestParams))
else:
response = await self.privateDeliveryGetSettleOrders(self.extend(request, requestParams))
elif type == 'option':
response = await self.privateOptionsGetOrders(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' fetchOrders() not support self market type')
#
# spot open orders
#
# [
# {
# "currency_pair": "ADA_USDT",
# "total": 2,
# "orders": [
# {
# "id": "155498539874",
# "text": "apiv4",
# "create_time": "1652406843",
# "update_time": "1652406843",
# "create_time_ms": 1652406843295,
# "update_time_ms": 1652406843295,
# "status": "open",
# "currency_pair": "ADA_USDT",
# "type": "limit",
# "account": "spot",
# "side": "buy",
# "amount": "3",
# "price": "0.35",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "3",
# "fill_price": "0",
# "filled_total": "0",
# "fee": "0",
# "fee_currency": "ADA",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_discount": False,
# "rebated_fee": "0",
# "rebated_fee_currency": "USDT"
# },
# ...
# ]
# },
# ...
# ]
#
# spot
#
# [
# {
# "id": "8834234273",
# "text": "3",
# "create_time": "1635406193",
# "update_time": "1635406193",
# "create_time_ms": 1635406193361,
# "update_time_ms": 1635406193361,
# "status": "closed",
# "currency_pair": "BTC_USDT",
# "type": "limit",
# "account": "spot", # margin for margin orders
# "side": "sell",
# "amount": "0.0002",
# "price": "58904.01",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "0.0000",
# "fill_price": "11.790516",
# "filled_total": "11.790516",
# "fee": "0.023581032",
# "fee_currency": "USDT",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_discount": False,
# "rebated_fee_currency": "BTC"
# }
# ]
#
# spot trigger
#
# [
# {
# "market": "ADA_USDT",
# "user": 10406147,
# "trigger": {
# "price": "0.65",
# "rule": "\u003c=",
# "expiration": 86400
# },
# "put": {
# "type": "limit",
# "side": "sell",
# "price": "0.65",
# "amount": "2.00000000000000000000",
# "account": "normal", # margin for margin orders
# "time_in_force": "gtc"
# },
# "id": 8449909,
# "ctime": 1652188982,
# "status": "open"
# }
# ]
#
# swap
#
# [
# {
# "status": "finished",
# "size": -1,
# "left": 0,
# "id": 82750739203,
# "is_liq": False,
# "is_close": False,
# "contract": "BTC_USDT",
# "text": "web",
# "fill_price": "60721.3",
# "finish_as": "filled",
# "iceberg": 0,
# "tif": "ioc",
# "is_reduce_only": True,
# "create_time": 1635403475.412,
# "finish_time": 1635403475.4127,
# "price": "0"
# }
# ]
#
# option
#
# [
# {
# "id": 2593450699,
# "contract": "BTC_USDT-20230601-27500-C",
# "mkfr": "0.0003",
# "tkfr": "0.0003",
# "tif": "gtc",
# "is_reduce_only": False,
# "create_time": 1685503873,
# "price": "200",
# "size": 1,
# "refr": "0",
# "left": 1,
# "text": "api",
# "fill_price": "0",
# "user": 5691076,
# "status": "open",
# "is_liq": False,
# "refu": 0,
# "is_close": False,
# "iceberg": 0
# }
# ]
#
result = response
if openSpotOrders:
result = []
for i in range(0, len(response)):
ordersInner = self.safe_value(response[i], 'orders')
result = self.array_concat(result, ordersInner)
orders = self.parse_orders(result, market, since, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit)
async def cancel_order(self, id: str, symbol: Str = None, params={}):
"""
Cancels an open order
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-single-order
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-single-order-2
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-single-order-3
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-single-order-4
:param str id: Order id
:param str symbol: Unified market symbol
:param dict [params]: Parameters specified by the exchange api
:param bool [params.trigger]: True if the order to be cancelled is a trigger order
:param bool [params.unifiedAccount]: set to True for canceling unified account orders
:returns: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = None if (symbol is None) else self.market(symbol)
trigger = self.safe_bool_n(params, ['is_stop_order', 'stop', 'trigger'], False)
params = self.omit(params, ['is_stop_order', 'stop', 'trigger'])
type, query = self.handle_market_type_and_params('cancelOrder', market, params)
request, requestParams = self.spot_order_prepare_request(market, trigger, query) if (type == 'spot' or type == 'margin') else self.prepare_request(market, type, query)
request['order_id'] = id
response = None
if type == 'spot' or type == 'margin':
if trigger:
response = await self.privateSpotDeletePriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateSpotDeleteOrdersOrderId(self.extend(request, requestParams))
elif type == 'swap':
if trigger:
response = await self.privateFuturesDeleteSettlePriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateFuturesDeleteSettleOrdersOrderId(self.extend(request, requestParams))
elif type == 'future':
if trigger:
response = await self.privateDeliveryDeleteSettlePriceOrdersOrderId(self.extend(request, requestParams))
else:
response = await self.privateDeliveryDeleteSettleOrdersOrderId(self.extend(request, requestParams))
elif type == 'option':
response = await self.privateOptionsDeleteOrdersOrderId(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' cancelOrder() not support self market type')
#
# spot
#
# {
# "id": "95282841887",
# "text": "apiv4",
# "create_time": "1637383156",
# "update_time": "1637383235",
# "create_time_ms": 1637383156017,
# "update_time_ms": 1637383235085,
# "status": "cancelled",
# "currency_pair": "ETH_USDT",
# "type": "limit",
# "account": "spot",
# "side": "buy",
# "amount": "0.01",
# "price": "3500",
# "time_in_force": "gtc",
# "iceberg": "0",
# "left": "0.01",
# "fill_price": "0",
# "filled_total": "0",
# "fee": "0",
# "fee_currency": "ETH",
# "point_fee": "0",
# "gt_fee": "0",
# "gt_discount": False,
# "rebated_fee": "0",
# "rebated_fee_currency": "USDT"
# }
#
# spot conditional
#
# {
# "market": "ETH_USDT",
# "user": 2436035,
# "trigger": {
# "price": "3500",
# "rule": "\u003c=",
# "expiration": 86400
# },
# "put": {
# "type": "limit",
# "side": "buy",
# "price": "3500",
# "amount": "0.01000000000000000000",
# "account": "normal",
# "time_in_force": "gtc"
# },
# "id": 5891843,
# "ctime": 1637382379,
# "ftime": 1637382673,
# "status": "canceled"
# }
#
# swap, future and option
#
# {
# "id": "82241928192",
# "contract": "BTC_USDT",
# "mkfr": "0",
# "tkfr": "0.0005",
# "tif": "gtc",
# "is_reduce_only": False,
# "create_time": "1635196145.06",
# "finish_time": "1635196233.396",
# "price": "61000",
# "size": "4",
# "refr": "0",
# "left": "4",
# "text": "web",
# "fill_price": "0",
# "user": "6693577",
# "finish_as": "cancelled",
# "status": "finished",
# "is_liq": False,
# "refu": "0",
# "is_close": False,
# "iceberg": "0",
# }
#
return self.parse_order(response, market)
async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
"""
cancel multiple orders
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-batch-of-orders-with-an-id-list
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-batch-of-orders-with-an-id-list-2
:param str[] ids: order ids
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param bool [params.unifiedAccount]: set to True for canceling unified account orders
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = None
if symbol is not None:
market = self.market(symbol)
type = None
defaultSettle = 'usdt' if (market is None) else market['settle']
settle = self.safe_string_lower(params, 'settle', defaultSettle)
type, params = self.handle_market_type_and_params('cancelOrders', market, params)
isSpot = (type == 'spot')
if isSpot and (symbol is None):
raise ArgumentsRequired(self.id + ' cancelOrders requires a symbol argument for spot markets')
if isSpot:
ordersRequests = []
for i in range(0, len(ids)):
id = ids[i]
orderItem: dict = {
'id': id,
'symbol': symbol,
}
ordersRequests.append(orderItem)
return await self.cancel_orders_for_symbols(ordersRequests, params)
request = {
'settle': settle,
}
finalList = [request] # hacky but needs to be done here
for i in range(0, len(ids)):
finalList.append(ids[i])
response = await self.privateFuturesPostSettleBatchCancelOrders(finalList)
return self.parse_orders(response)
async def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
"""
cancel multiple orders for multiple symbols
https://www.gate.com/docs/developers/apiv4/en/#cancel-a-batch-of-orders-with-an-id-list
:param CancellationRequest[] orders: list of order ids with symbol, example [{"id": "a", "symbol": "BTC/USDT"}, {"id": "b", "symbol": "ETH/USDT"}]
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str[] [params.clientOrderIds]: client order ids
:param bool [params.unifiedAccount]: set to True for canceling unified account orders
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
ordersRequests = []
for i in range(0, len(orders)):
order = orders[i]
symbol = self.safe_string(order, 'symbol')
market = self.market(symbol)
if not market['spot']:
raise NotSupported(self.id + ' cancelOrdersForSymbols() supports only spot markets')
id = self.safe_string(order, 'id')
orderItem: dict = {
'id': id,
'currency_pair': market['id'],
}
ordersRequests.append(orderItem)
response = await self.privateSpotPostCancelBatchOrders(ordersRequests)
#
# [
# {
# "currency_pair": "BTC_USDT",
# "id": "123456"
# }
# ]
#
return self.parse_orders(response)
async def cancel_all_orders(self, symbol: Str = None, params={}):
"""
cancel all open orders
https://www.gate.com/docs/developers/apiv4/en/#cancel-all-open-orders-in-specified-currency-pair
https://www.gate.com/docs/developers/apiv4/en/#cancel-all-open-orders-matched
https://www.gate.com/docs/developers/apiv4/en/#cancel-all-open-orders-matched-2
https://www.gate.com/docs/developers/apiv4/en/#cancel-all-open-orders-matched-3
: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 bool [params.unifiedAccount]: set to True for canceling unified account orders
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.load_unified_status()
market = None if (symbol is None) else self.market(symbol)
trigger = self.safe_bool_2(params, 'stop', 'trigger')
params = self.omit(params, ['stop', 'trigger'])
type, query = self.handle_market_type_and_params('cancelAllOrders', market, params)
request, requestParams = self.multi_order_spot_prepare_request(market, trigger, query) if (type == 'spot') else self.prepare_request(market, type, query)
response = None
if type == 'spot' or type == 'margin':
if trigger:
response = await self.privateSpotDeletePriceOrders(self.extend(request, requestParams))
else:
response = await self.privateSpotDeleteOrders(self.extend(request, requestParams))
elif type == 'swap':
if trigger:
response = await self.privateFuturesDeleteSettlePriceOrders(self.extend(request, requestParams))
else:
response = await self.privateFuturesDeleteSettleOrders(self.extend(request, requestParams))
elif type == 'future':
if trigger:
response = await self.privateDeliveryDeleteSettlePriceOrders(self.extend(request, requestParams))
else:
response = await self.privateDeliveryDeleteSettleOrders(self.extend(request, requestParams))
elif type == 'option':
response = await self.privateOptionsDeleteOrders(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' cancelAllOrders() not support self market type')
#
# [
# {
# "id": 139797004085,
# "contract": "ADA_USDT",
# "mkfr": "0",
# "tkfr": "0.0005",
# "tif": "gtc",
# "is_reduce_only": False,
# "create_time": 1647911169.343,
# "finish_time": 1647911226.849,
# "price": "0.8",
# "size": 1,
# "refr": "0.3",
# "left": 1,
# "text": "api",
# "fill_price": "0",
# "user": 6693577,
# "finish_as": "cancelled",
# "status": "finished",
# "is_liq": False,
# "refu": 2436035,
# "is_close": False,
# "iceberg": 0
# }
# ...
# ]
#
return self.parse_orders(response, market)
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
"""
transfer currency internally between wallets on the same account
https://www.gate.com/docs/developers/apiv4/en/#transfer-between-trading-accounts
:param str code: unified currency code for currency being transferred
:param float amount: the amount of currency to transfer
:param str fromAccount: the account to transfer currency from
:param str toAccount: the account to transfer currency to
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.symbol]: Unified market symbol *required for type == margin*
:returns: A `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
"""
await self.load_markets()
currency = self.currency(code)
fromId = self.convert_type_to_account(fromAccount)
toId = self.convert_type_to_account(toAccount)
truncated = self.currency_to_precision(code, amount)
request: dict = {
'currency': currency['id'], # todo: currencies have network-junctions
'amount': truncated,
}
if not (fromId in self.options['accountsByType']):
request['from'] = 'margin'
request['currency_pair'] = fromId
else:
request['from'] = fromId
if not (toId in self.options['accountsByType']):
request['to'] = 'margin'
request['currency_pair'] = toId
else:
request['to'] = toId
if fromId == 'margin' or toId == 'margin':
symbol = self.safe_string_2(params, 'symbol', 'currency_pair')
if symbol is None:
raise ArgumentsRequired(self.id + ' transfer requires params["symbol"] for isolated margin transfers')
market = self.market(symbol)
request['currency_pair'] = market['id']
params = self.omit(params, 'symbol')
if (toId == 'futures') or (toId == 'delivery') or (fromId == 'futures') or (fromId == 'delivery'):
request['settle'] = currency['id'] # todo: currencies have network-junctions
response = await self.privateWalletPostTransfers(self.extend(request, params))
#
# according to the docs(however actual response seems to be an empty string '')
#
# {
# "currency": "BTC",
# "from": "spot",
# "to": "margin",
# "amount": "1",
# "currency_pair": "BTC_USDT"
# }
#
return self.parse_transfer(response, currency)
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
#
# {
# "currency": "BTC",
# "from": "spot",
# "to": "margin",
# "amount": "1",
# "currency_pair": "BTC_USDT"
# }
#
return {
'id': self.safe_string(transfer, 'tx_id'),
'timestamp': None,
'datetime': None,
'currency': self.safe_currency_code(None, currency),
'amount': None,
'fromAccount': None,
'toAccount': None,
'status': None,
'info': transfer,
}
async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
"""
set the level of leverage for a market
https://www.gate.com/docs/developers/apiv4/en/#update-position-leverage
https://www.gate.com/docs/developers/apiv4/en/#update-position-leverage-2
:param float leverage: the rate of leverage
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
# WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
# AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
if (leverage < 0) or (leverage > 100):
raise BadRequest(self.id + ' setLeverage() leverage should be between 1 and 100')
await self.load_markets()
market = self.market(symbol)
request, query = self.prepare_request(market, None, params)
defaultMarginMode = self.safe_string_2(self.options, 'marginMode', 'defaultMarginMode')
crossLeverageLimit = self.safe_string(query, 'cross_leverage_limit')
marginMode = self.safe_string(query, 'marginMode', defaultMarginMode)
stringifiedMargin = self.number_to_string(leverage)
if crossLeverageLimit is not None:
marginMode = 'cross'
stringifiedMargin = crossLeverageLimit
if marginMode == 'cross' or marginMode == 'cross_margin':
request['cross_leverage_limit'] = stringifiedMargin
request['leverage'] = '0'
else:
request['leverage'] = stringifiedMargin
response = None
if market['swap']:
response = await self.privateFuturesPostSettlePositionsContractLeverage(self.extend(request, query))
elif market['future']:
response = await self.privateDeliveryPostSettlePositionsContractLeverage(self.extend(request, query))
else:
raise NotSupported(self.id + ' setLeverage() not support self market type')
#
# {
# "value": "0",
# "leverage": "5",
# "mode": "single",
# "realised_point": "0",
# "contract": "BTC_USDT",
# "entry_price": "0",
# "mark_price": "62035.86",
# "history_point": "0",
# "realised_pnl": "0",
# "close_order": null,
# "size": 0,
# "cross_leverage_limit": "0",
# "pending_orders": 0,
# "adl_ranking": 6,
# "maintenance_rate": "0.005",
# "unrealised_pnl": "0",
# "user": 2436035,
# "leverage_max": "100",
# "history_pnl": "0",
# "risk_limit": "1000000",
# "margin": "0",
# "last_close_pnl": "0",
# "liq_price": "0"
# }
#
return response
def parse_position(self, position: dict, market: Market = None):
#
# swap and future
#
# {
# "value": "4.60516",
# "leverage": "0",
# "mode": "single",
# "realised_point": "0",
# "contract": "BTC_USDT",
# "entry_price": "46030.3",
# "mark_price": "46051.6",
# "history_point": "0",
# "realised_pnl": "-0.002301515",
# "close_order": null,
# "size": 1,
# "cross_leverage_limit": "0",
# "pending_orders": 0,
# "adl_ranking": 5,
# "maintenance_rate": "0.004",
# "unrealised_pnl": "0.00213",
# "user": 5691076,
# "leverage_max": "125",
# "history_pnl": "0",
# "risk_limit": "1000000",
# "margin": "8.997698485",
# "last_close_pnl": "0",
# "liq_price": "0",
# "update_time": 1705034246,
# "update_id": 1,
# "initial_margin": "0",
# "maintenance_margin": "0",
# "open_time": 1705034246,
# "trade_max_size": "0"
# }
#
# option
#
# {
# "close_order": null,
# "size": 1,
# "vega": "5.29756",
# "theta": "-98.98917",
# "gamma": "0.00056",
# "delta": "0.68691",
# "contract": "BTC_USDT-20230602-26500-C",
# "entry_price": "529",
# "unrealised_pnl": "-1.0131",
# "user": 5691076,
# "mark_price": "427.69",
# "underlying_price": "26810.2",
# "underlying": "BTC_USDT",
# "realised_pnl": "-0.08042877",
# "mark_iv": "0.4224",
# "pending_orders": 0
# }
#
# fetchPositionsHistory(swap and future)
#
# {
# "contract": "SLERF_USDT", # Futures contract
# "text": "web", # Text of close order
# "long_price": "0.766306", # When 'side' is 'long,' it indicates the opening average price; when 'side' is 'short,' it indicates the closing average price.
# "pnl": "-23.41702352", # PNL
# "pnl_pnl": "-22.7187", # Position P/L
# "pnl_fee": "-0.06527125", # Transaction Fees
# "pnl_fund": "-0.63305227", # Funding Fees
# "accum_size": "100",
# "time": 1711279263, # Position close time
# "short_price": "0.539119", # When 'side' is 'long,' it indicates the opening average price; when 'side' is 'short,' it indicates the closing average price
# "side": "long", # Position side, long or short
# "max_size": "100", # Max Trade Size
# "first_open_time": 1711037985 # First Open Time
# }
#
contract = self.safe_string(position, 'contract')
market = self.safe_market(contract, market, '_', 'contract')
size = self.safe_string_2(position, 'size', 'accum_size')
side = self.safe_string(position, 'side')
if side is None:
if Precise.string_gt(size, '0'):
side = 'long'
elif Precise.string_lt(size, '0'):
side = 'short'
maintenanceRate = self.safe_string(position, 'maintenance_rate')
notional = self.safe_string(position, 'value')
leverage = self.safe_string(position, 'leverage')
marginMode = None
if leverage is not None:
if leverage == '0':
marginMode = 'cross'
else:
marginMode = 'isolated'
# Initial Position Margin = ( Position Value / Leverage ) + Close Position Fee
# *The default leverage under the full position is the highest leverage in the market.
# *Trading fee is charged Fee Rate(0.075%).
feePaid = self.safe_string(position, 'pnl_fee')
initialMarginString = None
if feePaid is None:
takerFee = '0.00075'
feePaid = Precise.string_mul(takerFee, notional)
initialMarginString = Precise.string_add(Precise.string_div(notional, leverage), feePaid)
timestamp = self.safe_timestamp_2(position, 'open_time', 'first_open_time')
if timestamp == 0:
timestamp = None
return self.safe_position({
'info': position,
'id': None,
'symbol': self.safe_string(market, 'symbol'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastUpdateTimestamp': self.safe_timestamp_2(position, 'update_time', 'time'),
'initialMargin': self.parse_number(initialMarginString),
'initialMarginPercentage': self.parse_number(Precise.string_div(initialMarginString, notional)),
'maintenanceMargin': self.parse_number(Precise.string_mul(maintenanceRate, notional)),
'maintenanceMarginPercentage': self.parse_number(maintenanceRate),
'entryPrice': self.safe_number(position, 'entry_price'),
'notional': self.parse_number(notional),
'leverage': self.safe_number(position, 'leverage'),
'unrealizedPnl': self.safe_number(position, 'unrealised_pnl'),
'realizedPnl': self.safe_number_2(position, 'realised_pnl', 'pnl'),
'contracts': self.parse_number(Precise.string_abs(size)),
'contractSize': self.safe_number(market, 'contractSize'),
'marginRatio': None,
'liquidationPrice': self.safe_number(position, 'liq_price'),
'markPrice': self.safe_number(position, 'mark_price'),
'lastPrice': None,
'collateral': self.safe_number(position, 'margin'),
'marginMode': marginMode,
'side': side,
'percentage': None,
'stopLossPrice': None,
'takeProfitPrice': None,
})
async def fetch_position(self, symbol: str, params={}):
"""
fetch data on an open contract position
https://www.gate.com/docs/developers/apiv4/en/#get-single-position
https://www.gate.com/docs/developers/apiv4/en/#get-single-position-2
https://www.gate.com/docs/developers/apiv4/en/#get-specified-contract-position
:param str symbol: unified market symbol of the market the position is held in
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
await self.load_markets()
market = self.market(symbol)
if not market['contract']:
raise BadRequest(self.id + ' fetchPosition() supports contract markets only')
request: dict = {}
request, params = self.prepare_request(market, market['type'], params)
extendedRequest = self.extend(request, params)
response = None
if market['swap']:
response = await self.privateFuturesGetSettlePositionsContract(extendedRequest)
elif market['future']:
response = await self.privateDeliveryGetSettlePositionsContract(extendedRequest)
elif market['type'] == 'option':
response = await self.privateOptionsGetPositionsContract(extendedRequest)
#
# swap and future
#
# {
# "value": "4.60516",
# "leverage": "0",
# "mode": "single",
# "realised_point": "0",
# "contract": "BTC_USDT",
# "entry_price": "46030.3",
# "mark_price": "46051.6",
# "history_point": "0",
# "realised_pnl": "-0.002301515",
# "close_order": null,
# "size": 1,
# "cross_leverage_limit": "0",
# "pending_orders": 0,
# "adl_ranking": 5,
# "maintenance_rate": "0.004",
# "unrealised_pnl": "0.00213",
# "user": 5691076,
# "leverage_max": "125",
# "history_pnl": "0",
# "risk_limit": "1000000",
# "margin": "8.997698485",
# "last_close_pnl": "0",
# "liq_price": "0",
# "update_time": 1705034246,
# "update_id": 1,
# "initial_margin": "0",
# "maintenance_margin": "0",
# "open_time": 1705034246,
# "trade_max_size": "0"
# }
#
# option
#
# {
# "close_order": null,
# "size": 1,
# "vega": "5.29756",
# "theta": "-98.98917",
# "gamma": "0.00056",
# "delta": "0.68691",
# "contract": "BTC_USDT-20230602-26500-C",
# "entry_price": "529",
# "unrealised_pnl": "-1.0131",
# "user": 5691076,
# "mark_price": "427.69",
# "underlying_price": "26810.2",
# "underlying": "BTC_USDT",
# "realised_pnl": "-0.08042877",
# "mark_iv": "0.4224",
# "pending_orders": 0
# }
#
return self.parse_position(response, market)
async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
"""
fetch all open positions
https://www.gate.com/docs/developers/apiv4/en/#list-all-positions-of-a-user
https://www.gate.com/docs/developers/apiv4/en/#list-all-positions-of-a-user-2
https://www.gate.com/docs/developers/apiv4/en/#list-user-s-positions-of-specified-underlying
:param str[]|None symbols: Not used by gate, but parsed internally by CCXT
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.settle]: 'btc' or 'usdt' - settle currency for perpetual swap and future - default="usdt" for swap and "btc" for future
:param str [params.type]: swap, future or option, if not provided self.options['defaultType'] is used
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
await self.load_markets()
market = None
symbols = self.market_symbols(symbols, None, True, True, True)
if symbols is not None:
symbolsLength = len(symbols)
if symbolsLength > 0:
market = self.market(symbols[0])
type = None
request: dict = {}
type, params = self.handle_market_type_and_params('fetchPositions', market, params)
if (type is None) or (type == 'spot'):
type = 'swap' # default to swap
if type == 'option':
if symbols is not None:
marketId = market['id']
optionParts = marketId.split('-')
request['underlying'] = self.safe_string(optionParts, 0)
else:
request, params = self.prepare_request(None, type, params)
response = None
if type == 'swap':
response = await self.privateFuturesGetSettlePositions(self.extend(request, params))
elif type == 'future':
response = await self.privateDeliveryGetSettlePositions(self.extend(request, params))
elif type == 'option':
response = await self.privateOptionsGetPositions(self.extend(request, params))
#
# swap and future
#
# [
# {
# "value": "4.602828",
# "leverage": "0",
# "mode": "single",
# "realised_point": "0",
# "contract": "BTC_USDT",
# "entry_price": "46030.3",
# "mark_price": "46028.28",
# "history_point": "0",
# "realised_pnl": "-0.002301515",
# "close_order": null,
# "size": 1,
# "cross_leverage_limit": "0",
# "pending_orders": 0,
# "adl_ranking": 5,
# "maintenance_rate": "0.004",
# "unrealised_pnl": "-0.000202",
# "user": 5691076,
# "leverage_max": "125",
# "history_pnl": "0",
# "risk_limit": "1000000",
# "margin": "8.997698485",
# "last_close_pnl": "0",
# "liq_price": "0",
# "update_time": 1705034246,
# "update_id": 1,
# "initial_margin": "0",
# "maintenance_margin": "0",
# "open_time": 1705034246,
# "trade_max_size": "0"
# }
# ]
#
# option
#
# [
# {
# "close_order": null,
# "size": 0,
# "vega": "0.01907",
# "theta": "-3.04888",
# "gamma": "0.00001",
# "delta": "0.0011",
# "contract": "BTC_USDT-20230601-27500-C",
# "entry_price": "0",
# "unrealised_pnl": "0",
# "user": 5691076,
# "mark_price": "0.07",
# "underlying_price": "26817.27",
# "underlying": "BTC_USDT",
# "realised_pnl": "0",
# "mark_iv": "0.4339",
# "pending_orders": 0
# }
# ]
#
return self.parse_positions(response, symbols)
async def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
"""
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
https://www.gate.com/docs/developers/apiv4/en/#list-all-futures-contracts
https://www.gate.com/docs/developers/apiv4/en/#list-all-futures-contracts-2
:param str[] [symbols]: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
"""
await self.load_markets()
type, query = self.handle_market_type_and_params('fetchLeverageTiers', None, params)
request, requestParams = self.prepare_request(None, type, query)
if type != 'future' and type != 'swap':
raise BadRequest(self.id + ' fetchLeverageTiers only supports swap and future')
response = None
if type == 'swap':
response = await self.publicFuturesGetSettleContracts(self.extend(request, requestParams))
elif type == 'future':
response = await self.publicDeliveryGetSettleContracts(self.extend(request, requestParams))
else:
raise NotSupported(self.id + ' fetchLeverageTiers() not support self market type')
#
# Perpetual swap
#
# [
# {
# "name": "BTC_USDT",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.5",
# "maintenance_rate": "0.005",
# "mark_type": "index",
# "last_price": "38026",
# "mark_price": "37985.6",
# "index_price": "37954.92",
# "funding_rate_indicative": "0.000219",
# "mark_price_round": "0.01",
# "funding_offset": 0,
# "in_delisting": False,
# "risk_limit_base": "1000000",
# "interest_rate": "0.0003",
# "order_price_round": "0.1",
# "order_size_min": 1,
# "ref_rebate_rate": "0.2",
# "funding_interval": 28800,
# "risk_limit_step": "1000000",
# "leverage_min": "1",
# "leverage_max": "100",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025",
# "taker_fee_rate": "0.00075",
# "funding_rate": "0.002053",
# "order_size_max": 1000000,
# "funding_next_apply": 1610035200,
# "short_users": 977,
# "config_change_time": 1609899548,
# "trade_size": 28530850594,
# "position_size": 5223816,
# "long_users": 455,
# "funding_impact_value": "60000",
# "orders_limit": 50,
# "trade_id": 10851092,
# "orderbook_id": 2129638396
# }
# ]
#
# Delivery Futures
#
# [
# {
# "name": "BTC_USDT_20200814",
# "underlying": "BTC_USDT",
# "cycle": "WEEKLY",
# "type": "direct",
# "quanto_multiplier": "0.0001",
# "mark_type": "index",
# "last_price": "9017",
# "mark_price": "9019",
# "index_price": "9005.3",
# "basis_rate": "0.185095",
# "basis_value": "13.7",
# "basis_impact_value": "100000",
# "settle_price": "0",
# "settle_price_interval": 60,
# "settle_price_duration": 1800,
# "settle_fee_rate": "0.0015",
# "expire_time": 1593763200,
# "order_price_round": "0.1",
# "mark_price_round": "0.1",
# "leverage_min": "1",
# "leverage_max": "100",
# "maintenance_rate": "1000000",
# "risk_limit_base": "140.726652109199",
# "risk_limit_step": "1000000",
# "risk_limit_max": "8000000",
# "maker_fee_rate": "-0.00025",
# "taker_fee_rate": "0.00075",
# "ref_discount_rate": "0",
# "ref_rebate_rate": "0.2",
# "order_price_deviate": "0.5",
# "order_size_min": 1,
# "order_size_max": 1000000,
# "orders_limit": 50,
# "orderbook_id": 63,
# "trade_id": 26,
# "trade_size": 435,
# "position_size": 130,
# "config_change_time": 1593158867,
# "in_delisting": False
# }
# ]
#
return self.parse_leverage_tiers(response, symbols, 'name')
async def fetch_market_leverage_tiers(self, symbol: str, params={}) -> List[LeverageTier]:
"""
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes for a single market
https://www.gate.com/docs/developers/apiv4/en/#list-risk-limit-tiers
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `leverage tiers structure <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`
"""
await self.load_markets()
market = self.market(symbol)
type, query = self.handle_market_type_and_params('fetchMarketLeverageTiers', market, params)
request, requestParams = self.prepare_request(market, type, query)
if type != 'future' and type != 'swap':
raise BadRequest(self.id + ' fetchMarketLeverageTiers only supports swap and future')
response = await self.publicFuturesGetSettleRiskLimitTiers(self.extend(request, requestParams))
#
# [
# {
# "maintenance_rate": "0.004",
# "tier": 1,
# "initial_rate": "0.008",
# "leverage_max": "125",
# "risk_limit": "1000000"
# }
# ]
#
return self.parse_market_leverage_tiers(response, market)
def parse_emulated_leverage_tiers(self, info, market=None) -> List[LeverageTier]:
marketId = self.safe_string(info, 'name')
maintenanceMarginUnit = self.safe_string(info, 'maintenance_rate') # '0.005',
leverageMax = self.safe_string(info, 'leverage_max') # '100',
riskLimitStep = self.safe_string(info, 'risk_limit_step') # '1000000',
riskLimitMax = self.safe_string(info, 'risk_limit_max') # '16000000',
initialMarginUnit = Precise.string_div('1', leverageMax)
maintenanceMarginRate = maintenanceMarginUnit
initialMarginRatio = initialMarginUnit
floor = '0'
tiers = []
while(Precise.string_lt(floor, riskLimitMax)):
cap = Precise.string_add(floor, riskLimitStep)
tiers.append({
'tier': self.parse_number(Precise.string_div(cap, riskLimitStep)),
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
'currency': self.safe_string(market, 'settle'),
'minNotional': self.parse_number(floor),
'maxNotional': self.parse_number(cap),
'maintenanceMarginRate': self.parse_number(maintenanceMarginRate),
'maxLeverage': self.parse_number(Precise.string_div('1', initialMarginRatio)),
'info': info,
})
maintenanceMarginRate = Precise.string_add(maintenanceMarginRate, maintenanceMarginUnit)
initialMarginRatio = Precise.string_add(initialMarginRatio, initialMarginUnit)
floor = cap
return tiers
def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
#
# [
# {
# "maintenance_rate": "0.004",
# "tier": 1,
# "initial_rate": "0.008",
# "leverage_max": "125",
# "risk_limit": "1000000"
# }
# ]
#
if not isinstance(info, list):
return self.parse_emulated_leverage_tiers(info, market)
minNotional = 0
tiers = []
for i in range(0, len(info)):
item = info[i]
maxNotional = self.safe_number(item, 'risk_limit')
tiers.append({
'tier': self.sum(i, 1),
'symbol': market['symbol'],
'currency': market['base'],
'minNotional': minNotional,
'maxNotional': maxNotional,
'maintenanceMarginRate': self.safe_number(item, 'maintenance_rate'),
'maxLeverage': self.safe_number(item, 'leverage_max'),
'info': item,
})
minNotional = maxNotional
return tiers
async def repay_isolated_margin(self, symbol: str, code: str, amount, params={}):
"""
repay borrowed margin and interest
https://www.gate.com/docs/apiv4/en/#repay-a-loan
:param str symbol: unified market symbol
:param str code: unified currency code of the currency to repay
:param float amount: the amount to repay
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.mode]: 'all' or 'partial' payment mode, extra parameter required for isolated margin
:param str [params.id]: '34267567' loan id, extra parameter required for isolated margin
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
"""
await self.load_markets()
currency = self.currency(code)
request: dict = {
'currency': currency['id'].upper(), # todo: currencies have network-junctions
'amount': self.currency_to_precision(code, amount),
}
market = self.market(symbol)
request['currency_pair'] = market['id']
request['type'] = 'repay'
response = await self.privateMarginPostUniLoans(self.extend(request, params))
#
# empty response
#
return self.parse_margin_loan(response, currency)
async def repay_cross_margin(self, code: str, amount, params={}):
"""
repay cross margin borrowed margin and interest
https://www.gate.com/docs/developers/apiv4/en/#cross-margin-repayments
https://www.gate.com/docs/developers/apiv4/en/#borrow-or-repay
:param str code: unified currency code of the currency to repay
:param float amount: the amount to repay
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.mode]: 'all' or 'partial' payment mode, extra parameter required for isolated margin
:param str [params.id]: '34267567' loan id, extra parameter required for isolated margin
:param boolean [params.unifiedAccount]: set to True for repaying in the unified account
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
"""
await self.load_markets()
await self.load_unified_status()
currency = self.currency(code)
request: dict = {
'currency': currency['id'].upper(), # todo: currencies have network-junctions
'amount': self.currency_to_precision(code, amount),
}
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'repayCrossMargin', 'unifiedAccount')
response = None
if isUnifiedAccount:
request['type'] = 'repay'
response = await self.privateUnifiedPostLoans(self.extend(request, params))
else:
response = await self.privateMarginPostCrossRepayments(self.extend(request, params))
response = self.safe_dict(response, 0)
#
# [
# {
# "id": "17",
# "create_time": 1620381696159,
# "update_time": 1620381696159,
# "currency": "EOS",
# "amount": "110.553635",
# "text": "web",
# "status": 2,
# "repaid": "110.506649705159",
# "repaid_interest": "0.046985294841",
# "unpaid_interest": "0.0000074393366667"
# }
# ]
#
return self.parse_margin_loan(response, currency)
async def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
"""
create a loan to borrow margin
https://www.gate.com/docs/developers/apiv4/en/#marginuni
:param str symbol: unified market symbol, required for isolated margin
:param str code: unified currency code of the currency to borrow
:param float amount: the amount to borrow
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.rate]: '0.0002' or '0.002' extra parameter required for isolated margin
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
"""
await self.load_markets()
currency = self.currency(code)
request: dict = {
'currency': currency['id'].upper(), # todo: currencies have network-junctions
'amount': self.currency_to_precision(code, amount),
}
response = None
market = self.market(symbol)
request['currency_pair'] = market['id']
request['type'] = 'borrow'
response = await self.privateMarginPostUniLoans(self.extend(request, params))
#
# {
# "id": "34267567",
# "create_time": "1656394778",
# "expire_time": "1657258778",
# "status": "loaned",
# "side": "borrow",
# "currency": "USDT",
# "rate": "0.0002",
# "amount": "100",
# "days": 10,
# "auto_renew": False,
# "currency_pair": "LTC_USDT",
# "left": "0",
# "repaid": "0",
# "paid_interest": "0",
# "unpaid_interest": "0.003333333333"
# }
#
return self.parse_margin_loan(response, currency)
async def borrow_cross_margin(self, code: str, amount: float, params={}):
"""
create a loan to borrow margin
https://www.gate.com/docs/apiv4/en/#create-a-cross-margin-borrow-loan
https://www.gate.com/docs/developers/apiv4/en/#borrow-or-repay
:param str code: unified currency code of the currency to borrow
:param float amount: the amount to borrow
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.rate]: '0.0002' or '0.002' extra parameter required for isolated margin
:param boolean [params.unifiedAccount]: set to True for borrowing in the unified account
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
"""
await self.load_markets()
await self.load_unified_status()
currency = self.currency(code)
request: dict = {
'currency': currency['id'].upper(), # todo: currencies have network-junctions
'amount': self.currency_to_precision(code, amount),
}
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'borrowCrossMargin', 'unifiedAccount')
response = None
if isUnifiedAccount:
request['type'] = 'borrow'
response = await self.privateUnifiedPostLoans(self.extend(request, params))
else:
response = await self.privateMarginPostCrossLoans(self.extend(request, params))
#
# {
# "id": "17",
# "create_time": 1620381696159,
# "update_time": 1620381696159,
# "currency": "EOS",
# "amount": "110.553635",
# "text": "web",
# "status": 2,
# "repaid": "110.506649705159",
# "repaid_interest": "0.046985294841",
# "unpaid_interest": "0.0000074393366667"
# }
#
return self.parse_margin_loan(response, currency)
def parse_margin_loan(self, info, currency: Currency = None):
#
# Cross
#
# {
# "id": "17",
# "create_time": 1620381696159,
# "update_time": 1620381696159,
# "currency": "EOS",
# "amount": "110.553635",
# "text": "web",
# "status": 2,
# "repaid": "110.506649705159",
# "repaid_interest": "0.046985294841",
# "unpaid_interest": "0.0000074393366667"
# }
#
# Isolated
#
# {
# "id": "34267567",
# "create_time": "1656394778",
# "expire_time": "1657258778",
# "status": "loaned",
# "side": "borrow",
# "currency": "USDT",
# "rate": "0.0002",
# "amount": "100",
# "days": 10,
# "auto_renew": False,
# "currency_pair": "LTC_USDT",
# "left": "0",
# "repaid": "0",
# "paid_interest": "0",
# "unpaid_interest": "0.003333333333"
# }
#
marginMode = self.safe_string_2(self.options, 'defaultMarginMode', 'marginMode', 'cross')
timestamp = self.safe_integer(info, 'create_time')
if marginMode == 'isolated':
timestamp = self.safe_timestamp(info, 'create_time')
currencyId = self.safe_string(info, 'currency')
marketId = self.safe_string(info, 'currency_pair')
return {
'id': self.safe_integer(info, 'id'),
'currency': self.safe_currency_code(currencyId, currency),
'amount': self.safe_number(info, 'amount'),
'symbol': self.safe_symbol(marketId, None, '_', 'margin'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'info': info,
}
async def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]:
"""
fetch the interest owed by the user for borrowing currency for margin trading
https://www.gate.com/docs/developers/apiv4/en/#list-interest-records
https://www.gate.com/docs/developers/apiv4/en/#interest-records-for-the-cross-margin-account
https://www.gate.com/docs/developers/apiv4/en/#list-interest-records-2
:param str [code]: unified currency code
:param str [symbol]: unified market symbol when fetching interest in isolated markets
:param int [since]: the earliest time in ms to fetch borrow interest for
:param int [limit]: the maximum number of structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.unifiedAccount]: set to True for fetching borrow interest in the unified account
:returns dict[]: a list of `borrow interest structures <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
"""
await self.load_markets()
await self.load_unified_status()
isUnifiedAccount = False
isUnifiedAccount, params = self.handle_option_and_params(params, 'fetchBorrowInterest', 'unifiedAccount')
request: dict = {}
request, params = self.handle_until_option('to', request, params)
currency = None
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id']
market = None
if symbol is not None:
market = self.market(symbol)
if since is not None:
request['from'] = since
if limit is not None:
request['limit'] = limit
response = None
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('fetchBorrowInterest', params, 'cross')
if isUnifiedAccount:
response = await self.privateUnifiedGetInterestRecords(self.extend(request, params))
elif marginMode == 'isolated':
if market is not None:
request['currency_pair'] = market['id']
response = await self.privateMarginGetUniInterestRecords(self.extend(request, params))
elif marginMode == 'cross':
response = await self.privateMarginGetCrossInterestRecords(self.extend(request, params))
interest = self.parse_borrow_interests(response, market)
return self.filter_by_currency_since_limit(interest, code, since, limit)
def parse_borrow_interest(self, info: dict, market: Market = None) -> BorrowInterest:
marketId = self.safe_string(info, 'currency_pair')
market = self.safe_market(marketId, market)
marginMode = 'isolated' if (marketId is not None) else 'cross'
timestamp = self.safe_integer(info, 'create_time')
return {
'info': info,
'symbol': self.safe_string(market, 'symbol'),
'currency': self.safe_currency_code(self.safe_string(info, 'currency')),
'interest': self.safe_number(info, 'interest'),
'interestRate': self.safe_number(info, 'actual_rate'),
'amountBorrowed': None,
'marginMode': marginMode,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
}
def nonce(self):
return self.milliseconds() - self.options['timeDifference']
def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
authentication = api[0] # public, private
type = api[1] # spot, margin, future, delivery
query = self.omit(params, self.extract_params(path))
containsSettle = path.find('settle') > -1
if containsSettle and path.endswith('batch_cancel_orders'): # weird check to prevent $settle in php and converting {settle} to array(settle)
# special case where we need to extract the settle from the path
# but the body is an array of strings
settle = self.safe_dict(params, 0)
path = self.implode_params(path, settle)
# remove the first element from params
newParams = []
anyParams = params
for i in range(1, len(anyParams)):
newParams.append(params[i])
params = newParams
query = newParams
elif isinstance(params, list):
# endpoints like createOrders use an array instead of an object
# so we infer the settle from one of the elements
# they have to be all the same so relying on the first one is fine
first = self.safe_value(params, 0, {})
path = self.implode_params(path, first)
else:
path = self.implode_params(path, params)
endPart = '' if (path == '') else ('/' + path)
entirePath = '/' + type + endPart
if (type == 'subAccounts') or (type == 'withdrawals'):
entirePath = endPart
url = self.urls['api'][authentication][type]
if url is None:
raise NotSupported(self.id + ' does not have a testnet for the ' + type + ' market type.')
url += entirePath
if authentication == 'public':
if query:
url += '?' + self.urlencode(query)
else:
self.check_required_credentials()
queryString = ''
requiresURLEncoding = False
if ((type == 'futures') or (type == 'delivery')) and method == 'POST':
pathParts = path.split('/')
secondPart = self.safe_string(pathParts, 1, '')
requiresURLEncoding = (secondPart.find('dual') >= 0) or (secondPart.find('positions') >= 0)
if (method == 'GET') or (method == 'DELETE') or requiresURLEncoding or (method == 'PATCH'):
if query:
queryString = self.urlencode(query)
# https://github.com/ccxt/ccxt/issues/25570
if queryString.find('currencies=') >= 0 and queryString.find('%2C') >= 0:
queryString = queryString.replace('%2C', ',')
url += '?' + queryString
if method == 'PATCH':
body = self.json(query)
else:
urlQueryParams = self.safe_value(query, 'query', {})
if urlQueryParams:
queryString = self.urlencode(urlQueryParams)
url += '?' + queryString
query = self.omit(query, 'query')
body = self.json(query)
bodyPayload = '' if (body is None) else body
bodySignature = self.hash(self.encode(bodyPayload), 'sha512')
nonce = self.nonce()
timestamp = self.parse_to_int(nonce / 1000)
timestampString = str(timestamp)
signaturePath = '/api/' + self.version + entirePath
payloadArray = [method.upper(), signaturePath, queryString, bodySignature, timestampString]
# eslint-disable-next-line quotes
payload = "\n".join(payloadArray)
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha512)
headers = {
'KEY': self.apiKey,
'Timestamp': timestampString,
'SIGN': signature,
'Content-Type': 'application/json',
}
return {'url': url, 'method': method, 'body': body, 'headers': headers}
async def modify_margin_helper(self, symbol: str, amount, params={}):
await self.load_markets()
market = self.market(symbol)
request, query = self.prepare_request(market, None, params)
request['change'] = self.number_to_string(amount)
response = None
if market['swap']:
response = await self.privateFuturesPostSettlePositionsContractMargin(self.extend(request, query))
elif market['future']:
response = await self.privateDeliveryPostSettlePositionsContractMargin(self.extend(request, query))
else:
raise NotSupported(self.id + ' modifyMarginHelper() not support self market type')
return self.parse_margin_modification(response, market)
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
#
# {
# "value": "11.9257",
# "leverage": "5",
# "mode": "single",
# "realised_point": "0",
# "contract": "ETH_USDT",
# "entry_price": "1203.45",
# "mark_price": "1192.57",
# "history_point": "0",
# "realised_pnl": "-0.00577656",
# "close_order": null,
# "size": "1",
# "cross_leverage_limit": "0",
# "pending_orders": "0",
# "adl_ranking": "5",
# "maintenance_rate": "0.005",
# "unrealised_pnl": "-0.1088",
# "user": "1486602",
# "leverage_max": "100",
# "history_pnl": "0",
# "risk_limit": "1000000",
# "margin": "5.415925875",
# "last_close_pnl": "0",
# "liq_price": "665.69"
# }
#
contract = self.safe_string(data, 'contract')
market = self.safe_market(contract, market, '_', 'contract')
total = self.safe_number(data, 'margin')
return {
'info': data,
'symbol': market['symbol'],
'type': None,
'marginMode': 'isolated',
'amount': None,
'total': total,
'code': self.safe_value(market, 'quote'),
'status': 'ok',
'timestamp': None,
'datetime': None,
}
async def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
remove margin from a position
https://www.gate.com/docs/developers/apiv4/en/#update-position-margin
https://www.gate.com/docs/developers/apiv4/en/#update-position-margin-2
: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 await self.modify_margin_helper(symbol, -amount, params)
async def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
add margin
https://www.gate.com/docs/developers/apiv4/en/#update-position-margin
https://www.gate.com/docs/developers/apiv4/en/#update-position-margin-2
: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 await self.modify_margin_helper(symbol, amount, params)
async def fetch_open_interest_history(self, symbol: str, timeframe='5m', since: Int = None, limit: Int = None, params={}):
"""
Retrieves the open interest of a currency
https://www.gate.com/docs/developers/apiv4/en/#futures-stats
:param str symbol: Unified CCXT market symbol
:param str timeframe: "5m", "15m", "30m", "1h", "4h", "1d"
:param int [since]: the time(ms) of the earliest record to retrieve unix timestamp
:param int [limit]: default 30
:param dict [params]: exchange specific parameters
: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 dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOpenInterestHistory', 'paginate', False)
if paginate:
return await self.fetch_paginated_call_deterministic('fetchOpenInterestHistory', symbol, since, limit, timeframe, params, 100)
market = self.market(symbol)
if not market['swap']:
raise BadRequest(self.id + ' fetchOpenInterest() supports swap markets only')
request: dict = {
'contract': market['id'],
'settle': market['settleId'],
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
}
if limit is not None:
request['limit'] = limit
if since is not None:
request['from'] = since
response = await self.publicFuturesGetSettleContractStats(self.extend(request, params))
#
# [
# {
# "long_liq_size": "0",
# "short_liq_size": "0",
# "short_liq_usd": "0",
# "lsr_account": "3.2808988764045",
# "mark_price": "0.34619",
# "top_lsr_size": "0",
# "time": "1674057000",
# "short_liq_amount": "0",
# "long_liq_amount": "0",
# "open_interest_usd": "9872386.7775",
# "top_lsr_account": "0",
# "open_interest": "2851725",
# "long_liq_usd": "0",
# "lsr_taker": "9.3765153315902"
# },
# ...
# ]
#
return self.parse_open_interests_history(response, market, since, limit)
def parse_open_interest(self, interest, market: Market = None):
#
# {
# "long_liq_size": "0",
# "short_liq_size": "0",
# "short_liq_usd": "0",
# "lsr_account": "3.2808988764045",
# "mark_price": "0.34619",
# "top_lsr_size": "0",
# "time": "1674057000",
# "short_liq_amount": "0",
# "long_liq_amount": "0",
# "open_interest_usd": "9872386.7775",
# "top_lsr_account": "0",
# "open_interest": "2851725",
# "long_liq_usd": "0",
# "lsr_taker": "9.3765153315902"
# }
#
timestamp = self.safe_timestamp(interest, 'time')
return {
'symbol': self.safe_string(market, 'symbol'),
'openInterestAmount': self.safe_number(interest, 'open_interest'),
'openInterestValue': self.safe_number(interest, 'open_interest_usd'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'info': interest,
}
async def fetch_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical settlement records
https://www.gate.com/docs/developers/apiv4/en/#list-settlement-history-2
:param str symbol: unified market symbol of the settlement history, required on gate
:param int [since]: timestamp in ms
:param int [limit]: number of records
:param dict [params]: exchange specific params
:returns dict[]: a list of `settlement history objects <https://docs.ccxt.com/#/?id=settlement-history-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchSettlementHistory() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
type = None
type, params = self.handle_market_type_and_params('fetchSettlementHistory', market, params)
if type != 'option':
raise NotSupported(self.id + ' fetchSettlementHistory() supports option markets only')
marketId = market['id']
optionParts = marketId.split('-')
request: dict = {
'underlying': self.safe_string(optionParts, 0),
}
if since is not None:
request['from'] = since
if limit is not None:
request['limit'] = limit
response = await self.publicOptionsGetSettlements(self.extend(request, params))
#
# [
# {
# "time": 1685952000,
# "profit": "18.266806892718",
# "settle_price": "26826.68068927182",
# "fee": "0.040240021034",
# "contract": "BTC_USDT-20230605-25000-C",
# "strike_price": "25000"
# }
# ]
#
settlements = self.parse_settlements(response, market)
sorted = self.sort_by(settlements, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
async def fetch_my_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical settlement records of the user
https://www.gate.com/docs/developers/apiv4/en/#list-my-options-settlements
:param str symbol: unified market symbol of the settlement history
:param int [since]: timestamp in ms
:param int [limit]: number of records
:param dict [params]: exchange specific params
:returns dict[]: a list of [settlement history objects]
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchMySettlementHistory() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
type = None
type, params = self.handle_market_type_and_params('fetchMySettlementHistory', market, params)
if type != 'option':
raise NotSupported(self.id + ' fetchMySettlementHistory() supports option markets only')
marketId = market['id']
optionParts = marketId.split('-')
request: dict = {
'underlying': self.safe_string(optionParts, 0),
'contract': marketId,
}
if since is not None:
request['from'] = since
if limit is not None:
request['limit'] = limit
response = await self.privateOptionsGetMySettlements(self.extend(request, params))
#
# [
# {
# "size": -1,
# "settle_profit": "0",
# "contract": "BTC_USDT-20220624-26000-C",
# "strike_price": "26000",
# "time": 1656057600,
# "settle_price": "20917.461281337048",
# "underlying": "BTC_USDT",
# "realised_pnl": "-0.00116042",
# "fee": "0"
# }
# ]
#
result = self.safe_value(response, 'result', {})
data = self.safe_value(result, 'list', [])
settlements = self.parse_settlements(data, market)
sorted = self.sort_by(settlements, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
def parse_settlement(self, settlement, market):
#
# fetchSettlementHistory
#
# {
# "time": 1685952000,
# "profit": "18.266806892718",
# "settle_price": "26826.68068927182",
# "fee": "0.040240021034",
# "contract": "BTC_USDT-20230605-25000-C",
# "strike_price": "25000"
# }
#
# fetchMySettlementHistory
#
# {
# "size": -1,
# "settle_profit": "0",
# "contract": "BTC_USDT-20220624-26000-C",
# "strike_price": "26000",
# "time": 1656057600,
# "settle_price": "20917.461281337048",
# "underlying": "BTC_USDT",
# "realised_pnl": "-0.00116042",
# "fee": "0"
# }
#
timestamp = self.safe_timestamp(settlement, 'time')
marketId = self.safe_string(settlement, 'contract')
return {
'info': settlement,
'symbol': self.safe_symbol(marketId, market),
'price': self.safe_number(settlement, 'settle_price'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
}
def parse_settlements(self, settlements, market):
#
# fetchSettlementHistory
#
# [
# {
# "time": 1685952000,
# "profit": "18.266806892718",
# "settle_price": "26826.68068927182",
# "fee": "0.040240021034",
# "contract": "BTC_USDT-20230605-25000-C",
# "strike_price": "25000"
# }
# ]
#
# fetchMySettlementHistory
#
# [
# {
# "size": -1,
# "settle_profit": "0",
# "contract": "BTC_USDT-20220624-26000-C",
# "strike_price": "26000",
# "time": 1656057600,
# "settle_price": "20917.461281337048",
# "underlying": "BTC_USDT",
# "realised_pnl": "-0.00116042",
# "fee": "0"
# }
# ]
#
result = []
for i in range(0, len(settlements)):
result.append(self.parse_settlement(settlements[i], market))
return result
async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
"""
fetch the history of changes, actions done by the user or operations that altered the balance of the user
https://www.gate.com/docs/developers/apiv4/en/#query-account-book
https://www.gate.com/docs/developers/apiv4/en/#list-margin-account-balance-change-history
https://www.gate.com/docs/developers/apiv4/en/#query-account-book-2
https://www.gate.com/docs/developers/apiv4/en/#query-account-book-3
https://www.gate.com/docs/developers/apiv4/en/#list-account-changing-history
:param str [code]: unified currency code
:param int [since]: timestamp in ms of the earliest ledger entry
:param int [limit]: max number of ledger entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: end time 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 [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
if paginate:
return await self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params)
type = None
currency = None
response = None
request: dict = {}
type, params = self.handle_market_type_and_params('fetchLedger', None, params)
if (type == 'spot') or (type == 'margin'):
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id'] # todo: currencies have network-junctions
if (type == 'swap') or (type == 'future'):
defaultSettle = 'usdt' if (type == 'swap') else 'btc'
settle = self.safe_string_lower(params, 'settle', defaultSettle)
params = self.omit(params, 'settle')
request['settle'] = settle
if since is not None:
request['from'] = since
if limit is not None:
request['limit'] = limit
request, params = self.handle_until_option('to', request, params)
if type == 'spot':
response = await self.privateSpotGetAccountBook(self.extend(request, params))
elif type == 'margin':
response = await self.privateMarginGetAccountBook(self.extend(request, params))
elif type == 'swap':
response = await self.privateFuturesGetSettleAccountBook(self.extend(request, params))
elif type == 'future':
response = await self.privateDeliveryGetSettleAccountBook(self.extend(request, params))
elif type == 'option':
response = await self.privateOptionsGetAccountBook(self.extend(request, params))
#
# spot
#
# [
# {
# "id": "123456",
# "time": 1547633726123,
# "currency": "BTC",
# "change": "1.03",
# "balance": "4.59316525194",
# "type": "margin_in"
# }
# ]
#
# margin
#
# [
# {
# "id": "123456",
# "time": "1547633726",
# "time_ms": 1547633726123,
# "currency": "BTC",
# "currency_pair": "BTC_USDT",
# "change": "1.03",
# "balance": "4.59316525194"
# }
# ]
#
# swap and future
#
# [
# {
# "time": 1682294400.123456,
# "change": "0.000010152188",
# "balance": "4.59316525194",
# "text": "ETH_USD:6086261",
# "type": "fee"
# }
# ]
#
# option
#
# [
# {
# "time": 1685594770,
# "change": "3.33",
# "balance": "29.87911771",
# "text": "BTC_USDT-20230602-26500-C:2611026125",
# "type": "prem"
# }
# ]
#
return self.parse_ledger(response, currency, since, limit)
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
#
# spot
#
# {
# "id": "123456",
# "time": 1547633726123,
# "currency": "BTC",
# "change": "1.03",
# "balance": "4.59316525194",
# "type": "margin_in"
# }
#
# margin
#
# {
# "id": "123456",
# "time": "1547633726",
# "time_ms": 1547633726123,
# "currency": "BTC",
# "currency_pair": "BTC_USDT",
# "change": "1.03",
# "balance": "4.59316525194"
# }
#
# swap and future
#
# {
# "time": 1682294400.123456,
# "change": "0.000010152188",
# "balance": "4.59316525194",
# "text": "ETH_USD:6086261",
# "type": "fee"
# }
#
# option
#
# {
# "time": 1685594770,
# "change": "3.33",
# "balance": "29.87911771",
# "text": "BTC_USDT-20230602-26500-C:2611026125",
# "type": "prem"
# }
#
direction = None
amount = self.safe_string(item, 'change')
if Precise.string_lt(amount, '0'):
direction = 'out'
amount = Precise.string_abs(amount)
else:
direction = 'in'
currencyId = self.safe_string(item, 'currency')
currency = self.safe_currency(currencyId, currency)
type = self.safe_string(item, 'type')
rawTimestamp = self.safe_string(item, 'time')
timestamp = None
if len(rawTimestamp) > 10:
timestamp = int(rawTimestamp)
else:
timestamp = int(rawTimestamp) * 1000
balanceString = self.safe_string(item, 'balance')
changeString = self.safe_string(item, 'change')
before = self.parse_number(Precise.string_sub(balanceString, changeString))
return self.safe_ledger_entry({
'info': item,
'id': self.safe_string(item, 'id'),
'direction': direction,
'account': None,
'referenceAccount': None,
'referenceId': None,
'type': self.parse_ledger_entry_type(type),
'currency': self.safe_currency_code(currencyId, currency),
'amount': self.parse_number(amount),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'before': before,
'after': self.safe_number(item, 'balance'),
'status': None,
'fee': None,
}, currency)
def parse_ledger_entry_type(self, type):
ledgerType: dict = {
'deposit': 'deposit',
'withdraw': 'withdrawal',
'sub_account_transfer': 'transfer',
'margin_in': 'transfer',
'margin_out': 'transfer',
'margin_funding_in': 'transfer',
'margin_funding_out': 'transfer',
'cross_margin_in': 'transfer',
'cross_margin_out': 'transfer',
'copy_trading_in': 'transfer',
'copy_trading_out': 'transfer',
'quant_in': 'transfer',
'quant_out': 'transfer',
'futures_in': 'transfer',
'futures_out': 'transfer',
'delivery_in': 'transfer',
'delivery_out': 'transfer',
'new_order': 'trade',
'order_fill': 'trade',
'referral_fee': 'rebate',
'order_fee': 'fee',
'interest': 'interest',
'lend': 'loan',
'redeem': 'loan',
'profit': 'interest',
'flash_swap_buy': 'trade',
'flash_swap_sell': 'trade',
'unknown': 'unknown',
'set': 'settlement',
'prem': 'trade',
'point_refr': 'rebate',
'point_fee': 'fee',
'point_dnw': 'deposit/withdraw',
'fund': 'fee',
'refr': 'rebate',
'fee': 'fee',
'pnl': 'trade',
'dnw': 'deposit/withdraw',
}
return self.safe_string(ledgerType, type, type)
async def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
"""
set dual/hedged mode to True or False for a swap market, make sure all positions are closed and no orders are open before setting dual mode
https://www.gate.com/docs/developers/apiv4/en/#enable-or-disable-dual-mode
:param bool hedged: set to True to enable dual mode
:param str|None symbol: if passed, dual mode is set for all markets with the same settle currency
:param dict params: extra parameters specific to the exchange API endpoint
:param str params['settle']: settle currency
:returns dict: response from the exchange
"""
market = self.market(symbol) if (symbol is not None) else None
request, query = self.prepare_request(market, 'swap', params)
request['dual_mode'] = hedged
return await self.privateFuturesPostSettleDualMode(self.extend(request, query))
async def fetch_underlying_assets(self, params={}):
"""
fetches the market ids of underlying assets for a specific contract market type
https://www.gate.com/docs/developers/apiv4/en/#list-all-underlyings
:param dict [params]: exchange specific params
:param str [params.type]: the contract market type, 'option', 'swap' or 'future', the default is 'option'
:returns dict[]: a list of `underlying assets <https://docs.ccxt.com/#/?id=underlying-assets-structure>`
"""
await self.load_markets()
marketType = None
marketType, params = self.handle_market_type_and_params('fetchUnderlyingAssets', None, params)
if (marketType is None) or (marketType == 'spot'):
marketType = 'option'
if marketType != 'option':
raise NotSupported(self.id + ' fetchUnderlyingAssets() supports option markets only')
response = await self.publicOptionsGetUnderlyings(params)
#
# [
# {
# "index_time": "1646915796",
# "name": "BTC_USDT",
# "index_price": "39142.73"
# }
# ]
#
underlyings = []
for i in range(0, len(response)):
underlying = response[i]
name = self.safe_string(underlying, 'name')
if name is not None:
underlyings.append(name)
return underlyings
async def fetch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
"""
retrieves the public liquidations of a trading pair
https://www.gate.com/docs/developers/apiv4/en/#retrieve-liquidation-history
:param str symbol: unified CCXT market symbol
:param int [since]: the earliest time in ms to fetch liquidations for
:param int [limit]: the maximum number of liquidation structures to retrieve
:param dict [params]: exchange specific parameters for the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest liquidation
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
"""
await self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise NotSupported(self.id + ' fetchLiquidations() supports swap markets only')
request: dict = {
'settle': market['settleId'],
'contract': market['id'],
}
if since is not None:
request['from'] = since
if limit is not None:
request['limit'] = limit
request, params = self.handle_until_option('to', request, params)
response = await self.publicFuturesGetSettleLiqOrders(self.extend(request, params))
#
# [
# {
# "contract": "BTC_USDT",
# "left": 0,
# "size": -165,
# "fill_price": "28070",
# "order_price": "28225",
# "time": 1696736132
# },
# ]
#
return self.parse_liquidations(response, market, since, limit)
async def fetch_my_liquidations(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
retrieves the users liquidated positions
https://www.gate.com/docs/developers/apiv4/en/#list-liquidation-history
https://www.gate.com/docs/developers/apiv4/en/#list-liquidation-history-2
https://www.gate.com/docs/developers/apiv4/en/#list-user-s-liquidation-history-of-specified-underlying
:param str symbol: unified CCXT market symbol
:param int [since]: the earliest time in ms to fetch liquidations for
:param int [limit]: the maximum number of liquidation structures to retrieve
:param dict [params]: exchange specific parameters for the exchange API endpoint
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchMyLiquidations() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
request: dict = {
'contract': market['id'],
}
response = None
if (market['swap']) or (market['future']):
if limit is not None:
request['limit'] = limit
request['settle'] = market['settleId']
elif market['option']:
marketId = market['id']
optionParts = marketId.split('-')
request['underlying'] = self.safe_string(optionParts, 0)
if market['swap']:
response = await self.privateFuturesGetSettleLiquidates(self.extend(request, params))
elif market['future']:
response = await self.privateDeliveryGetSettleLiquidates(self.extend(request, params))
elif market['option']:
response = await self.privateOptionsGetPositionClose(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchMyLiquidations() does not support ' + market['type'] + ' orders')
#
# swap and future
#
# [
# {
# "time": 1548654951,
# "contract": "BTC_USDT",
# "size": 600,
# "leverage": "25",
# "margin": "0.006705256878",
# "entry_price": "3536.123",
# "liq_price": "3421.54",
# "mark_price": "3420.27",
# "order_id": 317393847,
# "order_price": "3405",
# "fill_price": "3424",
# "left": 0
# }
# ]
#
# option
#
# [
# {
# "time": 1631764800,
# "pnl": "-42914.291",
# "settle_size": "-10001",
# "side": "short",
# "contract": "BTC_USDT-20210916-5000-C",
# "text": "settled"
# }
# ]
#
return self.parse_liquidations(response, market, since, limit)
def parse_liquidation(self, liquidation, market: Market = None):
#
# fetchLiquidations
#
# {
# "contract": "BTC_USDT",
# "left": 0,
# "size": -165,
# "fill_price": "28070",
# "order_price": "28225",
# "time": 1696736132
# }
#
# swap and future: fetchMyLiquidations
#
# {
# "time": 1548654951,
# "contract": "BTC_USDT",
# "size": 600,
# "leverage": "25",
# "margin": "0.006705256878",
# "entry_price": "3536.123",
# "liq_price": "3421.54",
# "mark_price": "3420.27",
# "order_id": 317393847,
# "order_price": "3405",
# "fill_price": "3424",
# "left": 0
# }
#
# option: fetchMyLiquidations
#
# {
# "time": 1631764800,
# "pnl": "-42914.291",
# "settle_size": "-10001",
# "side": "short",
# "contract": "BTC_USDT-20210916-5000-C",
# "text": "settled"
# }
#
marketId = self.safe_string(liquidation, 'contract')
timestamp = self.safe_timestamp(liquidation, 'time')
size = self.safe_string_2(liquidation, 'size', 'settle_size')
left = self.safe_string(liquidation, 'left', '0')
contractsString = Precise.string_abs(Precise.string_sub(size, left))
contractSizeString = self.safe_string(market, 'contractSize')
priceString = self.safe_string_2(liquidation, 'liq_price', 'fill_price')
baseValueString = Precise.string_mul(contractsString, contractSizeString)
quoteValueString = self.safe_string(liquidation, 'pnl')
if quoteValueString is None:
quoteValueString = Precise.string_mul(baseValueString, priceString)
# --- derive side ---
# 1) options payload has explicit 'side': 'long' | 'short'
optPos = self.safe_string_lower(liquidation, 'side')
side: Str = None
if optPos == 'long':
side = 'buy'
elif optPos == 'short':
side = 'sell'
else:
if size is not None: # 2) futures/perpetual(and fallback for options): infer from size
if Precise.string_gt(size, '0'):
side = 'buy'
elif Precise.string_lt(size, '0'):
side = 'sell'
return self.safe_liquidation({
'info': liquidation,
'symbol': self.safe_symbol(marketId, market),
'contracts': self.parse_number(contractsString),
'contractSize': self.parse_number(contractSizeString),
'price': self.parse_number(priceString),
'side': side,
'baseValue': self.parse_number(baseValueString),
'quoteValue': self.parse_number(Precise.string_abs(quoteValueString)),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
})
async def fetch_greeks(self, symbol: str, params={}) -> Greeks:
"""
fetches an option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
https://www.gate.com/docs/developers/apiv4/en/#list-tickers-of-options-contracts
:param str symbol: unified symbol of the market to fetch greeks for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
"""
await self.load_markets()
market = self.market(symbol)
request: dict = {
'underlying': market['info']['underlying'],
}
response = await self.publicOptionsGetTickers(self.extend(request, params))
#
# [
# {
# "vega": "1.78992",
# "leverage": "6.2096777055417",
# "ask_iv": "0.6245",
# "delta": "-0.69397",
# "last_price": "0",
# "theta": "-2.5723",
# "bid1_price": "222.9",
# "mark_iv": "0.5909",
# "name": "ETH_USDT-20231201-2300-P",
# "bid_iv": "0.5065",
# "ask1_price": "243.6",
# "mark_price": "236.57",
# "position_size": 0,
# "bid1_size": 368,
# "ask1_size": -335,
# "gamma": "0.00116"
# },
# ]
#
marketId = market['id']
for i in range(0, len(response)):
entry = response[i]
entryMarketId = self.safe_string(entry, 'name')
if entryMarketId == marketId:
return self.parse_greeks(entry, market)
return None
def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
#
# {
# "vega": "1.78992",
# "leverage": "6.2096777055417",
# "ask_iv": "0.6245",
# "delta": "-0.69397",
# "last_price": "0",
# "theta": "-2.5723",
# "bid1_price": "222.9",
# "mark_iv": "0.5909",
# "name": "ETH_USDT-20231201-2300-P",
# "bid_iv": "0.5065",
# "ask1_price": "243.6",
# "mark_price": "236.57",
# "position_size": 0,
# "bid1_size": 368,
# "ask1_size": -335,
# "gamma": "0.00116"
# }
#
marketId = self.safe_string(greeks, 'name')
symbol = self.safe_symbol(marketId, market)
return {
'symbol': symbol,
'timestamp': None,
'datetime': None,
'delta': self.safe_number(greeks, 'delta'),
'gamma': self.safe_number(greeks, 'gamma'),
'theta': self.safe_number(greeks, 'theta'),
'vega': self.safe_number(greeks, 'vega'),
'rho': None,
'bidSize': self.safe_number(greeks, 'bid1_size'),
'askSize': self.safe_number(greeks, 'ask1_size'),
'bidImpliedVolatility': self.safe_number(greeks, 'bid_iv'),
'askImpliedVolatility': self.safe_number(greeks, 'ask_iv'),
'markImpliedVolatility': self.safe_number(greeks, 'mark_iv'),
'bidPrice': self.safe_number(greeks, 'bid1_price'),
'askPrice': self.safe_number(greeks, 'ask1_price'),
'markPrice': self.safe_number(greeks, 'mark_price'),
'lastPrice': self.safe_number(greeks, 'last_price'),
'underlyingPrice': self.parse_number(market['info']['underlying_price']),
'info': greeks,
}
async def close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order:
"""
closes open positions for a market
https://www.gate.com/docs/developers/apiv4/en/#create-a-futures-order
https://www.gate.com/docs/developers/apiv4/en/#create-a-futures-order-2
https://www.gate.com/docs/developers/apiv4/en/#create-an-options-order
:param str symbol: Unified CCXT market symbol
:param str side: 'buy' or 'sell'
:param dict [params]: extra parameters specific to the okx api endpoint
:returns dict[]: `A list of position structures <https://docs.ccxt.com/#/?id=position-structure>`
"""
request: dict = {
'close': True,
}
params = self.extend(request, params)
if side is None:
side = '' # side is not used but needs to be present, otherwise crashes in php
return await self.create_order(symbol, 'market', side, 0, None, params)
async def fetch_leverage(self, symbol: str, params={}) -> Leverage:
"""
fetch the set leverage for a market
https://www.gate.com/docs/developers/apiv4/en/#get-unified-account-information
https://www.gate.com/docs/developers/apiv4/en/#get-detail-of-lending-market
https://www.gate.com/docs/developers/apiv4/en/#query-one-single-margin-currency-pair-deprecated
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.unified]: default False, set to True for fetching the unified accounts leverage
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
"""
await self.load_markets()
market = None
if symbol is not None:
# unified account does not require a symbol
market = self.market(symbol)
request: dict = {}
response = None
isUnified = self.safe_bool(params, 'unified')
params = self.omit(params, 'unified')
if market['spot']:
request['currency_pair'] = market['id']
if isUnified:
response = await self.publicMarginGetUniCurrencyPairsCurrencyPair(self.extend(request, params))
#
# {
# "currency_pair": "BTC_USDT",
# "base_min_borrow_amount": "0.0001",
# "quote_min_borrow_amount": "1",
# "leverage": "10"
# }
#
else:
response = await self.publicMarginGetCurrencyPairsCurrencyPair(self.extend(request, params))
#
# {
# "id": "BTC_USDT",
# "base": "BTC",
# "quote": "USDT",
# "leverage": 10,
# "min_base_amount": "0.0001",
# "min_quote_amount": "1",
# "max_quote_amount": "40000000",
# "status": 1
# }
#
elif isUnified:
response = await self.privateUnifiedGetAccounts(self.extend(request, params))
#
# {
# "user_id": 10001,
# "locked": False,
# "balances": {
# "ETH": {
# "available": "0",
# "freeze": "0",
# "borrowed": "0.075393666654",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "1016.1",
# "total_freeze": "0",
# "total_liab": "0"
# },
# "POINT": {
# "available": "9999999999.017023138734",
# "freeze": "0",
# "borrowed": "0",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "12016.1",
# "total_freeze": "0",
# "total_liab": "0"
# },
# "USDT": {
# "available": "0.00000062023",
# "freeze": "0",
# "borrowed": "0",
# "negative_liab": "0",
# "futures_pos_liab": "0",
# "equity": "16.1",
# "total_freeze": "0",
# "total_liab": "0"
# }
# },
# "total": "230.94621713",
# "borrowed": "161.66395521",
# "total_initial_margin": "1025.0524665088",
# "total_margin_balance": "3382495.944473949183",
# "total_maintenance_margin": "205.01049330176",
# "total_initial_margin_rate": "3299.827135672679",
# "total_maintenance_margin_rate": "16499.135678363399",
# "total_available_margin": "3381470.892007440383",
# "unified_account_total": "3381470.892007440383",
# "unified_account_total_liab": "0",
# "unified_account_total_equity": "100016.1",
# "leverage": "2"
# }
#
else:
raise NotSupported(self.id + ' fetchLeverage() does not support ' + market['type'] + ' markets')
return self.parse_leverage(response, market)
async def fetch_leverages(self, symbols: Strings = None, params={}) -> Leverages:
"""
fetch the set leverage for all leverage markets, only spot margin is supported on gate
https://www.gate.com/docs/developers/apiv4/en/#list-lending-markets
https://www.gate.com/docs/developers/apiv4/en/#list-all-supported-currency-pairs-supported-in-margin-trading-deprecated
:param str[] symbols: a list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.unified]: default False, set to True for fetching unified account leverages
:returns dict: a list of `leverage structures <https://docs.ccxt.com/#/?id=leverage-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
response = None
isUnified = self.safe_bool(params, 'unified')
params = self.omit(params, 'unified')
marketIdRequest = 'id'
if isUnified:
marketIdRequest = 'currency_pair'
response = await self.publicMarginGetUniCurrencyPairs(params)
#
# [
# {
# "currency_pair": "1INCH_USDT",
# "base_min_borrow_amount": "8",
# "quote_min_borrow_amount": "1",
# "leverage": "3"
# },
# ]
#
else:
response = await self.publicMarginGetCurrencyPairs(params)
#
# [
# {
# "id": "1CAT_USDT",
# "base": "1CAT",
# "quote": "USDT",
# "leverage": 3,
# "min_base_amount": "71",
# "min_quote_amount": "1",
# "max_quote_amount": "10000",
# "status": 1
# },
# ]
#
return self.parse_leverages(response, symbols, marketIdRequest, 'spot')
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
marketId = self.safe_string_2(leverage, 'currency_pair', 'id')
leverageValue = self.safe_integer(leverage, 'leverage')
return {
'info': leverage,
'symbol': self.safe_symbol(marketId, market, '_', 'spot'),
'marginMode': None,
'longLeverage': leverageValue,
'shortLeverage': leverageValue,
}
async def fetch_option(self, symbol: str, params={}) -> Option:
"""
fetches option data that is commonly found in an option chain
https://www.gate.com/docs/developers/apiv4/en/#query-specified-contract-detail
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `option chain structure <https://docs.ccxt.com/#/?id=option-chain-structure>`
"""
await self.load_markets()
market = self.market(symbol)
request: dict = {
'contract': market['id'],
}
response = await self.publicOptionsGetContractsContract(self.extend(request, params))
#
# {
# "is_active": True,
# "mark_price_round": "0.01",
# "settle_fee_rate": "0.00015",
# "bid1_size": 30,
# "taker_fee_rate": "0.0003",
# "price_limit_fee_rate": "0.1",
# "order_price_round": "0.1",
# "tag": "month",
# "ref_rebate_rate": "0",
# "name": "ETH_USDT-20240628-4500-C",
# "strike_price": "4500",
# "ask1_price": "280.5",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.2",
# "ask1_size": -19,
# "mark_price_down": "155.45",
# "orderbook_id": 11724695,
# "is_call": True,
# "last_price": "188.7",
# "mark_price": "274.26",
# "underlying": "ETH_USDT",
# "create_time": 1688024882,
# "settle_limit_fee_rate": "0.1",
# "orders_limit": 10,
# "mark_price_up": "403.83",
# "position_size": 80,
# "order_size_max": 10000,
# "position_limit": 100000,
# "multiplier": "0.01",
# "order_size_min": 1,
# "trade_size": 229,
# "underlying_price": "3326.6",
# "maker_fee_rate": "0.0003",
# "expiration_time": 1719561600,
# "trade_id": 15,
# "bid1_price": "269.3"
# }
#
return self.parse_option(response, None, market)
async def fetch_option_chain(self, code: str, params={}) -> OptionChain:
"""
fetches data for an underlying asset that is commonly found in an option chain
https://www.gate.com/docs/developers/apiv4/en/#list-all-the-contracts-with-specified-underlying-and-expiration-time
:param str code: base currency to fetch an option chain for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.underlying]: the underlying asset, can be obtained from fetchUnderlyingAssets()
:param int [params.expiration]: unix timestamp of the expiration time
:returns dict: a list of `option chain structures <https://docs.ccxt.com/#/?id=option-chain-structure>`
"""
await self.load_markets()
currency = self.currency(code)
request: dict = {
'underlying': currency['code'] + '_USDT', # todo: currency['id'].upper() & network junctions
}
response = await self.publicOptionsGetContracts(self.extend(request, params))
#
# [
# {
# "is_active": True,
# "mark_price_round": "0.1",
# "settle_fee_rate": "0.00015",
# "bid1_size": 434,
# "taker_fee_rate": "0.0003",
# "price_limit_fee_rate": "0.1",
# "order_price_round": "1",
# "tag": "day",
# "ref_rebate_rate": "0",
# "name": "BTC_USDT-20240324-63500-P",
# "strike_price": "63500",
# "ask1_price": "387",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.15",
# "ask1_size": -454,
# "mark_price_down": "124.3",
# "orderbook_id": 29600,
# "is_call": False,
# "last_price": "0",
# "mark_price": "366.6",
# "underlying": "BTC_USDT",
# "create_time": 1711118829,
# "settle_limit_fee_rate": "0.1",
# "orders_limit": 10,
# "mark_price_up": "630",
# "position_size": 0,
# "order_size_max": 10000,
# "position_limit": 10000,
# "multiplier": "0.01",
# "order_size_min": 1,
# "trade_size": 0,
# "underlying_price": "64084.65",
# "maker_fee_rate": "0.0003",
# "expiration_time": 1711267200,
# "trade_id": 0,
# "bid1_price": "307"
# },
# ]
#
return self.parse_option_chain(response, None, 'name')
def parse_option(self, chain: dict, currency: Currency = None, market: Market = None) -> Option:
#
# {
# "is_active": True,
# "mark_price_round": "0.1",
# "settle_fee_rate": "0.00015",
# "bid1_size": 434,
# "taker_fee_rate": "0.0003",
# "price_limit_fee_rate": "0.1",
# "order_price_round": "1",
# "tag": "day",
# "ref_rebate_rate": "0",
# "name": "BTC_USDT-20240324-63500-P",
# "strike_price": "63500",
# "ask1_price": "387",
# "ref_discount_rate": "0",
# "order_price_deviate": "0.15",
# "ask1_size": -454,
# "mark_price_down": "124.3",
# "orderbook_id": 29600,
# "is_call": False,
# "last_price": "0",
# "mark_price": "366.6",
# "underlying": "BTC_USDT",
# "create_time": 1711118829,
# "settle_limit_fee_rate": "0.1",
# "orders_limit": 10,
# "mark_price_up": "630",
# "position_size": 0,
# "order_size_max": 10000,
# "position_limit": 10000,
# "multiplier": "0.01",
# "order_size_min": 1,
# "trade_size": 0,
# "underlying_price": "64084.65",
# "maker_fee_rate": "0.0003",
# "expiration_time": 1711267200,
# "trade_id": 0,
# "bid1_price": "307"
# }
#
marketId = self.safe_string(chain, 'name')
market = self.safe_market(marketId, market)
timestamp = self.safe_timestamp(chain, 'create_time')
return {
'info': chain,
'currency': None,
'symbol': market['symbol'],
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'impliedVolatility': None,
'openInterest': None,
'bidPrice': self.safe_number(chain, 'bid1_price'),
'askPrice': self.safe_number(chain, 'ask1_price'),
'midPrice': None,
'markPrice': self.safe_number(chain, 'mark_price'),
'lastPrice': self.safe_number(chain, 'last_price'),
'underlyingPrice': self.safe_number(chain, 'underlying_price'),
'change': None,
'percentage': None,
'baseVolume': None,
'quoteVolume': None,
}
async def fetch_positions_history(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
"""
fetches historical positions
https://www.gate.com/docs/developers/apiv4/#list-position-close-history
https://www.gate.com/docs/developers/apiv4/#list-position-close-history-2
:param str[] symbols: unified conract symbols, must all have the same settle currency and the same market type
:param int [since]: the earliest time in ms to fetch positions for
:param int [limit]: the maximum amount of records to fetch, default=1000
:param dict params: extra parameters specific to the exchange api endpoint
:param int [params.until]: the latest time in ms to fetch positions for
EXCHANGE SPECIFIC PARAMETERS
:param int [params.offset]: list offset, starting from 0
:param str [params.side]: long or short
:param str [params.pnl]: query profit or loss
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
"""
await self.load_markets()
market = None
if symbols is not None:
symbolsLength = len(symbols)
if symbolsLength == 1:
market = self.market(symbols[0])
marketType = None
marketType, params = self.handle_market_type_and_params('fetchPositionsHistory', market, params, 'swap')
until = self.safe_integer(params, 'until')
params = self.omit(params, 'until')
request: dict = {}
request, params = self.prepare_request(market, marketType, params)
if limit is not None:
request['limit'] = limit
if since is not None:
request['from'] = self.parse_to_int(since / 1000)
if until is not None:
request['to'] = self.parse_to_int(until / 1000)
response = None
if marketType == 'swap':
response = await self.privateFuturesGetSettlePositionClose(self.extend(request, params))
elif marketType == 'future':
response = await self.privateDeliveryGetSettlePositionClose(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchPositionsHistory() does not support markets of type ' + marketType)
#
# [
# {
# "contract": "SLERF_USDT",
# "text": "web",
# "long_price": "0.766306",
# "pnl": "-23.41702352",
# "pnl_pnl": "-22.7187",
# "pnl_fee": "-0.06527125",
# "pnl_fund": "-0.63305227",
# "accum_size": "100",
# "time": 1711279263,
# "short_price": "0.539119",
# "side": "long",
# "max_size": "100",
# "first_open_time": 1711037985
# },
# ...
# ]
#
return self.parse_positions(response, symbols, params)
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
#
# {"label": "ORDER_NOT_FOUND", "message": "Order not found"}
# {"label": "INVALID_PARAM_VALUE", "message": "invalid argument: status"}
# {"label": "INVALID_PARAM_VALUE", "message": "invalid argument: Trigger.rule"}
# {"label": "INVALID_PARAM_VALUE", "message": "invalid argument: trigger.expiration invalid range"}
# {"label": "INVALID_ARGUMENT", "detail": "invalid size"}
# {"user_id":10406147,"id":"id","succeeded":false,"message":"INVALID_PROTOCOL","label":"INVALID_PROTOCOL"}
#
label = self.safe_string(response, 'label')
if label is not None:
feedback = self.id + ' ' + body
self.throw_exactly_matched_exception(self.exceptions['exact'], label, feedback)
raise ExchangeError(feedback)
return None