5179 lines
240 KiB
Python
5179 lines
240 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.coincatch import ImplicitAPI
|
||
import hashlib
|
||
import math
|
||
import json
|
||
from ccxt.base.types import Any, Balances, Bool, Currencies, Currency, DepositAddress, Int, LedgerEntry, Leverage, MarginMode, MarginModification, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, Transaction, TransferEntry
|
||
from typing import List
|
||
from ccxt.base.errors import ExchangeError
|
||
from ccxt.base.errors import AuthenticationError
|
||
from ccxt.base.errors import PermissionDenied
|
||
from ccxt.base.errors import ArgumentsRequired
|
||
from ccxt.base.errors import BadRequest
|
||
from ccxt.base.errors import BadSymbol
|
||
from ccxt.base.errors import InsufficientFunds
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import DDoSProtection
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import OnMaintenance
|
||
from ccxt.base.errors import InvalidNonce
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class coincatch(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(coincatch, self).describe(), {
|
||
'id': 'coincatch',
|
||
'name': 'CoinCatch',
|
||
'countries': ['VG'], # British Virgin Islands
|
||
'rateLimit': 50, # 20 times per second
|
||
'version': 'v1',
|
||
'certified': False,
|
||
'pro': True,
|
||
'has': {
|
||
'CORS': None,
|
||
'spot': True,
|
||
'margin': False,
|
||
'swap': True,
|
||
'future': False,
|
||
'option': False,
|
||
'addMargin': True,
|
||
'cancelAllOrders': True,
|
||
'cancelAllOrdersAfter': False,
|
||
'cancelOrder': True,
|
||
'cancelOrders': True,
|
||
'cancelWithdraw': False,
|
||
'closePosition': False,
|
||
'createConvertTrade': False,
|
||
'createDepositAddress': False,
|
||
'createLimitBuyOrder': True,
|
||
'createLimitSellOrder': True,
|
||
'createMarketBuyOrder': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrder': True,
|
||
'createMarketOrderWithCost': False,
|
||
'createMarketSellOrder': True,
|
||
'createMarketSellOrderWithCost': False,
|
||
'createOrder': True,
|
||
'createOrders': True,
|
||
'createOrderWithTakeProfitAndStopLoss': True,
|
||
'createPostOnlyOrder': True,
|
||
'createReduceOnlyOrder': True,
|
||
'createStopLimitOrder': True,
|
||
'createStopLossOrder': True,
|
||
'createStopMarketOrder': True,
|
||
'createStopOrder': True,
|
||
'createTakeProfitOrder': True,
|
||
'createTrailingAmountOrder': False,
|
||
'createTrailingPercentOrder': False,
|
||
'createTriggerOrder': True,
|
||
'fetchAccounts': False,
|
||
'fetchBalance': True,
|
||
'fetchCanceledAndClosedOrders': True,
|
||
'fetchCanceledOrders': False,
|
||
'fetchClosedOrder': False,
|
||
'fetchClosedOrders': False,
|
||
'fetchConvertCurrencies': False,
|
||
'fetchConvertQuote': False,
|
||
'fetchConvertTrade': False,
|
||
'fetchConvertTradeHistory': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDepositAddress': True,
|
||
'fetchDeposits': True,
|
||
'fetchDepositsWithdrawals': False,
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': False,
|
||
'fetchFundingRate': True,
|
||
'fetchFundingRateHistory': True,
|
||
'fetchFundingRates': False,
|
||
'fetchIndexOHLCV': False,
|
||
'fetchLedger': True,
|
||
'fetchLeverage': True,
|
||
'fetchLeverageTiers': False,
|
||
'fetchMarginAdjustmentHistory': False,
|
||
'fetchMarginMode': True,
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': True,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterestHistory': False,
|
||
'fetchOpenOrder': False,
|
||
'fetchOpenOrders': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrders': False,
|
||
'fetchOrderTrades': True,
|
||
'fetchPosition': True,
|
||
'fetchPositionHistory': False,
|
||
'fetchPositionMode': True,
|
||
'fetchPositions': True,
|
||
'fetchPositionsForSymbol': True,
|
||
'fetchPositionsHistory': False,
|
||
'fetchPremiumIndexOHLCV': False,
|
||
'fetchStatus': False,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': False,
|
||
'fetchTradingFees': False,
|
||
'fetchTransactions': False,
|
||
'fetchTransfers': False,
|
||
'fetchWithdrawals': True,
|
||
'reduceMargin': True,
|
||
'sandbox': False,
|
||
'setLeverage': True,
|
||
'setMargin': False,
|
||
'setMarginMode': True,
|
||
'setPositionMode': True,
|
||
'transfer': False,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1m': '1m',
|
||
'3m': '3m',
|
||
'5m': '5m',
|
||
'15': '15m',
|
||
'30': '30m',
|
||
'1h': '1H',
|
||
'2h': '2H',
|
||
'4h': '4H',
|
||
'6h': '6H',
|
||
'12h': '12H',
|
||
'1d': '1D',
|
||
'3d': '3D',
|
||
'1w': '1W',
|
||
'1M': '1M',
|
||
},
|
||
'urls': {
|
||
'logo': 'https://github.com/user-attachments/assets/3d49065f-f05d-4573-88a2-1b5201ec6ff3',
|
||
'api': {
|
||
'public': 'https://api.coincatch.com',
|
||
'private': 'https://api.coincatch.com',
|
||
},
|
||
'www': 'https://www.coincatch.com/',
|
||
'doc': 'https://coincatch.github.io/github.io/en/',
|
||
'fees': 'https://www.coincatch.com/en/rate/',
|
||
'referral': {
|
||
'url': 'https://partner.coincatch.cc/bg/92hy70391729607848548',
|
||
'discount': 0.1,
|
||
},
|
||
},
|
||
'api': {
|
||
'public': {
|
||
'get': {
|
||
'api/spot/v1/public/time': 1, # done
|
||
'api/spot/v1/public/currencies': 20 / 3, # done
|
||
'api/spot/v1/market/ticker': 1, # done
|
||
'api/spot/v1/market/tickers': 1, # done
|
||
'api/spot/v1/market/fills': 2, # not used
|
||
'api/spot/v1/market/fills-history': 2, # done
|
||
'api/spot/v1/market/candles': 1, # done
|
||
'api/spot/v1/market/history-candles': 1, # not used
|
||
'api/spot/v1/market/depth': 1, # not used
|
||
'api/spot/v1/market/merge-depth': 1, # done
|
||
'api/mix/v1/market/contracts': 1, # done
|
||
'api/mix/v1/market/merge-depth': 1, # done
|
||
'api/mix/v1/market/depth': 1, # not used
|
||
'api/mix/v1/market/ticker': 1, # done
|
||
'api/mix/v1/market/tickers': 1, # done
|
||
'api/mix/v1/market/fills': 1, # not used
|
||
'api/mix/v1/market/fills-history': 1, # done
|
||
'api/mix/v1/market/candles': 1, # done
|
||
'pi/mix/v1/market/index': 1,
|
||
'api/mix/v1/market/funding-time': 1,
|
||
'api/mix/v1/market/history-fundRate': 1, # done
|
||
'api/mix/v1/market/current-fundRate': 1, # done
|
||
'api/mix/v1/market/open-interest': 1,
|
||
'api/mix/v1/market/mark-price': 1,
|
||
'api/mix/v1/market/symbol-leverage': 1, # done
|
||
'api/mix/v1/market/queryPositionLever': 1,
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
'api/spot/v1/wallet/deposit-address': 4, # done
|
||
'pi/spot/v1/wallet/withdrawal-list': 1, # not used
|
||
'api/spot/v1/wallet/withdrawal-list-v2': 1, # done but should be checked
|
||
'api/spot/v1/wallet/deposit-list': 1, # done
|
||
'api/spot/v1/account/getInfo': 1,
|
||
'api/spot/v1/account/assets': 2, # done
|
||
'api/spot/v1/account/transferRecords': 1,
|
||
'api/mix/v1/account/account': 2, # done
|
||
'api/mix/v1/account/accounts': 2, # done
|
||
'api/mix/v1/position/singlePosition-v2': 2, # done
|
||
'api/mix/v1/position/allPosition-v2': 4, # done
|
||
'api/mix/v1/account/accountBill': 2,
|
||
'api/mix/v1/account/accountBusinessBill': 4,
|
||
'api/mix/v1/order/current': 1, # done
|
||
'api/mix/v1/order/marginCoinCurrent': 1, # done
|
||
'api/mix/v1/order/history': 2, # done
|
||
'api/mix/v1/order/historyProductType': 4, # done
|
||
'api/mix/v1/order/detail': 2, # done
|
||
'api/mix/v1/order/fills': 2, # done
|
||
'api/mix/v1/order/allFills': 2, # done
|
||
'api/mix/v1/plan/currentPlan': 1, # done
|
||
'api/mix/v1/plan/historyPlan': 2, # done
|
||
},
|
||
'post': {
|
||
'api/spot/v1/wallet/transfer-v2': 4, # done
|
||
'api/spot/v1/wallet/withdrawal-v2': 4, # done but should be checked
|
||
'api/spot/v1/wallet/withdrawal-inner-v2': 1,
|
||
'api/spot/v1/account/bills': 2, # done
|
||
'api/spot/v1/trade/orders': 2, # done
|
||
'api/spot/v1/trade/batch-orders': {'cost': 4, 'step': 10}, # done
|
||
'api/spot/v1/trade/cancel-order': 1, # not used
|
||
'api/spot/v1/trade/cancel-order-v2': 2, # done
|
||
'api/spot/v1/trade/cancel-symbol-order': 2, # done
|
||
'api/spot/v1/trade/cancel-batch-orders': 1, # not used
|
||
'api/spot/v1/trade/cancel-batch-orders-v2': 1, # done
|
||
'api/spot/v1/trade/orderInfo': 1, # done
|
||
'api/spot/v1/trade/open-orders': 1, # done
|
||
'api/spot/v1/trade/history': 1, # done
|
||
'api/spot/v1/trade/fills': 1, # done
|
||
'api/spot/v1/plan/placePlan': 1, # done
|
||
'api/spot/v1/plan/modifyPlan': 1, # done
|
||
'api/spot/v1/plan/cancelPlan': 1, # done
|
||
'api/spot/v1/plan/currentPlan': 1, # done
|
||
'api/spot/v1/plan/historyPlan': 1, # done
|
||
'api/spot/v1/plan/batchCancelPlan': 2, # done
|
||
'api/mix/v1/account/open-count': 1,
|
||
'api/mix/v1/account/setLeverage': 4, # done
|
||
'api/mix/v1/account/setMargin': 4, # done
|
||
'api/mix/v1/account/setMarginMode': 4, # done
|
||
'api/mix/v1/account/setPositionMode': 4, # done
|
||
'api/mix/v1/order/placeOrder': 2, # done
|
||
'api/mix/v1/order/batch-orders': {'cost': 4, 'step': 10}, # done
|
||
'api/mix/v1/order/cancel-order': 2, # done
|
||
'api/mix/v1/order/cancel-batch-orders': 2, # done
|
||
'api/mix/v1/order/cancel-symbol-orders': 2, # done
|
||
'api/mix/v1/order/cancel-all-orders': 2, # done
|
||
'api/mix/v1/plan/placePlan': 2, # done
|
||
'api/mix/v1/plan/modifyPlan': 2,
|
||
'api/mix/v1/plan/modifyPlanPreset': 2,
|
||
'api/mix/v1/plan/placeTPSL': 2, # done
|
||
'api/mix/v1/plan/placeTrailStop': 2, # not used
|
||
'api/mix/v1/plan/placePositionsTPSL': 2, # not used
|
||
'api/mix/v1/plan/modifyTPSLPlan': 2,
|
||
'api/mix/v1/plan/cancelPlan': 2, # done
|
||
'api/mix/v1/plan/cancelSymbolPlan': 2, # done
|
||
'api/mix/v1/plan/cancelAllPlan': 2, # done
|
||
},
|
||
},
|
||
},
|
||
'requiredCredentials': {
|
||
'apiKey': True,
|
||
'secret': True,
|
||
'password': True,
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'spot': {
|
||
'tierBased': False,
|
||
'percentage': True,
|
||
'feeSide': 'get',
|
||
'maker': self.parse_number('0.001'),
|
||
'taker': self.parse_number('0.001'),
|
||
},
|
||
},
|
||
},
|
||
'options': {
|
||
'brokerId': '47cfy',
|
||
'createMarketBuyOrderRequiresPrice': True, # for spot orders only
|
||
'timeframes': {
|
||
'spot': {
|
||
'1m': '1min',
|
||
'5m': '5min',
|
||
'15m': '15min',
|
||
'30m': '30min',
|
||
'1h': '1h',
|
||
'4h': '4h',
|
||
'6h': '6h',
|
||
'12h': '12h',
|
||
'1d': '1day',
|
||
'3d': '3day',
|
||
'1w': '1week',
|
||
'1M': '1M',
|
||
},
|
||
'swap': {
|
||
'1m': '1m',
|
||
'3m': '3m',
|
||
'5m': '5m',
|
||
'15': '15m',
|
||
'30': '30m',
|
||
'1h': '1H',
|
||
'2h': '2H',
|
||
'4h': '4H',
|
||
'6h': '6H',
|
||
'12h': '12H',
|
||
'1d': '1D',
|
||
'3d': '3D',
|
||
'1w': '1W',
|
||
'1M': '1M',
|
||
},
|
||
},
|
||
'currencyIdsListForParseMarket': None,
|
||
'broker': '',
|
||
'networks': {
|
||
'BTC': 'BITCOIN',
|
||
'ERC20': 'ERC20',
|
||
'TRC20': 'TRC20',
|
||
'BEP20': 'BEP20',
|
||
'ARB': 'ArbitrumOne',
|
||
'OPTIMISM': 'Optimism',
|
||
'LTC': 'LTC',
|
||
'BCH': 'BCH',
|
||
'ETC': 'ETC',
|
||
'SOL': 'SOL',
|
||
'NEO3': 'NEO3',
|
||
'STX': 'stacks',
|
||
'EGLD': 'Elrond',
|
||
'NEAR': 'NEARProtocol',
|
||
'ACA': 'AcalaToken',
|
||
'KLAY': 'Klaytn',
|
||
'FTM': 'Fantom',
|
||
'TERRA': 'Terra',
|
||
'WAVES': 'WAVES',
|
||
'TAO': 'TAO',
|
||
'SUI': 'SUI',
|
||
'SEI': 'SEI',
|
||
'RUNE': 'THORChain',
|
||
'ZIL': 'ZIL',
|
||
'SXP': 'Solar',
|
||
'FET': 'FET',
|
||
'AVAX': 'C-Chain',
|
||
'XRP': 'XRP',
|
||
'EOS': 'EOS',
|
||
'DOGE': 'DOGECOIN',
|
||
'CAP20': 'CAP20',
|
||
'MATIC': 'Polygon',
|
||
'CSPR': 'CSPR',
|
||
'GLMR': 'Moonbeam',
|
||
'MINA': 'MINA',
|
||
'CFX': 'CFX',
|
||
'STRAT': 'StratisEVM',
|
||
'TIA': 'Celestia',
|
||
'ChilizChain': 'ChilizChain',
|
||
'APT': 'Aptos',
|
||
'ONT': 'Ontology',
|
||
'ICP': 'ICP',
|
||
'ADA': 'Cardano',
|
||
'FIL': 'FIL',
|
||
'CELO': 'CELO',
|
||
'DOT': 'DOT',
|
||
'XLM': 'StellarLumens',
|
||
'ATOM': 'ATOM',
|
||
'CRO': 'CronosChain',
|
||
},
|
||
'networksById': {
|
||
'TRC20': 'TRC20',
|
||
'TRX(TRC20)': 'TRC20',
|
||
'ArbitrumOne': 'ARB', # todo check
|
||
'THORChain': 'RUNE', # todo check
|
||
'Solar': 'SXP', # todo check
|
||
'C-Chain': 'AVAX', # todo check
|
||
'CAP20': 'CAP20', # todo check
|
||
'CFXeSpace': 'CFX', # todo check
|
||
'CFX': 'CFX',
|
||
'StratisEVM': 'STRAT', # todo check
|
||
'ChilizChain': 'ChilizChain', # todo check
|
||
'StellarLumens': 'XLM', # todo check
|
||
'CronosChain': 'CRO', # todo check
|
||
'Optimism': 'Optimism',
|
||
},
|
||
},
|
||
'features': {
|
||
'default': {
|
||
'sandbox': False,
|
||
'createOrder': {
|
||
'marginMode': False,
|
||
'triggerPrice': True,
|
||
'triggerPriceType': {
|
||
'last': True,
|
||
'mark': True,
|
||
'index': False,
|
||
},
|
||
'triggerDirection': False,
|
||
'stopLossPrice': False, # todo
|
||
'takeProfitPrice': False, # todo
|
||
'attachedStopLossTakeProfit': None,
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': False,
|
||
},
|
||
'hedged': False,
|
||
'trailing': False,
|
||
'leverage': False,
|
||
'marketBuyByCost': True,
|
||
'marketBuyRequiresPrice': False,
|
||
'selfTradePrevention': False,
|
||
'iceberg': False,
|
||
},
|
||
'createOrders': {
|
||
'max': 50,
|
||
},
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'limit': 500,
|
||
'daysBack': 100000, # todo implement
|
||
'untilDays': 100000, # todo implement
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': False,
|
||
'limit': 100,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'marketType': True,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': None,
|
||
'fetchClosedOrders': None, # todo implement
|
||
'fetchOHLCV': {
|
||
'limit': 1000,
|
||
},
|
||
},
|
||
'spot': {
|
||
'extends': 'default',
|
||
},
|
||
'forDerivatives': {
|
||
'extends': 'default',
|
||
'createOrder': {
|
||
# todo check
|
||
'attachedStopLossTakeProfit': {
|
||
'triggerPriceType': None,
|
||
'price': False,
|
||
},
|
||
},
|
||
'fetchMyTrades': {
|
||
'limit': 100,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
'inverse': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
},
|
||
'future': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
},
|
||
'commonCurrencies': {},
|
||
'exceptions': {
|
||
'exact': {
|
||
'22001': OrderNotFound, # No order to cancel
|
||
'429': DDoSProtection, # Request is too frequent
|
||
'40001': AuthenticationError, # The request header "ACCESS_KEY" cannot be empty
|
||
'40002': AuthenticationError, # The request header "ACCESS_SIGN" cannot be empty
|
||
'40003': AuthenticationError, # The request header "ACCESS_TIMESTAMP" cannot be empty
|
||
'40005': InvalidNonce, # Invalid ACCESS_TIMESTAMP
|
||
'40006': AuthenticationError, # Invalid ACCESS_KEY
|
||
'40007': BadRequest, # Invalid Content_Type,please use“application/json”format
|
||
'40008': InvalidNonce, # Request timestamp expired
|
||
'40009': AuthenticationError, # api verification failed
|
||
'40011': AuthenticationError, # The request header "ACCESS_PASSPHRASE" cannot be empty
|
||
'40012': AuthenticationError, # apikey/passphrase is incorrect
|
||
'40013': ExchangeError, # User has been frozen
|
||
'40014': PermissionDenied, # Incorrect permissions
|
||
'40015': ExchangeError, # System error
|
||
'40016': PermissionDenied, # The user must bind a mobile phone or Google authenticator
|
||
'40017': ExchangeError, # Parameter verification failed
|
||
'40018': PermissionDenied, # Illegal IP request
|
||
'40019': BadRequest, # Parameter {0} cannot be empty
|
||
'40020': BadRequest, # Parameter orderIds or clientOids error
|
||
'40034': BadRequest, # Parameter {0} does not exist
|
||
'400172': BadRequest, # symbol cannot be empty
|
||
'40912': BadRequest, # Batch processing orders can only process up to 50
|
||
'40913': BadRequest, # orderId or clientOrderId must be passed one
|
||
'40102': BadRequest, # The contract configuration does not exist, please check the parameters
|
||
'40200': OnMaintenance, # Server upgrade, please try again later
|
||
'40305': BadRequest, # client_oid length is not greater than 40, and cannot be Martian characters
|
||
'40409': ExchangeError, # wrong format
|
||
'40704': ExchangeError, # Only check the data of the last three months
|
||
'40724': BadRequest, # Parameter is empty
|
||
'40725': ExchangeError, # spot service return an error
|
||
'40762': InsufficientFunds, # The order amount exceeds the balance
|
||
'40774': BadRequest, # The order type for unilateral position must also be the unilateral position type.
|
||
'40808': BadRequest, # Parameter verification exception {0}
|
||
'43001': OrderNotFound, # The order does not exist
|
||
'43002': InvalidOrder, # Pending order failed
|
||
'43004': OrderNotFound, # There is no order to cancel
|
||
'43005': RateLimitExceeded, # Exceeded the maximum order limit of transaction volume
|
||
'43006': BadRequest, # The order quantity is less than the minimum transaction quantity
|
||
'43007': BadRequest, # The order quantity is greater than the maximum transaction quantity
|
||
'43008': BadRequest, # The current order price cannot be less than {0}
|
||
'43009': BadRequest, # The current commission price exceeds the limit {0}
|
||
'43010': BadRequest, # The transaction amount cannot be less than {0}
|
||
'43011': BadRequest, # The current order price cannot be less than {0}
|
||
'43012': InsufficientFunds, # {"code":"43012","msg":"Insufficient balance","requestTime":1729327822139,"data":null}
|
||
'43117': InsufficientFunds, # Exceeds the maximum amount that can be transferred
|
||
'43118': BadRequest, # clientOrderId duplicate
|
||
'43122': BadRequest, # The purchase limit of self currency is {0}, and there is still {1} left
|
||
'45006': InsufficientFunds, # Insufficient position
|
||
'45110': BadRequest, # less than the minimum amount {0} {1}
|
||
# {"code":"40913","msg":"orderId or clientOrderId must be passed one","requestTime":1726160988275,"data":null}
|
||
},
|
||
'broad': {},
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
})
|
||
|
||
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
|
||
step = self.safe_integer(config, 'step')
|
||
cost = self.safe_integer(config, 'cost', 1)
|
||
orders = self.safe_list_2(params, 'orderList', 'orderDataList', [])
|
||
ordersLength = len(orders)
|
||
if (step is not None) and (ordersLength > step):
|
||
numberOfSteps = int(math.ceil(ordersLength / step))
|
||
return cost * numberOfSteps
|
||
else:
|
||
return cost
|
||
|
||
async def fetch_time(self, params={}) -> Int:
|
||
"""
|
||
fetches the current integer timestamp in milliseconds from the exchange server
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-server-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.publicGetApiSpotV1PublicTime(params)
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725046822028,
|
||
# "data": "1725046822028"
|
||
# }
|
||
#
|
||
return self.safe_integer(response, 'data')
|
||
|
||
async def fetch_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies on an exchange
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-coin-list
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
response = await self.publicGetApiSpotV1PublicCurrencies(params)
|
||
data = self.safe_list(response, 'data', [])
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725102364202,
|
||
# "data": [
|
||
# {
|
||
# "coinId": "1",
|
||
# "coinName": "BTC",
|
||
# "transfer": "true",
|
||
# "chains": [
|
||
# {
|
||
# "chainId": "10",
|
||
# "chain": "BITCOIN",
|
||
# "needTag": "false",
|
||
# "withdrawable": "true",
|
||
# "rechargeable": "true",
|
||
# "withdrawFee": "0.0005",
|
||
# "extraWithDrawFee": "0",
|
||
# "depositConfirm": "1",
|
||
# "withdrawConfirm": "1",
|
||
# "minDepositAmount": "0.00001",
|
||
# "minWithdrawAmount": "0.001",
|
||
# "browserUrl": "https://blockchair.com/bitcoin/transaction/"
|
||
# }
|
||
# ]
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
result: dict = {}
|
||
currenciesIds = []
|
||
for i in range(0, len(data)):
|
||
currecy = data[i]
|
||
currencyId = self.safe_string(currecy, 'coinName')
|
||
currenciesIds.append(currencyId)
|
||
code = self.safe_currency_code(currencyId)
|
||
networks = self.safe_list(currecy, 'chains')
|
||
parsedNetworks: dict = {}
|
||
for j in range(0, len(networks)):
|
||
network = networks[j]
|
||
networkId = self.safe_string(network, 'chain')
|
||
networkCode = self.network_id_to_code(networkId)
|
||
parsedNetworks[networkCode] = {
|
||
'id': networkId,
|
||
'network': networkCode,
|
||
'limits': {
|
||
'deposit': {
|
||
'min': self.safe_number(network, 'minDepositAmount'),
|
||
'max': None,
|
||
},
|
||
'withdraw': {
|
||
'min': self.safe_number(network, 'minWithdrawAmount'),
|
||
'max': None,
|
||
},
|
||
},
|
||
'active': None,
|
||
'deposit': self.safe_string(network, 'rechargeable') == 'true',
|
||
'withdraw': self.safe_string(network, 'withdrawable') == 'true',
|
||
'fee': self.safe_number(network, 'withdrawFee'),
|
||
'precision': None,
|
||
'info': network,
|
||
}
|
||
result[code] = self.safe_currency_structure({
|
||
'id': currencyId,
|
||
'numericId': self.safe_integer(currecy, 'coinId'),
|
||
'code': code,
|
||
'precision': None,
|
||
'type': None,
|
||
'name': None,
|
||
'active': None,
|
||
'deposit': None,
|
||
'withdraw': None,
|
||
'fee': None,
|
||
'limits': {
|
||
'deposit': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'withdraw': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'networks': parsedNetworks,
|
||
'info': currecy,
|
||
})
|
||
if self.safe_list(self.options, 'currencyIdsListForParseMarket') is None:
|
||
self.options['currencyIdsListForParseMarket'] = currenciesIds
|
||
return result
|
||
|
||
async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
fetch deposit and withdraw fees
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-coin-list
|
||
|
||
:param str[] [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.publicGetApiSpotV1PublicCurrencies(params)
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_deposit_withdraw_fees(data, codes, 'coinName')
|
||
|
||
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "coinId":"1",
|
||
# "coinName":"BTC",
|
||
# "transfer":"true",
|
||
# "chains":[
|
||
# {
|
||
# "chain":null,
|
||
# "needTag":"false",
|
||
# "withdrawable":"true",
|
||
# "rechargeAble":"true",
|
||
# "withdrawFee":"0.005",
|
||
# "depositConfirm":"1",
|
||
# "withdrawConfirm":"1",
|
||
# "minDepositAmount":"0.001",
|
||
# "minWithdrawAmount":"0.001",
|
||
# "browserUrl":"https://blockchair.com/bitcoin/testnet/transaction/"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
chains = self.safe_list(fee, 'chains', [])
|
||
chainsLength = len(chains)
|
||
result: dict = {
|
||
'info': fee,
|
||
'withdraw': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'networks': {},
|
||
}
|
||
for i in range(0, chainsLength):
|
||
chain = chains[i]
|
||
networkId = self.safe_string(chain, 'chain')
|
||
currencyCode = self.safe_string(currency, 'code')
|
||
networkCode = self.network_id_to_code(networkId, currencyCode)
|
||
result['networks'][networkCode] = {
|
||
'deposit': {'fee': None, 'percentage': None},
|
||
'withdraw': {'fee': self.safe_number(chain, 'withdrawFee'), 'percentage': False},
|
||
}
|
||
if chainsLength == 1:
|
||
result['withdraw']['fee'] = self.safe_number(chain, 'withdrawFee')
|
||
result['withdraw']['percentage'] = False
|
||
return result
|
||
|
||
async def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
retrieves data on all markets for the exchange
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-all-tickers
|
||
https://coincatch.github.io/github.io/en/mix/#get-all-symbols
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
response = await self.publicGetApiSpotV1MarketTickers(params)
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725114040155,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "high24h": "59461.34",
|
||
# "low24h": "57723.23",
|
||
# "close": "59056.02",
|
||
# "quoteVol": "18240112.23368",
|
||
# "baseVol": "309.05564",
|
||
# "usdtVol": "18240112.2336744",
|
||
# "ts": "1725114038951",
|
||
# "buyOne": "59055.85",
|
||
# "sellOne": "59057.45",
|
||
# "bidSz": "0.0139",
|
||
# "askSz": "0.0139",
|
||
# "openUtc0": "59126.71",
|
||
# "changeUtc": "-0.0012",
|
||
# "change": "0.01662"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
if self.safe_list(self.options, 'currencyIdsListForParseMarket') is None:
|
||
await self.fetch_currencies()
|
||
spotMarkets = self.safe_list(response, 'data', [])
|
||
request: dict = {}
|
||
productType: Str = None
|
||
productType, params = self.handle_option_and_params(params, 'fetchMarkets', 'productType', productType)
|
||
swapMarkets = []
|
||
request['productType'] = 'umcbl'
|
||
response = await self.publicGetApiMixV1MarketContracts(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725297439225,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSDT_UMCBL",
|
||
# "makerFeeRate": "0.0002",
|
||
# "takerFeeRate": "0.0006",
|
||
# "feeRateUpRatio": "0.005",
|
||
# "openCostUpRatio": "0.01",
|
||
# "quoteCoin": "USDT",
|
||
# "baseCoin": "BTC",
|
||
# "buyLimitPriceRatio": "0.01",
|
||
# "sellLimitPriceRatio": "0.01",
|
||
# "supportMarginCoins": ["USDT"],
|
||
# "minTradeNum": "0.001",
|
||
# "priceEndStep": "1",
|
||
# "volumePlace": "3",
|
||
# "pricePlace": "1",
|
||
# "sizeMultiplier": "0.001",
|
||
# "symbolType": "perpetual",
|
||
# "symbolStatus": "normal",
|
||
# "offTime": "-1",
|
||
# "limitOpenTime": "-1",
|
||
# "maintainTime": "",
|
||
# "symbolName": "BTCUSDT",
|
||
# "minTradeUSDT": null,
|
||
# "maxPositionNum": null,
|
||
# "maxOrderNum": null
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
swapUMCBL = self.safe_list(response, 'data', [])
|
||
request['productType'] = 'dmcbl'
|
||
response = await self.publicGetApiMixV1MarketContracts(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code":"00000",
|
||
# "msg":"success",
|
||
# "requestTime":1725297439646,
|
||
# "data":[
|
||
# {
|
||
# "symbol":"BTCUSD_DMCBL",
|
||
# "makerFeeRate":"0.0002",
|
||
# "takerFeeRate":"0.0006",
|
||
# "feeRateUpRatio":"0.005",
|
||
# "openCostUpRatio":"0.01",
|
||
# "quoteCoin":"USD",
|
||
# "baseCoin":"BTC",
|
||
# "buyLimitPriceRatio":"0.01",
|
||
# "sellLimitPriceRatio":"0.01",
|
||
# "supportMarginCoins":[
|
||
# "BTC",
|
||
# "ETH"
|
||
# ],
|
||
# "minTradeNum":"0.001",
|
||
# "priceEndStep":"1",
|
||
# "volumePlace":"3",
|
||
# "pricePlace":"1",
|
||
# "sizeMultiplier":"0.001",
|
||
# "symbolType":"perpetual",
|
||
# "symbolStatus":"normal",
|
||
# "offTime":"-1",
|
||
# "limitOpenTime":"-1",
|
||
# "maintainTime":"",
|
||
# "symbolName":"BTCUSD",
|
||
# "minTradeUSDT":null,
|
||
# "maxPositionNum":null,
|
||
# "maxOrderNum":null
|
||
# }
|
||
# ]
|
||
# }
|
||
swapDMCBL = self.safe_list(response, 'data', [])
|
||
swapDMCBLExtended = []
|
||
for i in range(0, len(swapDMCBL)):
|
||
market = swapDMCBL[i]
|
||
supportMarginCoins = self.safe_list(market, 'supportMarginCoins', [])
|
||
for j in range(0, len(supportMarginCoins)):
|
||
settle = supportMarginCoins[j]
|
||
obj = {
|
||
'supportMarginCoins': [settle],
|
||
}
|
||
swapDMCBLExtended.append(self.extend(market, obj))
|
||
swapMarkets = self.array_concat(swapUMCBL, swapDMCBLExtended)
|
||
markets = self.array_concat(spotMarkets, swapMarkets)
|
||
return self.parse_markets(markets)
|
||
|
||
def parse_market(self, market: dict) -> Market:
|
||
#
|
||
# spot
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "high24h": "59461.34",
|
||
# "low24h": "57723.23",
|
||
# "close": "59056.02",
|
||
# "quoteVol": "18240112.23368",
|
||
# "baseVol": "309.05564",
|
||
# "usdtVol": "18240112.2336744",
|
||
# "ts": "1725114038951",
|
||
# "buyOne": "59055.85",
|
||
# "sellOne": "59057.45",
|
||
# "bidSz": "0.0139",
|
||
# "askSz": "0.0139",
|
||
# "openUtc0": "59126.71",
|
||
# "changeUtc": "-0.0012",
|
||
# "change": "0.01662"
|
||
# },
|
||
#
|
||
# swap
|
||
# {
|
||
# "symbol": "BTCUSDT_UMCBL",
|
||
# "makerFeeRate": "0.0002",
|
||
# "takerFeeRate": "0.0006",
|
||
# "feeRateUpRatio": "0.005",
|
||
# "openCostUpRatio": "0.01",
|
||
# "quoteCoin": "USDT",
|
||
# "baseCoin": "BTC",
|
||
# "buyLimitPriceRatio": "0.01",
|
||
# "sellLimitPriceRatio": "0.01",
|
||
# "supportMarginCoins": ["USDT"],
|
||
# "minTradeNum": "0.001",
|
||
# "priceEndStep": "1",
|
||
# "volumePlace": "3",
|
||
# "pricePlace": "1",
|
||
# "sizeMultiplier": "0.001",
|
||
# "symbolType": "perpetual",
|
||
# "symbolStatus": "normal",
|
||
# "offTime": "-1",
|
||
# "limitOpenTime": "-1",
|
||
# "maintainTime": "",
|
||
# "symbolName": "BTCUSDT",
|
||
# "minTradeUSDT": null,
|
||
# "maxPositionNum": null,
|
||
# "maxOrderNum": null
|
||
# }
|
||
#
|
||
marketId = self.safe_string(market, 'symbol')
|
||
tradingFees = self.safe_dict(self.fees, 'trading')
|
||
fees = self.safe_dict(tradingFees, 'spot')
|
||
baseId = self.safe_string(market, 'baseCoin')
|
||
quoteId = self.safe_string(market, 'quoteCoin')
|
||
settleId: Str = None
|
||
suffix = ''
|
||
settle: Str = None
|
||
type = 'spot'
|
||
isLinear: Bool = None
|
||
isInverse: Bool = None
|
||
subType: Str = None
|
||
isSpot = baseId is None # for now spot markets have no properties baseCoin and quoteCoin
|
||
if isSpot:
|
||
parsedMarketId = self.parse_spot_market_id(marketId)
|
||
baseId = self.safe_string(parsedMarketId, 'baseId')
|
||
quoteId = self.safe_string(parsedMarketId, 'quoteId')
|
||
marketId += '_SPBL' # spot markets should have current suffix
|
||
else:
|
||
type = 'swap'
|
||
fees['taker'] = self.safe_number(market, 'takerFeeRate')
|
||
fees['maker'] = self.safe_number(market, 'makerFeeRate')
|
||
supportMarginCoins = self.safe_list(market, 'supportMarginCoins', [])
|
||
settleId = self.safe_string(supportMarginCoins, 0)
|
||
settle = self.safe_currency_code(settleId)
|
||
suffix = ':' + settle
|
||
isLinear = quoteId == settleId # todo check
|
||
isInverse = baseId == settleId # todo check
|
||
if isLinear:
|
||
subType = 'linear'
|
||
elif isInverse:
|
||
subType = 'inverse'
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
symbol = base + '/' + quote + suffix
|
||
symbolStatus = self.safe_string(market, 'symbolStatus')
|
||
active = (symbolStatus == 'normal') if symbolStatus else None
|
||
volumePlace = self.safe_string(market, 'volumePlace')
|
||
amountPrecisionString = self.parse_precision(volumePlace)
|
||
pricePlace = self.safe_string(market, 'pricePlace')
|
||
priceEndStep = self.safe_string(market, 'priceEndStep')
|
||
pricePrecisionString = Precise.string_mul(self.parse_precision(pricePlace), priceEndStep)
|
||
return self.safe_market_structure({
|
||
'id': marketId,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'active': active,
|
||
'type': type,
|
||
'subType': subType,
|
||
'spot': isSpot,
|
||
'margin': False if isSpot else None,
|
||
'swap': not isSpot,
|
||
'future': False,
|
||
'option': False,
|
||
'contract': not isSpot,
|
||
'settle': settle,
|
||
'settleId': settleId,
|
||
'contractSize': self.safe_number(market, 'sizeMultiplier'),
|
||
'linear': isLinear,
|
||
'inverse': isInverse,
|
||
'taker': self.safe_number(fees, 'taker'),
|
||
'maker': self.safe_number(fees, 'maker'),
|
||
'percentage': self.safe_bool(fees, 'percentage'),
|
||
'tierBased': self.safe_bool(fees, 'tierBased'),
|
||
'feeSide': self.safe_string(fees, 'feeSide'),
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.parse_number(amountPrecisionString),
|
||
'price': self.parse_number(pricePrecisionString),
|
||
},
|
||
'limits': {
|
||
'amount': {
|
||
'min': self.safe_number(market, 'minTradeNum'),
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
})
|
||
|
||
def parse_spot_market_id(self, marketId):
|
||
baseId = None
|
||
quoteId = None
|
||
currencyIds = self.safe_list(self.options, 'currencyIdsListForParseMarket', [])
|
||
for i in range(0, len(currencyIds)):
|
||
currencyId = currencyIds[i]
|
||
entryIndex = marketId.find(currencyId)
|
||
if entryIndex > -1:
|
||
restId = marketId.replace(currencyId, '')
|
||
if entryIndex == 0:
|
||
baseId = currencyId
|
||
quoteId = restId
|
||
else:
|
||
baseId = restId
|
||
quoteId = currencyId
|
||
break
|
||
result: dict = {
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
}
|
||
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://coincatch.github.io/github.io/en/spot/#get-single-ticker
|
||
https://coincatch.github.io/github.io/en/mix/#get-single-symbol-ticker
|
||
|
||
: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: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = None
|
||
if market['spot']:
|
||
response = await self.publicGetApiSpotV1MarketTicker(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725132487751,
|
||
# "data": {
|
||
# "symbol": "ETHUSDT",
|
||
# "high24h": "2533.76",
|
||
# "low24h": "2492.72",
|
||
# "close": "2499.76",
|
||
# "quoteVol": "21457850.7442",
|
||
# "baseVol": "8517.1869",
|
||
# "usdtVol": "21457850.744163",
|
||
# "ts": "1725132487476",
|
||
# "buyOne": "2499.75",
|
||
# "sellOne": "2499.76",
|
||
# "bidSz": "0.5311",
|
||
# "askSz": "4.5806",
|
||
# "openUtc0": "2525.69",
|
||
# "changeUtc": "-0.01027",
|
||
# "change": "-0.00772"
|
||
# }
|
||
# }
|
||
#
|
||
elif market['swap']:
|
||
response = await self.publicGetApiMixV1MarketTicker(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725316687174,
|
||
# "data": {
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "last": "2540.6",
|
||
# "bestAsk": "2540.71",
|
||
# "bestBid": "2540.38",
|
||
# "bidSz": "12.1",
|
||
# "askSz": "20",
|
||
# "high24h": "2563.91",
|
||
# "low24h": "2398.3",
|
||
# "timestamp": "1725316687177",
|
||
# "priceChangePercent": "0.01134",
|
||
# "baseVolume": "706928.96",
|
||
# "quoteVolume": "1756401737.8766",
|
||
# "usdtVolume": "1756401737.8766",
|
||
# "openUtc": "2424.49",
|
||
# "chgUtc": "0.04789",
|
||
# "indexPrice": "2541.977142",
|
||
# "fundingRate": "0.00006",
|
||
# "holdingAmount": "144688.49",
|
||
# "deliveryStartTime": null,
|
||
# "deliveryTime": null,
|
||
# "deliveryStatus": "normal"
|
||
# }
|
||
# }
|
||
#
|
||
else:
|
||
raise NotSupported(self.id + ' ' + 'fetchTicker() is not supported for ' + market['type'] + ' type of markets')
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_ticker(data, 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://coincatch.github.io/github.io/en/spot/#get-all-tickers
|
||
https://coincatch.github.io/github.io/en/mix/#get-all-symbol-ticker
|
||
|
||
:param str[] [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
|
||
:param str [params.type]: 'spot' or 'swap'(default 'spot')
|
||
:param str [params.productType]: 'umcbl' or 'dmcbl'(default 'umcbl') - USDT perpetual contract or Universal margin perpetual contract
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
methodName = 'fetchTickers'
|
||
await self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True)
|
||
market = self.get_market_from_symbols(symbols)
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
response = None
|
||
if marketType == 'spot':
|
||
response = await self.publicGetApiSpotV1MarketTickers(params)
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725114040155,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "high24h": "59461.34",
|
||
# "low24h": "57723.23",
|
||
# "close": "59056.02",
|
||
# "quoteVol": "18240112.23368",
|
||
# "baseVol": "309.05564",
|
||
# "usdtVol": "18240112.2336744",
|
||
# "ts": "1725114038951",
|
||
# "buyOne": "59055.85",
|
||
# "sellOne": "59057.45",
|
||
# "bidSz": "0.0139",
|
||
# "askSz": "0.0139",
|
||
# "openUtc0": "59126.71",
|
||
# "changeUtc": "-0.0012",
|
||
# "change": "0.01662"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
elif marketType == 'swap':
|
||
productType = 'umcbl'
|
||
productType, params = self.handle_option_and_params(params, methodName, 'productType', productType)
|
||
request: dict = {
|
||
'productType': productType,
|
||
}
|
||
response = await self.publicGetApiMixV1MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725320291340,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSDT_UMCBL",
|
||
# "last": "59110.5",
|
||
# "bestAsk": "59113.2",
|
||
# "bestBid": "59109.5",
|
||
# "bidSz": "1.932",
|
||
# "askSz": "0.458",
|
||
# "high24h": "59393.5",
|
||
# "low24h": "57088.5",
|
||
# "timestamp": "1725320291347",
|
||
# "priceChangePercent": "0.01046",
|
||
# "baseVolume": "59667.001",
|
||
# "quoteVolume": "3472522256.9927",
|
||
# "usdtVolume": "3472522256.9927",
|
||
# "openUtc": "57263",
|
||
# "chgUtc": "0.03231",
|
||
# "indexPrice": "59151.25442",
|
||
# "fundingRate": "0.00007",
|
||
# "holdingAmount": "25995.377",
|
||
# "deliveryStartTime": null,
|
||
# "deliveryTime": null,
|
||
# "deliveryStatus": "normal"}
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_tickers(data, symbols)
|
||
|
||
def parse_ticker(self, ticker, market: Market = None) -> Ticker:
|
||
#
|
||
# spot
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "high24h": "59461.34",
|
||
# "low24h": "57723.23",
|
||
# "close": "59056.02",
|
||
# "quoteVol": "18240112.23368",
|
||
# "baseVol": "309.05564",
|
||
# "usdtVol": "18240112.2336744",
|
||
# "ts": "1725114038951",
|
||
# "buyOne": "59055.85",
|
||
# "sellOne": "59057.45",
|
||
# "bidSz": "0.0139",
|
||
# "askSz": "0.0139",
|
||
# "openUtc0": "59126.71",
|
||
# "changeUtc": "-0.0012",
|
||
# "change": "0.01662"
|
||
# }
|
||
#
|
||
# swap
|
||
# {
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "last": "2540.6",
|
||
# "bestAsk": "2540.71",
|
||
# "bestBid": "2540.38",
|
||
# "bidSz": "12.1",
|
||
# "askSz": "20",
|
||
# "high24h": "2563.91",
|
||
# "low24h": "2398.3",
|
||
# "timestamp": "1725316687177",
|
||
# "priceChangePercent": "0.01134",
|
||
# "baseVolume": "706928.96",
|
||
# "quoteVolume": "1756401737.8766",
|
||
# "usdtVolume": "1756401737.8766",
|
||
# "openUtc": "2424.49",
|
||
# "chgUtc": "0.04789",
|
||
# "indexPrice": "2541.977142",
|
||
# "fundingRate": "0.00006",
|
||
# "holdingAmount": "144688.49",
|
||
# "deliveryStartTime": null,
|
||
# "deliveryTime": null,
|
||
# "deliveryStatus": "normal"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(ticker, 'ts', 'timestamp')
|
||
marketId = self.safe_string(ticker, 'symbol', '')
|
||
if marketId.find('_') < 0:
|
||
marketId += '_SPBL' # spot markets from tickers endpoints have no suffix specific for market id
|
||
market = self.safe_market_custom(marketId, market)
|
||
last = self.safe_string_2(ticker, 'close', 'last')
|
||
return self.safe_ticker({
|
||
'symbol': market['symbol'],
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': self.safe_string(ticker, 'high24h'),
|
||
'low': self.safe_string(ticker, 'low24h'),
|
||
'bid': self.safe_string_2(ticker, 'buyOne', 'bestBid'),
|
||
'bidVolume': self.safe_string(ticker, 'bidSz'),
|
||
'ask': self.safe_string_2(ticker, 'sellOne', 'bestAsk'),
|
||
'askVolume': self.safe_string(ticker, 'askSz'),
|
||
'vwap': None,
|
||
'open': self.safe_string_2(ticker, 'openUtc0', 'openUtc'),
|
||
'close': last,
|
||
'last': last,
|
||
'previousClose': None,
|
||
'change': None,
|
||
'percentage': Precise.string_mul(self.safe_string_2(ticker, 'changeUtc', 'chgUtc'), '100'),
|
||
'average': None,
|
||
'baseVolume': self.safe_string_2(ticker, 'baseVol', 'baseVolume'),
|
||
'quoteVolume': self.safe_string_2(ticker, 'quoteVol', 'quoteVolume'),
|
||
'indexPrice': self.safe_string(ticker, 'indexPrice'),
|
||
'markPrice': None,
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
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://coincatch.github.io/github.io/en/spot/#get-merged-depth-data
|
||
https://coincatch.github.io/github.io/en/mix/#get-merged-depth-data
|
||
|
||
: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(maximum and default value is 100)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.precision]: 'scale0'(default), 'scale1', 'scale2' or 'scale3' - price accuracy, according to the selected accuracy step size to return the cumulative depth
|
||
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'fetchOrderBook'
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
precision: Str = None
|
||
precision, params = self.handle_option_and_params(params, methodName, 'precision')
|
||
if precision is not None:
|
||
request['precision'] = precision
|
||
response = None
|
||
if market['spot']:
|
||
response = await self.publicGetApiSpotV1MarketMergeDepth(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725137170814,
|
||
# "data": {
|
||
# "asks": [[2507.07, 0.4248]],
|
||
# "bids": [[2507.05, 0.1198]],
|
||
# "ts": "1725137170850",
|
||
# "scale": "0.01",
|
||
# "precision": "scale0",
|
||
# "isMaxPrecision": "NO"
|
||
# }
|
||
# }
|
||
#
|
||
elif market['swap']:
|
||
response = await self.publicGetApiMixV1MarketMergeDepth(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
data = self.safe_dict(response, 'data', {})
|
||
timestamp = self.safe_integer(data, 'ts')
|
||
return self.parse_order_book(data, symbol, timestamp, 'bids', 'asks')
|
||
|
||
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
||
"""
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-candle-data
|
||
https://coincatch.github.io/github.io/en/mix/#get-candle-data
|
||
|
||
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
||
: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(default 100)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
||
:param str [params.price]: "mark" for mark price candles
|
||
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
||
"""
|
||
methodName = 'fetchOHLCV'
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
marketType = market['type']
|
||
timeframes = self.options['timeframes'][marketType]
|
||
encodedTimeframe = self.safe_string(timeframes, timeframe, timeframe)
|
||
maxLimit = 1000
|
||
requestedLimit = limit
|
||
if (since is not None) or (until is not None):
|
||
requestedLimit = maxLimit # the exchange returns only last limit candles, so we have to fetch max limit if since or until are provided
|
||
if requestedLimit is not None:
|
||
request['limit'] = requestedLimit
|
||
response = None
|
||
if market['spot']:
|
||
request['period'] = encodedTimeframe
|
||
if since is not None:
|
||
request['after'] = since
|
||
if until is not None:
|
||
request['before'] = until
|
||
response = await self.publicGetApiSpotV1MarketCandles(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725142465742,
|
||
# "data": [
|
||
# {
|
||
# "open": "2518.6",
|
||
# "high": "2519.19",
|
||
# "low": "2518.42",
|
||
# "close": "2518.86",
|
||
# "quoteVol": "17193.239401",
|
||
# "baseVol": "6.8259",
|
||
# "usdtVol": "17193.239401",
|
||
# "ts": "1725142200000"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_ohlcvs(data, market, timeframe, since, limit)
|
||
elif market['swap']:
|
||
request['granularity'] = encodedTimeframe
|
||
if until is None:
|
||
until = self.milliseconds()
|
||
if since is None:
|
||
duration = self.parse_timeframe(timeframe)
|
||
since = until - (duration * maxLimit * 1000)
|
||
request['startTime'] = since # since and until are mandatory for swap
|
||
request['endTime'] = until
|
||
priceType: Str = None
|
||
priceType, params = self.handle_option_and_params(params, methodName, 'price')
|
||
if priceType == 'mark':
|
||
request['kLineType'] = 'market mark index'
|
||
response = await self.publicGetApiMixV1MarketCandles(self.extend(request, params))
|
||
#
|
||
# [
|
||
# [
|
||
# "1725379020000",
|
||
# "57614",
|
||
# "57636",
|
||
# "57614",
|
||
# "57633",
|
||
# "28.725",
|
||
# "1655346.493"
|
||
# ],
|
||
# ...
|
||
# ]
|
||
#
|
||
return self.parse_ohlcvs(response, market, timeframe, since, limit)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
return [
|
||
self.safe_integer_2(ohlcv, 'ts', 0),
|
||
self.safe_number_2(ohlcv, 'open', 1),
|
||
self.safe_number_2(ohlcv, 'high', 2),
|
||
self.safe_number_2(ohlcv, 'low', 3),
|
||
self.safe_number_2(ohlcv, 'close', 4),
|
||
self.safe_number_2(ohlcv, 'baseVol', 5),
|
||
]
|
||
|
||
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://coincatch.github.io/github.io/en/spot/#get-recent-trades
|
||
https://coincatch.github.io/github.io/en/mix/#get-fills
|
||
|
||
: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 entry to fetch
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
methodName = 'fetchTrades'
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
maxLimit = 1000
|
||
requestLimit = limit
|
||
if (since is not None) or (until is not None):
|
||
requestLimit = maxLimit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
if requestLimit is not None:
|
||
request['limit'] = requestLimit
|
||
response = None
|
||
if market['spot']:
|
||
response = await self.publicGetApiSpotV1MarketFillsHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725198410976,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "tradeId": "1214135619719827457",
|
||
# "side": "buy",
|
||
# "fillPrice": "2458.62",
|
||
# "fillQuantity": "0.4756",
|
||
# "fillTime": "1725198409967"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
elif market['swap']:
|
||
response = await self.publicGetApiMixV1MarketFillsHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725389251975,
|
||
# "data": [
|
||
# {
|
||
# "tradeId": "1214936067582234782",
|
||
# "price": "57998.5",
|
||
# "size": "1.918",
|
||
# "side": "Sell",
|
||
# "timestamp": "1725389251000",
|
||
# "symbol": "BTCUSDT_UMCBL"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_trades(data, market, since, limit)
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# fetchTrades spot
|
||
# {
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "tradeId": "1214135619719827457",
|
||
# "side": "Buy",
|
||
# "fillPrice": "2458.62",
|
||
# "fillQuantity": "0.4756",
|
||
# "fillTime": "1725198409967"
|
||
# }
|
||
#
|
||
# fetchTrades swap
|
||
# {
|
||
# "tradeId": "1214936067582234782",
|
||
# "price": "57998.5",
|
||
# "size": "1.918",
|
||
# "side": "Sell",
|
||
# "timestamp": "1725389251000",
|
||
# "symbol": "BTCUSDT_UMCBL"
|
||
# }
|
||
#
|
||
# fetchMyTrades spot
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217143186968068096",
|
||
# "fillId": "1217143193356505089",
|
||
# "orderType": "market",
|
||
# "side": "buy",
|
||
# "fillPrice": "2340.55",
|
||
# "fillQuantity": "0.0042",
|
||
# "fillTotalAmount": "9.83031",
|
||
# "feeCcy": "ETH",
|
||
# "fees": "-0.0000042",
|
||
# "takerMakerFlag": "taker",
|
||
# "cTime": "1725915471400"
|
||
# }
|
||
#
|
||
# fetchMyTrades swap
|
||
# {
|
||
# "tradeId": "1225467075440189441",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "orderId": "1225467075288719360",
|
||
# "price": "2362.03",
|
||
# "sizeQty": "0.1",
|
||
# "fee": "-0.00005996",
|
||
# "side": "burst_close_long",
|
||
# "fillAmount": "236.203",
|
||
# "profit": "-0.0083359",
|
||
# "enterPointSource": "SYS",
|
||
# "tradeSide": "burst_close_long",
|
||
# "holdMode": "double_hold",
|
||
# "takerMakerFlag": "taker",
|
||
# "cTime": "1727900039539"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(trade, 'symbol')
|
||
market = self.safe_market_custom(marketId, market)
|
||
timestamp = self.safe_integer_n(trade, ['fillTime', 'timestamp', 'cTime'])
|
||
fees = self.safe_string_2(trade, 'fees', 'fee')
|
||
feeCost: Str = None
|
||
if fees is not None:
|
||
feeCost = Precise.string_abs(fees)
|
||
feeCurrency = self.safe_string(trade, 'feeCcy')
|
||
if (feeCurrency is None) and (market['settle'] is not None):
|
||
feeCurrency = market['settle']
|
||
side = self.safe_string_lower_2(trade, 'tradeSide', 'side')
|
||
return self.safe_trade({
|
||
'id': self.safe_string_2(trade, 'tradeId', 'fillId'),
|
||
'order': self.safe_string(trade, 'orderId'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': market['symbol'],
|
||
'type': self.safe_string(trade, 'orderType'),
|
||
'side': self.parse_order_side(side),
|
||
'takerOrMaker': self.safe_string(trade, 'takerMakerFlag'),
|
||
'price': self.safe_string_2(trade, 'fillPrice', 'price'),
|
||
'amount': self.safe_string_n(trade, ['fillQuantity', 'size', 'sizeQty']),
|
||
'cost': self.safe_string_2(trade, 'fillTotalAmount', 'fillAmount'),
|
||
'fee': {
|
||
'cost': feeCost,
|
||
'currency': feeCurrency,
|
||
},
|
||
'info': trade,
|
||
}, market)
|
||
|
||
async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
||
"""
|
||
fetch the current funding rate
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-current-funding-rate
|
||
|
||
: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)
|
||
marketId = market['id']
|
||
parts = marketId.split('_')
|
||
request: dict = {
|
||
'symbol': marketId,
|
||
'productType': self.safe_string(parts, 1),
|
||
}
|
||
response = await self.publicGetApiMixV1MarketCurrentFundRate(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725402130395,
|
||
# "data": {
|
||
# "symbol": "BTCUSDT_UMCBL",
|
||
# "fundingRate": "0.000043"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_funding_rate(data, market)
|
||
|
||
def parse_funding_rate(self, contract, market: Market = None):
|
||
marketId = self.safe_string(contract, 'symbol')
|
||
market = self.safe_market_custom(marketId, market)
|
||
fundingRate = self.safe_number(contract, 'fundingRate')
|
||
return {
|
||
'info': contract,
|
||
'symbol': market['symbol'],
|
||
'markPrice': None,
|
||
'indexPrice': None,
|
||
'interestRate': None,
|
||
'estimatedSettlePrice': None,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'fundingRate': fundingRate,
|
||
'fundingTimestamp': None,
|
||
'fundingDatetime': None,
|
||
'nextFundingRate': None,
|
||
'nextFundingTimestamp': None,
|
||
'nextFundingDatetime': None,
|
||
'previousFundingRate': None,
|
||
'previousFundingTimestamp': None,
|
||
'previousFundingDatetime': None,
|
||
}
|
||
|
||
def handle_option_params_and_request(self, params: object, methodName: str, optionName: str, request: object, requestProperty: str, defaultValue=None):
|
||
option, paramsOmited = self.handle_option_and_params(params, methodName, optionName, defaultValue)
|
||
if option is not None:
|
||
request[requestProperty] = option
|
||
return [request, paramsOmited]
|
||
|
||
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical funding rate prices
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-history-funding-rate
|
||
|
||
: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 entries to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.pageNo]: the page number to fetch
|
||
:param bool [params.nextPage]: whether to query the next page(default False)
|
||
: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()
|
||
maxEntriesPerRequest = 100
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
requestedLimit = limit
|
||
if since is not None:
|
||
requestedLimit = maxEntriesPerRequest
|
||
if requestedLimit is not None:
|
||
request['pageSize'] = requestedLimit
|
||
response = await self.publicGetApiMixV1MarketHistoryFundRate(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725455810888,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTCUSD",
|
||
# "fundingRate": "0.000635",
|
||
# "settleTime": "1724889600000"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
rates = []
|
||
for i in range(0, len(data)):
|
||
entry = data[i]
|
||
timestamp = self.safe_integer(entry, 'settleTime')
|
||
rates.append({
|
||
'info': entry,
|
||
'symbol': self.safe_symbol(self.safe_string(entry, 'symbol'), market, None, 'swap'),
|
||
'fundingRate': self.safe_number(entry, 'fundingRate'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
})
|
||
sorted = self.sort_by(rates, 'timestamp')
|
||
return self.filter_by_since_limit(sorted, since, limit)
|
||
|
||
async def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-account-assets
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: 'spot' or 'swap' - the type of the market to fetch balance for(default 'spot')
|
||
:param str [params.productType]: *swap only* 'umcbl' or 'dmcbl'(default 'umcbl')
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'fetchBalance'
|
||
marketType = None
|
||
marketType, params = self.handle_market_type_and_params(methodName, None, params)
|
||
response = None
|
||
if marketType == 'spot':
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725202685986,
|
||
# "data": [
|
||
# {
|
||
# "coinId": 2,
|
||
# "coinName": "USDT",
|
||
# "available": "99.20000000",
|
||
# "frozen": "0.00000000",
|
||
# "lock": "0.00000000",
|
||
# "uTime": "1724938746000"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiSpotV1AccountAssets(params)
|
||
elif marketType == 'swap':
|
||
productType = 'umcbl'
|
||
productType, params = self.handle_option_and_params(params, methodName, 'productType', productType)
|
||
request: dict = {
|
||
'productType': productType,
|
||
}
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726666298135,
|
||
# "data": [
|
||
# {
|
||
# "marginCoin": "USDT",
|
||
# "locked": "0",
|
||
# "available": "60",
|
||
# "crossMaxAvailable": "60",
|
||
# "fixedMaxAvailable": "60",
|
||
# "maxTransferOut": "60",
|
||
# "equity": "60",
|
||
# "usdtEquity": "60",
|
||
# "btcEquity": "0.001002360626",
|
||
# "crossRiskRate": "0",
|
||
# "unrealizedPL": "0",
|
||
# "bonus": "0",
|
||
# "crossedUnrealizedPL": null,
|
||
# "isolatedUnrealizedPL": null
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiMixV1AccountAccounts(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_balance(data)
|
||
|
||
def parse_balance(self, balances) -> Balances:
|
||
#
|
||
# spot
|
||
# [
|
||
# {
|
||
# "coinId": 2,
|
||
# "coinName": "USDT",
|
||
# "available": "99.20000000",
|
||
# "frozen": "0.00000000",
|
||
# "lock": "0.00000000",
|
||
# "uTime": "1724938746000"
|
||
# }
|
||
# ]
|
||
#
|
||
# swap
|
||
# [
|
||
# {
|
||
# "marginCoin": "USDT",
|
||
# "locked": "0",
|
||
# "available": "60",
|
||
# "crossMaxAvailable": "60",
|
||
# "fixedMaxAvailable": "60",
|
||
# "maxTransferOut": "60",
|
||
# "equity": "60",
|
||
# "usdtEquity": "60",
|
||
# "btcEquity": "0.001002360626",
|
||
# "crossRiskRate": "0",
|
||
# "unrealizedPL": "0",
|
||
# "bonus": "0",
|
||
# "crossedUnrealizedPL": null,
|
||
# "isolatedUnrealizedPL": null
|
||
# }
|
||
# ]
|
||
#
|
||
result: dict = {
|
||
'info': balances,
|
||
}
|
||
for i in range(0, len(balances)):
|
||
balanceEntry = self.safe_dict(balances, i, {})
|
||
currencyId = self.safe_string_2(balanceEntry, 'coinName', 'marginCoin')
|
||
code = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['free'] = self.safe_string(balanceEntry, 'available')
|
||
locked = self.safe_string_2(balanceEntry, 'lock', 'locked')
|
||
frozen = self.safe_string(balanceEntry, 'frozen', '0')
|
||
account['used'] = Precise.string_add(locked, frozen)
|
||
account['total'] = self.safe_string(balanceEntry, 'equity')
|
||
result[code] = account
|
||
return self.safe_balance(result)
|
||
|
||
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
||
"""
|
||
transfer currency internally between wallets on the same account
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#transfer
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: amount to transfer
|
||
:param str fromAccount: 'spot' or 'swap' or 'mix_usdt' or 'mix_usd' - account to transfer from
|
||
:param str toAccount: 'spot' or 'swap' or 'mix_usdt' or 'mix_usd' - account to transfer to
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.clientOrderId]: a unique id for the transfer
|
||
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
if fromAccount == 'swap':
|
||
if code == 'USDT':
|
||
fromAccount = 'mix_usdt'
|
||
else:
|
||
fromAccount = 'mix_usd'
|
||
if toAccount == 'swap':
|
||
if code == 'USDT':
|
||
toAccount = 'mix_usdt'
|
||
else:
|
||
toAccount = 'mix_usd'
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
'fromType': fromAccount,
|
||
'toType': toAccount,
|
||
}
|
||
clientOrderId: Str = None
|
||
clientOrderId, params = self.handle_option_and_params(params, 'transfer', 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
response = await self.privatePostApiSpotV1WalletTransferV2(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726664727436,
|
||
# "data": {
|
||
# "transferId": "1220285801129066496",
|
||
# "clientOrderId": null
|
||
# }
|
||
# }
|
||
#
|
||
return self.parse_transfer(response, currency)
|
||
|
||
def parse_transfer(self, transfer, currency: Currency = None):
|
||
msg = self.safe_string(transfer, 'msg')
|
||
status: Str = None
|
||
if msg == 'success':
|
||
status = 'ok'
|
||
data = self.safe_dict(transfer, 'data', {})
|
||
return {
|
||
'id': self.safe_string(data, 'transferId'),
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'currency': self.safe_currency_code(None, currency),
|
||
'amount': None,
|
||
'fromAccount': None,
|
||
'toAccount': None,
|
||
'status': status,
|
||
'info': transfer,
|
||
}
|
||
|
||
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
fetch the deposit address for a currency associated with self account
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-coin-address
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.network]: network for fetch deposit address
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
}
|
||
networkCode: Str = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is None:
|
||
networkCode = self.default_network_code(code)
|
||
if networkCode is None:
|
||
raise ArgumentsRequired(self.id + ' fetchDepositAddress() requires a network parameter or a default network code')
|
||
request['chain'] = self.network_code_to_id(networkCode, code)
|
||
response = await self.privateGetApiSpotV1WalletDepositAddress(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725210515143,
|
||
# "data": {
|
||
# "coin": "USDT",
|
||
# "address": "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
|
||
# "chain": "TRC20",
|
||
# "tag": null,
|
||
# "url": "https://tronscan.org/#/transaction/"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
depositAddress = self.parse_deposit_address(data, currency)
|
||
return depositAddress
|
||
|
||
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
||
#
|
||
# {
|
||
# "coin": "USDT",
|
||
# "address": "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
|
||
# "chain": "TRC20",
|
||
# "tag": null,
|
||
# "url": "https://tronscan.org/#/transaction/"
|
||
# }
|
||
#
|
||
address = self.safe_string(depositAddress, 'address')
|
||
self.check_address(address)
|
||
networkId = self.safe_string(depositAddress, 'chain')
|
||
network = self.safe_string(self.options['networksById'], networkId, networkId)
|
||
tag = self.safe_string(depositAddress, 'tag')
|
||
return {
|
||
'currency': currency['code'],
|
||
'address': address,
|
||
'tag': tag,
|
||
'network': network,
|
||
'info': depositAddress,
|
||
}
|
||
|
||
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://coincatch.github.io/github.io/en/spot/#get-deposit-list
|
||
|
||
:param str code: unified currency code of the currency transferred
|
||
:param int [since]: the earliest time in ms to fetch transfers for(default 24 hours ago)
|
||
:param int [limit]: the maximum number of transfer structures to retrieve(not used by exchange)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch transfers for(default time now)
|
||
:param int [params.pageNo]: pageNo default 1
|
||
:param int [params.pageSize]: pageSize(default 20, max 100)
|
||
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
methodName = 'fetchDeposits'
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
currency: Currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
response = await self.privateGetApiSpotV1WalletDepositList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725205525239,
|
||
# "data": [
|
||
# {
|
||
# "id": "1213046466852196352",
|
||
# "txId": "824246b030cd84d56400661303547f43a1d9fef66cf968628dd5112f362053ff",
|
||
# "coin": "USDT",
|
||
# "type": "deposit",
|
||
# "amount": "99.20000000",
|
||
# "status": "success",
|
||
# "toAddress": "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
|
||
# "fee": null,
|
||
# "chain": "TRX(TRC20)",
|
||
# "confirm": null,
|
||
# "clientOid": null,
|
||
# "tag": null,
|
||
# "fromAddress": null,
|
||
# "dest": "on_chain",
|
||
# "cTime": "1724938735688",
|
||
# "uTime": "1724938746015"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_transactions(data, currency, since, limit)
|
||
|
||
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://coincatch.github.io/github.io/en/spot/#get-withdraw-list-v2
|
||
|
||
:param str code: unified currency code of the currency transferred
|
||
:param int [since]: the earliest time in ms to fetch transfers for(default 24 hours ago)
|
||
:param int [limit]: the maximum number of transfer structures to retrieve(default 50, max 200)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch transfers for(default time now)
|
||
:param str [params.clientOid]: clientOid
|
||
:param str [params.orderId]: The response orderId
|
||
:param str [params.idLessThan]: Requests the content on the page before self ID(older data), the value input should be the orderId of the corresponding interface.
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
methodName = 'fetchWithdrawals'
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
currency: Currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
response = await self.privateGetApiSpotV1WalletWithdrawalListV2(self.extend(request, params))
|
||
# todo add after withdrawal
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_transactions(data, currency, since, limit)
|
||
|
||
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
make a withdrawal
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#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
|
||
:param str params['network']: network for withdraw(mandatory)
|
||
:param str [params.remark]: remark
|
||
:param str [params.clientOid]: custom id
|
||
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'address': address,
|
||
'amount': amount,
|
||
}
|
||
if tag is not None:
|
||
request['tag'] = tag
|
||
networkCode: Str = 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.privatePostApiSpotV1WalletWithdrawalV2(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "data": {
|
||
# "orderId":888291686266343424",
|
||
# "clientOrderId":"123"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_transaction(data, currency)
|
||
|
||
def parse_transaction(self, transaction, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchDeposits
|
||
#
|
||
# {
|
||
# "id": "1213046466852196352",
|
||
# "txId": "824246b030cd84d56400661303547f43a1d9fef66cf968628dd5112f362053ff",
|
||
# "coin": "USDT",
|
||
# "type": "deposit",
|
||
# "amount": "99.20000000",
|
||
# "status": "success",
|
||
# "toAddress": "TKTUt7qiTaMgnTwZXjE3ZBkPB6LKhLPJyZ",
|
||
# "fee": null,
|
||
# "chain": "TRX(TRC20)",
|
||
# "confirm": null,
|
||
# "clientOid": null,
|
||
# "tag": null,
|
||
# "fromAddress": null,
|
||
# "dest": "on_chain",
|
||
# "cTime": "1724938735688",
|
||
# "uTime": "1724938746015"
|
||
# }
|
||
#
|
||
# withdraw
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "data": {
|
||
# "orderId":888291686266343424",
|
||
# "clientOrderId":"123"
|
||
# }
|
||
# }
|
||
#
|
||
status = self.safe_string(transaction, 'status')
|
||
if status == 'success':
|
||
status = 'ok'
|
||
txid = self.safe_string(transaction, 'txId')
|
||
coin = self.safe_string(transaction, 'coin')
|
||
code = self.safe_currency_code(coin, currency)
|
||
timestamp = self.safe_integer(transaction, 'cTime')
|
||
amount = self.safe_number(transaction, 'amount')
|
||
networkId = self.safe_string(transaction, 'chain')
|
||
network = self.safe_string(self.options['networksById'], networkId, networkId)
|
||
addressTo = self.safe_string(transaction, 'toAddress')
|
||
addressFrom = self.safe_string(transaction, 'fromAddress')
|
||
tag = self.safe_string(transaction, 'tag')
|
||
type = self.safe_string(transaction, 'type')
|
||
feeCost = self.safe_number(transaction, 'fee')
|
||
fee = None
|
||
if feeCost is not None:
|
||
fee = {
|
||
'cost': feeCost,
|
||
'currency': code,
|
||
}
|
||
return {
|
||
'info': transaction,
|
||
'id': self.safe_string_2(transaction, 'id', 'orderId'),
|
||
'txid': txid,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'network': network,
|
||
'address': None,
|
||
'addressTo': addressTo,
|
||
'addressFrom': addressFrom,
|
||
'tag': tag,
|
||
'tagTo': None,
|
||
'tagFrom': None,
|
||
'type': type,
|
||
'amount': amount,
|
||
'currency': code,
|
||
'status': status,
|
||
'updated': None,
|
||
'internal': None,
|
||
'comment': None,
|
||
'fee': fee,
|
||
}
|
||
|
||
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://coincatch.github.io/github.io/en/spot/#place-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
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'createMarketBuyOrderWithCost'
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' ' + methodName + '() supports spot orders only')
|
||
params['methodName'] = methodName
|
||
params['createMarketBuyOrderRequiresPrice'] = False
|
||
return await self.create_order(symbol, 'market', 'buy', cost, None, params)
|
||
|
||
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
||
"""
|
||
create a trade order
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#place-order
|
||
https://coincatch.github.io/github.io/en/spot/#place-plan-order
|
||
https://coincatch.github.io/github.io/en/mix/#place-order
|
||
https://coincatch.github.io/github.io/en/mix/#place-plan-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit' or 'LIMIT_MAKER' for spot, 'market' or 'limit' or 'STOP' for swap
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of you want to trade in units of the base currency
|
||
:param float [price]: the price that 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.hedged]: *swap markets only* must be set to True if position mode is hedged(default False)
|
||
:param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
|
||
:param float [params.triggerPrice]: the price that the order is to be triggered
|
||
:param bool [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
|
||
:param str [params.timeInForce]: 'GTC', 'IOC', 'FOK' or 'PO'
|
||
:param str [params.clientOrderId]: a unique id for the order - is mandatory for swap
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
params['methodName'] = self.safe_string(params, 'methodName', 'createOrder')
|
||
market = self.market(symbol)
|
||
if market['spot']:
|
||
return await self.create_spot_order(symbol, type, side, amount, price, params)
|
||
elif market['swap']:
|
||
return await self.create_swap_order(symbol, type, side, amount, price, params)
|
||
else:
|
||
raise NotSupported(self.id + ' createOrder() is not supported for ' + market['type'] + ' type of markets')
|
||
|
||
async def create_spot_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
||
"""
|
||
create a trade order on spot market
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#place-order
|
||
https://coincatch.github.io/github.io/en/spot/#place-plan-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of you want to trade in units of the base currency
|
||
:param float [price]: the price that 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.cost]: *market buy only* the quote quantity that can be used alternative for the amount
|
||
:param float [params.triggerPrice]: the price that the order is to be triggered at
|
||
:param bool [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
|
||
:param str [params.timeInForce]: 'GTC', 'IOC', 'FOK' or 'PO'
|
||
:param str [params.clientOrderId]: a unique id for the order(max length 40)
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
params['methodName'] = self.safe_string(params, 'methodName', 'createSpotOrder')
|
||
request: dict = self.create_spot_order_request(symbol, type, side, amount, price, params)
|
||
isPlanOrer = self.safe_string(request, 'triggerPrice') is not None
|
||
response = None
|
||
if isPlanOrer:
|
||
response = await self.privatePostApiSpotV1PlanPlacePlan(request)
|
||
else:
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725915469815,
|
||
# "data": {
|
||
# "orderId": "1217143186968068096",
|
||
# "clientOrderId": "8fa3eb89-2377-4519-a199-35d5db9ed262"
|
||
# }
|
||
# }
|
||
#
|
||
response = await self.privatePostApiSpotV1TradeOrders(request)
|
||
data = self.safe_dict(response, 'data', {})
|
||
market = self.market(symbol)
|
||
return self.parse_order(data, market)
|
||
|
||
def create_spot_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> dict:
|
||
"""
|
||
@ignore
|
||
helper function to build request
|
||
: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 you want to trade in units of the base currency
|
||
:param float [price]: the price that 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 that the order is to be triggered at
|
||
:param float [params.cost]: *market buy only* the quote quantity that can be used alternative for the amount
|
||
:param bool [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
|
||
:param str [params.timeInForce]: 'GTC', 'IOC', 'FOK' or 'PO'(default 'GTC')
|
||
:param str [params.clientOrderId]: a unique id for the order(max length 40)
|
||
:returns dict: request to be sent to the exchange
|
||
"""
|
||
methodName = 'createSpotOrderRequest'
|
||
# spot market info has no presicion so we do not use it
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'side': side,
|
||
'orderType': type,
|
||
}
|
||
isMarketOrder = (type == 'market')
|
||
timeInForceAndParams = self.handle_time_in_force_and_post_only(methodName, params, isMarketOrder)
|
||
params = timeInForceAndParams['params']
|
||
timeInForce = timeInForceAndParams['timeInForce']
|
||
cost: Str = None
|
||
cost, params = self.handle_param_string(params, 'cost')
|
||
triggerPrice: Str = None
|
||
triggerPrice, params = self.handle_param_string(params, 'triggerPrice')
|
||
isMarketBuy = isMarketOrder and (side == 'buy')
|
||
if (not isMarketBuy) and (cost is not None):
|
||
raise NotSupported(self.id + ' ' + methodName + ' supports cost parameter for market buy orders only')
|
||
if isMarketBuy:
|
||
costAndParams = self.handle_requires_price_and_cost(methodName, params, price, amount, cost)
|
||
cost = costAndParams['cost']
|
||
params = costAndParams['params']
|
||
if triggerPrice is None:
|
||
if type == 'limit':
|
||
request['price'] = price # spot markets have no precision
|
||
request['quantity'] = cost if isMarketBuy else self.number_to_string(amount) # spot markets have no precision
|
||
request['force'] = timeInForce if timeInForce else 'normal' # the exchange requres force but accepts any value
|
||
else:
|
||
request['triggerPrice'] = triggerPrice # spot markets have no precision
|
||
if timeInForce is not None:
|
||
request['timeInForceValue'] = timeInForce
|
||
clientOrderId: Str = None
|
||
clientOrderId, params = self.handle_param_string(params, 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
if type == 'limit':
|
||
request['executePrice'] = price # spot markets have no precision
|
||
triggerType: Str = None
|
||
if isMarketOrder:
|
||
triggerType = 'market_price'
|
||
else:
|
||
triggerType = 'fill_price'
|
||
request['triggerType'] = triggerType
|
||
# tood check placeType
|
||
request['size'] = cost if isMarketOrder else self.number_to_string(amount) # spot markets have no precision
|
||
return self.extend(request, params)
|
||
|
||
def handle_requires_price_and_cost(self, methodName: str, params: dict = {}, price: Num = None, amount: Num = None, cost: Str = None, side: str = 'buy'):
|
||
optionName = 'createMarket' + self.capitalize(side) + 'OrderRequiresPrice'
|
||
requiresPrice = True
|
||
requiresPrice, params = self.handle_option_and_params(params, methodName, optionName, True)
|
||
amountString: Str = None
|
||
if amount is not None:
|
||
amountString = self.number_to_string(amount)
|
||
priceString: Str = None
|
||
if price is not None:
|
||
priceString = self.number_to_string(price)
|
||
if requiresPrice:
|
||
if (price is None) and (cost is None):
|
||
raise InvalidOrder(self.id + ' ' + methodName + '() requires the price argument for market ' + side + ' orders to calculate the total cost to spend(amount * price), alternatively set the ' + optionName + ' option or param to False and pass the cost to spend in the amount argument')
|
||
elif cost is None:
|
||
cost = Precise.string_mul(amountString, priceString)
|
||
else:
|
||
cost = cost if cost else amountString
|
||
result: dict = {
|
||
'cost': cost,
|
||
'params': params,
|
||
}
|
||
return result
|
||
|
||
def handle_time_in_force_and_post_only(self, methodName: str, params: dict = {}, isMarketOrder: Bool = False):
|
||
timeInForce: Str = None
|
||
timeInForce, params = self.handle_option_and_params(params, methodName, 'timeInForce')
|
||
postOnly = False
|
||
postOnly, params = self.handle_post_only(isMarketOrder, timeInForce == 'post_only', params)
|
||
if postOnly:
|
||
timeInForce = 'PO'
|
||
timeInForce = self.encode_time_in_force(timeInForce)
|
||
result: dict = {
|
||
'timeInForce': timeInForce,
|
||
'params': params,
|
||
}
|
||
return result
|
||
|
||
async def create_swap_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
||
"""
|
||
create a trade order on swap market
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#place-order
|
||
https://coincatch.github.io/github.io/en/mix/#place-plan-order
|
||
https://coincatch.github.io/github.io/en/mix/#place-stop-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of you want to trade in units of the base currency
|
||
:param float [price]: the price that 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.hedged]: must be set to True if position mode is hedged(default False)
|
||
:param bool [params.postOnly]: *non-trigger orders only* if True, the order will only be posted to the order book and not executed immediately
|
||
:param bool [params.reduceOnly]: True or False whether the order is reduce only
|
||
:param str [params.timeInForce]: *non-trigger orders only* 'GTC', 'FOK', 'IOC' or 'PO'
|
||
:param str [params.clientOrderId]: a unique id for the order
|
||
:param float [params.triggerPrice]: the price that the order is to be triggered at
|
||
: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 dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered(perpetual swap markets only)
|
||
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
||
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only)
|
||
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
params['methodName'] = self.safe_string(params, 'methodName', 'createSwapOrder')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request = self.create_swap_order_request(symbol, type, side, amount, price, params)
|
||
endpointType = self.safe_string(request, 'endpointType')
|
||
request = self.omit(request, 'endpointType')
|
||
response = None
|
||
if endpointType == 'trigger':
|
||
response = await self.privatePostApiMixV1PlanPlacePlan(request)
|
||
elif endpointType == 'tpsl':
|
||
response = await self.privatePostApiMixV1PlanPlaceTPSL(request)
|
||
else: # standard
|
||
response = await self.privatePostApiMixV1OrderPlaceOrder(request)
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1727977301979,
|
||
# "data":
|
||
# {
|
||
# "clientOid": "1225791137701519360",
|
||
# "orderId": "1225791137697325056"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data, market)
|
||
|
||
def create_swap_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> dict:
|
||
"""
|
||
@ignore
|
||
helper function to build request
|
||
: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 you want to trade in units of the base currency
|
||
:param float [price]: the price that 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.hedged]: must be set to True if position mode is hedged(default False)
|
||
:param bool [params.postOnly]: *non-trigger orders only* if True, the order will only be posted to the order book and not executed immediately
|
||
:param bool [params.reduceOnly]: True or False whether the order is reduce only
|
||
:param str [params.timeInForce]: *non-trigger orders only* 'GTC', 'FOK', 'IOC' or 'PO'
|
||
:param str [params.clientOrderId]: a unique id for the order
|
||
:param float [params.triggerPrice]: the price that the order is to be triggered at
|
||
: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 dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered(perpetual swap markets only)
|
||
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
||
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only)
|
||
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
||
:returns dict: request to be sent to the exchange
|
||
"""
|
||
methodName = 'createSwapOrderRequest'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
'size': self.amount_to_precision(symbol, amount),
|
||
}
|
||
request, params = self.handle_option_params_and_request(params, methodName, 'clientOrderId', request, 'clientOid')
|
||
isMarketOrder = (type == 'market')
|
||
params = self.handle_trigger_stop_loss_and_take_profit(symbol, side, type, price, methodName, params)
|
||
endpointType = self.safe_string(params, 'endpointType')
|
||
if (endpointType is None) or (endpointType == 'standard'):
|
||
timeInForceAndParams = self.handle_time_in_force_and_post_only(methodName, params, isMarketOrder) # only for non-trigger orders
|
||
params = timeInForceAndParams['params']
|
||
timeInForce = timeInForceAndParams['timeInForce']
|
||
if timeInForce is not None:
|
||
request['timeInForceValue'] = timeInForce
|
||
if price is not None:
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
if (endpointType != 'tpsl'):
|
||
request['orderType'] = type
|
||
sideIsExchangeSpecific = False
|
||
hedged = False
|
||
if (side == 'buy_single') or (side == 'sell_single') or (side == 'open_long') or (side == 'open_short') or (side == 'close_long') or (side == 'close_short'):
|
||
sideIsExchangeSpecific = True
|
||
if (side != 'buy_single') and (side != 'sell_single'):
|
||
hedged = True
|
||
if not sideIsExchangeSpecific:
|
||
hedged, params = self.handle_option_and_params(params, methodName, 'hedged', hedged)
|
||
# hedged and non-hedged orders have different side values and reduceOnly handling
|
||
reduceOnly = self.safe_bool(params, 'reduceOnly')
|
||
if hedged:
|
||
if (reduceOnly is not None) and reduceOnly:
|
||
if side == 'buy':
|
||
side = 'close_short'
|
||
elif side == 'sell':
|
||
side = 'close_long'
|
||
else:
|
||
if side == 'buy':
|
||
side = 'open_long'
|
||
elif side == 'sell':
|
||
side = 'open_short'
|
||
else:
|
||
side = side.lower() + '_single'
|
||
if hedged:
|
||
params = self.omit(params, 'reduceOnly')
|
||
request['side'] = side
|
||
return self.extend(request, params)
|
||
|
||
def handle_trigger_stop_loss_and_take_profit(self, symbol, side, type, price, methodName='createOrder', params={}):
|
||
request: dict = {}
|
||
endpointType = 'standard' # standard, trigger, tpsl, trailing - to define the endpoint to use
|
||
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
||
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
||
requestTriggerPrice: Str = None
|
||
takeProfitParams = self.safe_dict(params, 'takeProfit')
|
||
stopLossParams = self.safe_dict(params, 'stopLoss')
|
||
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
||
isTrigger = (triggerPrice is not None)
|
||
trailingPercent = self.safe_string(params, 'trailingPercent')
|
||
trailingTriggerPrice = self.safe_string(params, 'trailingTriggerPrice')
|
||
hasTPPrice = (takeProfitPrice is not None)
|
||
hasSLPrice = (stopLossPrice is not None)
|
||
hasTPParams = (takeProfitParams is not None)
|
||
if hasTPParams and not hasTPPrice:
|
||
takeProfitPrice = self.safe_string(takeProfitParams, 'triggerPrice')
|
||
hasTPPrice = (takeProfitPrice is not None)
|
||
hasSLParams = (stopLossParams is not None)
|
||
if hasSLParams and not hasSLPrice:
|
||
stopLossPrice = self.safe_string(stopLossParams, 'triggerPrice')
|
||
hasSLPrice = (stopLossPrice is not None)
|
||
hasBothTPAndSL = hasTPPrice and hasSLPrice
|
||
isTrailingPercentOrder = (trailingPercent is not None)
|
||
isMarketOrder = (type == 'market')
|
||
# handle with triggerPrice stopLossPrice and takeProfitPrice
|
||
if hasBothTPAndSL or isTrigger or (methodName == 'createOrderWithTakeProfitAndStopLoss'):
|
||
if isTrigger:
|
||
if isMarketOrder:
|
||
request['triggerType'] = 'market_price'
|
||
else:
|
||
request['triggerType'] = 'fill_price'
|
||
request['executePrice'] = self.price_to_precision(symbol, price)
|
||
request['triggerPrice'] = self.price_to_precision(symbol, triggerPrice)
|
||
endpointType = 'trigger' # if order also has triggerPrice we use endpoint for trigger orders
|
||
if methodName == 'createOrders':
|
||
endpointType = None # we do not provide endpointType for createOrders
|
||
if hasTPPrice:
|
||
request['presetTakeProfitPrice'] = takeProfitPrice
|
||
if hasSLPrice:
|
||
request['presetStopLossPrice'] = stopLossPrice
|
||
elif hasTPPrice or hasSLPrice or isTrailingPercentOrder:
|
||
if not isMarketOrder:
|
||
raise NotSupported(self.id + ' ' + methodName + '() supports does not support ' + type + ' type of stop loss and take profit orders(only market type is supported for stop loss and take profit orders). To create a market order with stop loss or take profit attached use createOrderWithTakeProfitAndStopLoss()')
|
||
endpointType = 'tpsl' # if order has only one of the two we use endpoint for tpsl orders
|
||
holdSide = 'long'
|
||
if side == 'buy':
|
||
holdSide = 'short'
|
||
request['holdSide'] = holdSide
|
||
if isTrailingPercentOrder:
|
||
if trailingTriggerPrice is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires the trailingTriggerPrice parameter for trailing stop orders')
|
||
requestTriggerPrice = trailingTriggerPrice
|
||
request['rangeRate'] = trailingPercent
|
||
request['planType'] = 'moving_plan'
|
||
elif hasTPPrice: # take profit
|
||
requestTriggerPrice = takeProfitPrice
|
||
request['planType'] = 'profit_plan'
|
||
else: # stop loss
|
||
requestTriggerPrice = stopLossPrice
|
||
request['planType'] = 'loss_plan'
|
||
request['triggerPrice'] = self.price_to_precision(symbol, requestTriggerPrice)
|
||
if endpointType is not None:
|
||
request['endpointType'] = endpointType
|
||
params = self.omit(params, ['stopLoss', 'takeProfit', 'stopLossPrice', 'takeProfitPrice', 'triggerPrice', 'stopPrice', 'trailingPercent', 'trailingTriggerPrice'])
|
||
return self.extend(request, params)
|
||
|
||
async def create_order_with_take_profit_and_stop_loss(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfit: Num = None, stopLoss: Num = None, params={}) -> Order:
|
||
"""
|
||
*swap markets only* create an order with a stop loss or take profit attached(type 3)
|
||
: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 you want to trade in units of the base currency or the number of contracts
|
||
:param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
|
||
:param float [takeProfit]: the take profit price, in units of the quote currency
|
||
:param float [stopLoss]: the stop loss price, in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'createOrderWithTakeProfitAndStopLoss'
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['swap']:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is supported for swap markets only')
|
||
params['methodName'] = methodName
|
||
return super(coincatch, self).create_order_with_take_profit_and_stop_loss(symbol, type, side, amount, price, takeProfit, stopLoss, params)
|
||
|
||
def encode_time_in_force(self, timeInForce: Str) -> Str:
|
||
timeInForceMap = {
|
||
'GTC': 'normal',
|
||
'IOC': 'iok',
|
||
'FOK': 'fok',
|
||
'PO': 'post_only',
|
||
}
|
||
return self.safe_string(timeInForceMap, timeInForce, timeInForce)
|
||
|
||
async def create_orders(self, orders: List[OrderRequest], params={}):
|
||
"""
|
||
create a list of trade orders(all orders should be of the same symbol)
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#batch-order
|
||
|
||
: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(max 50 entries)
|
||
:param dict [params]: extra parameters specific to the api endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
# same symbol for all orders
|
||
methodName = 'createOrders'
|
||
params['methodName'] = methodName
|
||
ordersRequests = []
|
||
symbols = []
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
symbol = self.safe_string(rawOrder, 'symbol')
|
||
symbols.append(symbol)
|
||
type = self.safe_string(rawOrder, 'type')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_number(rawOrder, 'amount')
|
||
price = self.safe_number(rawOrder, 'price')
|
||
orderParams = self.safe_dict(rawOrder, 'params', {})
|
||
orderRequest = self.create_order_request(symbol, type, side, amount, price, orderParams)
|
||
triggerPrice = self.safe_string(orderParams, 'triggerPrice')
|
||
if triggerPrice is not None:
|
||
raise NotSupported(self.id + ' ' + methodName + '() does not support trigger orders')
|
||
clientOrderId = self.safe_string(orderRequest, 'clientOrderId')
|
||
if clientOrderId is None:
|
||
orderRequest['clientOrderId'] = self.uuid() # both spot and swap endpoints require clientOrderId
|
||
ordersRequests.append(orderRequest)
|
||
symbols = self.unique(symbols)
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength != 1:
|
||
raise BadRequest(self.id + ' createOrders() requires all orders to be of the same symbol')
|
||
ordersSymbol = self.safe_string(symbols, 0)
|
||
market = self.market(ordersSymbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
marketType = market['type']
|
||
response = None
|
||
responseOrders = None
|
||
propertyName: Str = None
|
||
if marketType == 'spot':
|
||
request['orderList'] = ordersRequests
|
||
response = await self.privatePostApiSpotV1TradeBatchOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726160718706,
|
||
# "data": {
|
||
# "resultList": [
|
||
# {
|
||
# "orderId": "1218171835238367232",
|
||
# "clientOrderId": "28759338-ca10-42dd-8ac3-5183785ef60b"
|
||
# }
|
||
# ],
|
||
# "failure": [
|
||
# {
|
||
# "orderId": "",
|
||
# "clientOrderId": "ee2e67c9-47fc-4311-9cc1-737ec408d509",
|
||
# "errorMsg": "The order price of eth_usdt cannot be less than 5.00% of the current price",
|
||
# "errorCode": "43008"
|
||
# },
|
||
# {
|
||
# "orderId": "",
|
||
# "clientOrderId": "1af2defa-0c2d-4bb5-acb7-6feb6a86787a",
|
||
# "errorMsg": "less than the minimum amount 1 USDT",
|
||
# "errorCode": "45110"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
propertyName = 'resultList'
|
||
elif market['swap']:
|
||
request['marginCoin'] = market['settleId']
|
||
request['orderDataList'] = ordersRequests
|
||
response = await self.privatePostApiMixV1OrderBatchOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1729100084017,
|
||
# "data": {
|
||
# "orderInfo": [
|
||
# {
|
||
# "orderId": "1230500426827522049",
|
||
# "clientOid": "1230500426898825216"
|
||
# }
|
||
# ],
|
||
# "failure": [
|
||
# {
|
||
# "orderId": "",
|
||
# "clientOid": null,
|
||
# "errorMsg": "The order price exceeds the maximum price limit: 2,642.53",
|
||
# "errorCode": "22047"
|
||
# }
|
||
# ],
|
||
# "result": True
|
||
# }
|
||
# }
|
||
#
|
||
propertyName = 'orderInfo'
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
data = self.safe_dict(response, 'data', {})
|
||
responseOrders = self.safe_list(data, propertyName, [])
|
||
return self.parse_orders(responseOrders)
|
||
|
||
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> dict:
|
||
methodName = self.safe_string(params, 'methodName', 'createOrderRequest')
|
||
params['methodName'] = methodName
|
||
market = self.market(symbol)
|
||
if market['spot']:
|
||
return self.create_spot_order_request(symbol, type, side, amount, price, params)
|
||
elif market['swap']:
|
||
return self.create_swap_order_request(symbol, type, side, amount, price, params)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
|
||
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
||
"""
|
||
edit a trade trigger, stop-looss or take-profit order
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#modify-plan-order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'editOrder'
|
||
# only trigger, stop-looss or take-profit orders can be edited
|
||
params['methodName'] = methodName
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['spot']:
|
||
return await self.edit_spot_order(id, symbol, type, side, amount, price, params)
|
||
else:
|
||
# todo return await self.editSwapOrder(id, symbol, type, side, amount, price, params)
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
|
||
async def edit_spot_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
@ignore
|
||
edit a trade order
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#modify-plan-order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.clientOrderId]: a unique id for the order that can be used alternative for the id
|
||
:param str params['triggerPrice']: *mandatory* the price that the order is to be triggered at
|
||
:param float [params.cost]: *market buy only* the quote quantity that can be used alternative for the amount
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'editSpotOrder'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' editSpotOrder() does not support ' + market['type'] + ' orders')
|
||
request: dict = {
|
||
'orderType': type,
|
||
}
|
||
clientOrderId = self.safe_string(params, 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
elif id is None:
|
||
raise BadRequest(self.id + ' ' + methodName + '() requires id or clientOrderId')
|
||
else:
|
||
request['orderId'] = id
|
||
cost: Str = None
|
||
cost, params = self.handle_param_string(params, 'cost')
|
||
isMarketBuy = (type == 'market') and (side == 'buy')
|
||
if (not isMarketBuy) and (cost is not None):
|
||
raise NotSupported(self.id + ' ' + methodName + '() supports cost parameter for market buy orders only')
|
||
if amount is not None:
|
||
if isMarketBuy:
|
||
costAndParams = self.handle_requires_price_and_cost(methodName, params, price, amount, cost)
|
||
cost = costAndParams['cost']
|
||
params = costAndParams['params']
|
||
else:
|
||
request['size'] = self.number_to_string(amount) # spot markets have no precision
|
||
if cost is not None:
|
||
request['size'] = cost # spot markets have no precision
|
||
if (type == 'limit') and (price is not None):
|
||
request['price'] = price # spot markets have no precision
|
||
response = await self.privatePostApiSpotV1PlanModifyPlan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1668136575920,
|
||
# "data": {
|
||
# "orderId": "974792060738441216",
|
||
# "clientOrderId": "974792554995224576"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data, market)
|
||
|
||
async def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
fetches information on an order made by the user(non-trigger orders only)
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-order-details
|
||
https://coincatch.github.io/github.io/en/mix/#get-order-details
|
||
|
||
:param str id: the order id
|
||
:param str symbol: unified symbol of the market the order was made in(is mandatory for swap)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: 'spot' or 'swap' - the type of the market to fetch entry for(default 'spot')
|
||
:param str [params.clientOrderId]: a unique id for the order that can be used alternative for the id
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'fetchOrder'
|
||
# for non-trigger orders only
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
clientOrderId = self.safe_string(params, 'clientOrderId')
|
||
if clientOrderId is None:
|
||
request['orderId'] = id
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
response = None
|
||
order: dict = None
|
||
if marketType == 'spot':
|
||
# user could query cancelled/filled order details within 24 hours, After 24 hours should use fetchOrders
|
||
response = await self.privatePostApiSpotV1TradeOrderInfo(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725918004434,
|
||
# "data": [
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217143186968068096",
|
||
# "clientOrderId": "8fa3eb89-2377-4519-a199-35d5db9ed262",
|
||
# "price": "0",
|
||
# "quantity": "10.0000000000000000",
|
||
# "orderType": "market",
|
||
# "side": "buy",
|
||
# "status": "full_fill",
|
||
# "fillPrice": "2340.5500000000000000",
|
||
# "fillQuantity": "0.0042000000000000",
|
||
# "fillTotalAmount": "9.8303100000000000",
|
||
# "enterPointSource": "API",
|
||
# "feeDetail": "{
|
||
# \"ETH\": {
|
||
# \"deduction\": False,
|
||
# \"feeCoinCode\": \"ETH\",
|
||
# \"totalDeductionFee\": 0,
|
||
# \"totalFee\": -0.0000042000000000},
|
||
# \"newFees\": {
|
||
# \"c\": 0,
|
||
# \"d\": 0,
|
||
# \"deduction\": False,
|
||
# \"r\": -0.0000042,
|
||
# \"t\": -0.0000042,
|
||
# \"totalDeductionFee\": 0
|
||
# }
|
||
# }",
|
||
# "orderSource": "market",
|
||
# "cTime": "1725915469877"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data')
|
||
if data is None:
|
||
response = json.loads(response) # the response from closed orders is not a standard JSON
|
||
data = self.safe_list(response, 'data', [])
|
||
order = self.safe_dict(data, 0, {})
|
||
elif marketType == 'swap':
|
||
if market is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for ' + marketType + ' type of markets')
|
||
request['symbol'] = market['id']
|
||
if clientOrderId is not None:
|
||
params = self.omit(params, 'clientOrderId')
|
||
request['clientOid'] = clientOrderId
|
||
response = await self.privateGetApiMixV1OrderDetail(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1727981421364,
|
||
# "data": {
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "size": 0.01,
|
||
# "orderId": "1225791137697325056",
|
||
# "clientOid": "1225791137701519360",
|
||
# "filledQty": 0.01,
|
||
# "fee": -0.01398864,
|
||
# "price": null,
|
||
# "priceAvg": 2331.44,
|
||
# "state": "filled",
|
||
# "side": "close_long",
|
||
# "timeInForce": "normal",
|
||
# "totalProfits": -2.23680000,
|
||
# "posSide": "long",
|
||
# "marginCoin": "USDT",
|
||
# "filledAmount": 23.3144,
|
||
# "orderType": "market",
|
||
# "leverage": "5",
|
||
# "marginMode": "crossed",
|
||
# "reduceOnly": True,
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "close_long",
|
||
# "holdMode": "double_hold",
|
||
# "orderSource": "market",
|
||
# "cTime": "1727977302003",
|
||
# "uTime": "1727977303604"
|
||
# }
|
||
# }
|
||
#
|
||
order = self.safe_dict(response, 'data', {})
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
return self.parse_order(order, 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://coincatch.github.io/github.io/en/spot/#get-order-list
|
||
https://coincatch.github.io/github.io/en/spot/#get-current-plan-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-open-order
|
||
https://coincatch.github.io/github.io/en/mix/#get-all-open-order
|
||
https://coincatch.github.io/github.io/en/mix/#get-plan-order-tpsl-list
|
||
|
||
: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 boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.type]: 'spot' or 'swap' - the type of the market to fetch entries for(default 'spot')
|
||
:param str [params.productType]: *swap only* 'umcbl' or 'dmcbl' - the product type of the market to fetch entries for(default 'umcbl')
|
||
:param str [params.marginCoin]: *swap only* the margin coin of the market to fetch entries for
|
||
:param str [params.isPlan]: *swap trigger only* 'plan' or 'profit_loss'('plan'(default) for trigger(plan) orders, 'profit_loss' for stop-loss and take-profit orders)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'fetchOpenOrders'
|
||
await self.load_markets()
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
params['methodName'] = methodName
|
||
if marketType == 'spot':
|
||
return await self.fetch_open_spot_orders(symbol, since, limit, params)
|
||
elif marketType == 'swap':
|
||
return await self.fetch_open_swap_orders(symbol, since, limit, params)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
|
||
async def fetch_open_spot_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
@ignore
|
||
fetch all unfilled currently open orders for spot markets
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-order-list
|
||
https://coincatch.github.io/github.io/en/spot/#get-current-plan-orders
|
||
|
||
: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 boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.lastEndId]: *for trigger orders only* the last order id to fetch entries after
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'fetchOpenSpotOrders'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
request: dict = {}
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
isTrigger = False
|
||
isTrigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', isTrigger)
|
||
result = None
|
||
if isTrigger:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for trigger orders')
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
response = await self.privatePostApiSpotV1PlanCurrentPlan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728664710749,
|
||
# "data": {
|
||
# "nextFlag": False,
|
||
# "endId": 1228661660806787072,
|
||
# "orderList": [
|
||
# {
|
||
# "orderId": "1228669617606991872",
|
||
# "clientOid": "1228669617573437440",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "size": "50",
|
||
# "executePrice": "0",
|
||
# "triggerPrice": "4000",
|
||
# "status": "not_trigger",
|
||
# "orderType": "market",
|
||
# "side": "sell",
|
||
# "triggerType": "fill_price",
|
||
# "enterPointSource": "API",
|
||
# "placeType": null,
|
||
# "cTime": "1728663585092",
|
||
# "uTime": null
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'orderList', [])
|
||
else:
|
||
response = await self.privatePostApiSpotV1TradeOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725965783430,
|
||
# "data": [
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217347655911653376",
|
||
# "clientOrderId": "c57c07d1-bd00-4167-95e2-9b22a55fbc28",
|
||
# "price": "2000.0000000000000000",
|
||
# "quantity": "0.0010000000000000",
|
||
# "orderType": "limit",
|
||
# "side": "buy",
|
||
# "status": "new",
|
||
# "fillPrice": "0",
|
||
# "fillQuantity": "0.0000000000000000",
|
||
# "fillTotalAmount": "0.0000000000000000",
|
||
# "enterPointSource": "API",
|
||
# "feeDetail": "",
|
||
# "orderSource": "normal",
|
||
# "cTime": "1725964219072"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
result = self.safe_list(response, 'data', [])
|
||
return self.parse_orders(result, market, since, limit)
|
||
|
||
async def fetch_open_swap_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
@ignore
|
||
fetch all unfilled currently open orders for swap markets
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-open-order
|
||
https://coincatch.github.io/github.io/en/mix/#get-all-open-order
|
||
https://coincatch.github.io/github.io/en/mix/#get-plan-order-tpsl-list
|
||
|
||
: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 boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.isPlan]: 'plan' or 'profit_loss'('plan'(default) for trigger(plan) orders, 'profit_loss' for stop-loss and take-profit orders)
|
||
:param str [params.productType]: 'umcbl' or 'dmcbl' - the product type of the market to fetch entries for(default 'umcbl')
|
||
:param str [params.marginCoin]: the margin coin of the market to fetch entries for
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
methodName = 'fetchOpenSwapOrders'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
isTrigger = False
|
||
isTrigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', isTrigger)
|
||
plan: Str = None
|
||
plan, params = self.handle_option_and_params(params, methodName, 'isPlan', plan)
|
||
productType = self.handle_option(methodName, 'productType')
|
||
market: Market = None
|
||
response = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if (isTrigger) or (plan is not None): # the same endpoint is used for trigger and stop-loss/take-profit orders
|
||
if productType is not None:
|
||
request['productType'] = productType
|
||
if plan is not None:
|
||
request['isPlan'] = plan # current param is used to define the type of the orders to fetch(trigger or stop-loss/take-profit)
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1729168682690,
|
||
# "data": [
|
||
# {
|
||
# "orderId": "1230779428914049025",
|
||
# "clientOid": "1230779428914049024",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "marginCoin": "USDT",
|
||
# "size": "0.01",
|
||
# "executePrice": "1000",
|
||
# "triggerPrice": "1200",
|
||
# "status": "not_trigger",
|
||
# "orderType": "limit",
|
||
# "planType": "normal_plan",
|
||
# "side": "buy_single",
|
||
# "triggerType": "fill_price",
|
||
# "presetTakeProfitPrice": "4000",
|
||
# "presetTakeLossPrice": "900",
|
||
# "rangeRate": "",
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "buy_single",
|
||
# "holdMode": "single_hold",
|
||
# "reduceOnly": False,
|
||
# "cTime": "1729166603306",
|
||
# "uTime": null
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiMixV1PlanCurrentPlan(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetApiMixV1OrderCurrent(self.extend(request, params))
|
||
elif isTrigger:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for swap trigger orders')
|
||
else:
|
||
if productType is None:
|
||
productType = 'umcbl'
|
||
request: dict = {
|
||
'productType': productType, # is mandatory for current endpoint(all open non-trigger orders)
|
||
}
|
||
marginCoin: Str = None
|
||
marginCoin = self.handle_option(methodName, 'marginCoin', marginCoin)
|
||
if marginCoin is not None:
|
||
request['marginCoin'] = marginCoin
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728127869097,
|
||
# "data": [
|
||
# {
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "size": 0.02,
|
||
# "orderId": "1226422495431974913",
|
||
# "clientOid": "1226422495457140736",
|
||
# "filledQty": 0.00,
|
||
# "fee": 0E-8,
|
||
# "price": 500.00,
|
||
# "state": "new",
|
||
# "side": "buy_single",
|
||
# "timeInForce": "normal",
|
||
# "totalProfits": 0E-8,
|
||
# "posSide": "long",
|
||
# "marginCoin": "USDT",
|
||
# "filledAmount": 0.0000,
|
||
# "orderType": "limit",
|
||
# "leverage": "5",
|
||
# "marginMode": "crossed",
|
||
# "reduceOnly": False,
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "buy_single",
|
||
# "holdMode": "single_hold",
|
||
# "orderSource": "normal",
|
||
# "cTime": "1728127829422",
|
||
# "uTime": "1728127830980"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiMixV1OrderMarginCoinCurrent(self.extend(request, params))
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_orders(data, market, since, limit)
|
||
|
||
async def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple canceled and closed orders made by the user
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-order-list
|
||
https://coincatch.github.io/github.io/en/spot/#get-history-plan-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-history-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-producttype-history-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-history-plan-orders-tpsl
|
||
|
||
:param str symbol: *is mandatory* unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch orders for
|
||
:param boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.isPlan]: *swap only* 'plan' or 'profit_loss'('plan'(default) for trigger(plan) orders, 'profit_loss' for stop-loss and take-profit orders)
|
||
:param str [params.type]: 'spot' or 'swap' - the type of the market to fetch entries for(default 'spot')
|
||
:param str [params.productType]: *swap only* 'umcbl' or 'dmcbl' - the product type of the market to fetch entries for(default 'umcbl')
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'fetchCanceledAndClosedOrders'
|
||
await self.load_markets()
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
params['methodName'] = methodName
|
||
if marketType == 'spot':
|
||
return await self.fetch_canceled_and_closed_spot_orders(symbol, since, limit, params)
|
||
elif marketType == 'swap':
|
||
return await self.fetch_canceled_and_closed_swap_orders(symbol, since, limit, params)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
|
||
async def fetch_canceled_and_closed_spot_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
@ignore
|
||
fetches information on multiple canceled and closed orders made by the user on spot markets
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-order-history
|
||
https://coincatch.github.io/github.io/en/spot/#get-history-plan-orders
|
||
|
||
:param str symbol: *is mandatory* unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: *for trigger orders only* the latest time in ms to fetch orders for
|
||
:param boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.lastEndId]: *for trigger orders only* the last order id to fetch entries after
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'fetchCanceledAndClosedSpotOrders'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for spot markets')
|
||
maxLimit = 500
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
requestLimit = limit
|
||
isTrigger = False
|
||
isTrigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', isTrigger)
|
||
result = None
|
||
if isTrigger:
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until', until)
|
||
# now = self.milliseconds()
|
||
requestSince = since
|
||
interval = 90 * 24 * 60 * 60 * 1000 # startTime and endTime interval cannot be greater than 90 days
|
||
now = self.milliseconds()
|
||
# both since and until are required for trigger orders
|
||
if (until is None) and (requestSince is None):
|
||
requestSince = now - interval
|
||
until = now
|
||
elif until is not None:
|
||
requestSince = until - interval
|
||
else: # if since is defined
|
||
until = since + interval
|
||
request['startTime'] = requestSince
|
||
request['endTime'] = until
|
||
if requestLimit is None:
|
||
requestLimit = maxLimit
|
||
request['pageSize'] = requestLimit
|
||
response = await self.privatePostApiSpotV1PlanHistoryPlan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728668998002,
|
||
# "data": {
|
||
# "nextFlag": False,
|
||
# "endId": 1228669617606991872,
|
||
# "orderList": [
|
||
# {
|
||
# "orderId": "1228669617606991872",
|
||
# "clientOid": "1228669617573437440",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "size": "50",
|
||
# "executePrice": "0",
|
||
# "triggerPrice": "4000",
|
||
# "status": "cancel",
|
||
# "orderType": "market",
|
||
# "side": "sell",
|
||
# "triggerType": "fill_price",
|
||
# "enterPointSource": "API",
|
||
# "placeType": null,
|
||
# "cTime": "1728663585092",
|
||
# "uTime": "1728666719223"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'orderList', [])
|
||
else:
|
||
if since is not None:
|
||
request['after'] = since
|
||
requestLimit = maxLimit
|
||
if requestLimit is not None:
|
||
request['limit'] = requestLimit
|
||
response = await self.privatePostApiSpotV1TradeHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725963777690,
|
||
# "data": [
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217143186968068096",
|
||
# "clientOrderId": "8fa3eb89-2377-4519-a199-35d5db9ed262",
|
||
# "price": "0",
|
||
# "quantity": "10.0000000000000000",
|
||
# "orderType": "market",
|
||
# "side": "buy",
|
||
# "status": "full_fill",
|
||
# "fillPrice": "2340.5500000000000000",
|
||
# "fillQuantity": "0.0042000000000000",
|
||
# "fillTotalAmount": "9.8303100000000000",
|
||
# "enterPointSource": "API",
|
||
# "feeDetail": "{
|
||
# \"ETH\": {
|
||
# \"deduction\": False,
|
||
# \"feeCoinCode\": \"ETH\",
|
||
# \"totalDeductionFee\": 0,
|
||
# \"totalFee\": -0.0000042000000000
|
||
# },
|
||
# \"newFees\": {
|
||
# \"c\": 0,
|
||
# \"d\": 0,
|
||
# \"deduction\": False,
|
||
# \"r\": -0.0000042,
|
||
# \"t\": -0.0000042,
|
||
# \"totalDeductionFee\": 0
|
||
# }
|
||
# }",
|
||
# "orderSource": "market",
|
||
# "cTime": "1725915469877"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
parsedResponse = json.loads(response) # the response is not a standard JSON
|
||
result = self.safe_list(parsedResponse, 'data', [])
|
||
return self.parse_orders(result, market, since, limit)
|
||
|
||
async def fetch_canceled_and_closed_swap_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
@ignore
|
||
fetches information on multiple canceled and closed orders made by the user on swap markets
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-history-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-producttype-history-orders
|
||
https://coincatch.github.io/github.io/en/mix/#get-history-plan-orders-tpsl
|
||
|
||
:param str [symbol]: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch orders for
|
||
:param boolean [params.trigger]: True if fetching trigger orders(default False)
|
||
:param str [params.isPlan]: *swap only* 'plan' or 'profit_loss'('plan'(default) for trigger(plan) orders, 'profit_loss' for stop-loss and take-profit orders)
|
||
:param str [params.productType]: *swap only* 'umcbl' or 'dmcbl' - the product type of the market to fetch entries for(default 'umcbl')
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'fetchCanceledAndClosedSwapOrders'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
requestSince = since
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until', until)
|
||
now = self.milliseconds()
|
||
# since and until are mandatory
|
||
# they should be within 90 days interval
|
||
interval = 90 * 24 * 60 * 60 * 1000
|
||
if (until is None) and (requestSince is None):
|
||
requestSince = now - interval
|
||
until = now
|
||
elif until is not None:
|
||
requestSince = until - interval
|
||
else: # if since is defined
|
||
until = since + interval
|
||
request: dict = {
|
||
'startTime': requestSince,
|
||
'endTime': until,
|
||
}
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
productType = self.handle_option(methodName, 'productType')
|
||
isTrigger = False
|
||
isTrigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', isTrigger)
|
||
plan: Str = None
|
||
plan, params = self.handle_option_and_params(params, methodName, 'isPlan', plan)
|
||
response = None
|
||
result = None
|
||
if (isTrigger) or (plan is not None):
|
||
if plan is not None:
|
||
request['isPlan'] = plan
|
||
if productType is not None:
|
||
request['productType'] = productType
|
||
response = await self.privateGetApiMixV1PlanHistoryPlan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1729174716526,
|
||
# "data": [
|
||
# {
|
||
# "orderId": "1230763430987104257",
|
||
# "clientOid": "1230763431003881472",
|
||
# "executeOrderId": "",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "marginCoin": "USDT",
|
||
# "size": "0.03",
|
||
# "executePrice": "0",
|
||
# "triggerPrice": "2000",
|
||
# "status": "cancel",
|
||
# "orderType": "market",
|
||
# "planType": "loss_plan",
|
||
# "side": "sell_single",
|
||
# "triggerType": "fill_price",
|
||
# "presetTakeProfitPrice": "0",
|
||
# "presetTakeLossPrice": "0",
|
||
# "rangeRate": null,
|
||
# "enterPointSource": "SYS",
|
||
# "tradeSide": "sell_single",
|
||
# "holdMode": "single_hold",
|
||
# "reduceOnly": True,
|
||
# "executeTime": "1729173770776",
|
||
# "executeSize": "0",
|
||
# "cTime": "1729162789103",
|
||
# "uTime": "1729173770776"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
result = self.safe_list(response, 'data', [])
|
||
else:
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
response = await self.privateGetApiMixV1OrderHistory(self.extend(request, params))
|
||
else:
|
||
if productType is None:
|
||
productType = 'umcbl' # is mandatory for current endpoint
|
||
request['productType'] = productType
|
||
response = await self.privateGetApiMixV1OrderHistoryProductType(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728129807637,
|
||
# "data": {
|
||
# "nextFlag": False,
|
||
# "endId": "1221413696648339457",
|
||
# "orderList": [
|
||
# {
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "size": 0.1,
|
||
# "orderId": "1225467075288719360",
|
||
# "clientOid": "1225467075288719361",
|
||
# "filledQty": 0.1,
|
||
# "fee": -0.00005996,
|
||
# "price": null,
|
||
# "priceAvg": 2362.03,
|
||
# "state": "filled",
|
||
# "side": "burst_close_long",
|
||
# "timeInForce": "normal",
|
||
# "totalProfits": -0.00833590,
|
||
# "posSide": "long",
|
||
# "marginCoin": "ETH",
|
||
# "filledAmount": 236.20300000,
|
||
# "orderType": "market",
|
||
# "leverage": "12",
|
||
# "marginMode": "fixed",
|
||
# "reduceOnly": True,
|
||
# "enterPointSource": "SYS",
|
||
# "tradeSide": "burst_close_long",
|
||
# "holdMode": "double_hold",
|
||
# "orderSource": "market",
|
||
# "cTime": "1727900039503",
|
||
# "uTime": "1727900039576"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'orderList', [])
|
||
return self.parse_orders(result, market)
|
||
|
||
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
cancels an open order
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#cancel-order-v2
|
||
https://coincatch.github.io/github.io/en/spot/#cancel-plan-order
|
||
https://coincatch.github.io/github.io/en/mix/#cancel-order
|
||
https://coincatch.github.io/github.io/en/mix/#cancel-plan-order-tpsl
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.clientOrderId]: a unique id for the order that can be used alternative for the id
|
||
:param bool [params.trigger]: True for canceling a trigger order(default False)
|
||
:param bool [params.stop]: *swap only* an alternative for trigger param
|
||
:param str [params.planType]: *swap trigger only* the type of the plan order to cancel: 'profit_plan' - profit order, 'loss_plan' - loss order, 'normal_plan' - plan order, 'pos_profit' - position profit, 'pos_loss' - position loss, 'moving_plan' - Trailing TP/SL, 'track_plan' - Trailing Stop
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'cancelOrder'
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {}
|
||
clientOrderId: Str = None
|
||
clientOrderId, params = self.handle_param_string(params, 'clientOrderId')
|
||
if (id is None) and (clientOrderId is None):
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires an id argument or clientOrderId parameter')
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
else:
|
||
request['orderId'] = id
|
||
marketType = market['type']
|
||
trigger = False
|
||
trigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', trigger)
|
||
response = None
|
||
if not trigger or (marketType != 'spot'):
|
||
request['symbol'] = market['id']
|
||
if marketType == 'spot':
|
||
if trigger:
|
||
response = await self.privatePostApiSpotV1PlanCancelPlan(self.extend(request, params))
|
||
else:
|
||
response = await self.privatePostApiSpotV1TradeCancelOrderV2(self.extend(request, params))
|
||
elif marketType == 'swap':
|
||
planType: Str = None
|
||
planType, params = self.handle_option_and_params(params, methodName, 'planType', planType)
|
||
request['marginCoin'] = market['settleId']
|
||
if (trigger) or (planType is not None):
|
||
if planType is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a planType parameter for swap trigger orders("profit_plan" - profit order, "loss_plan" - loss order, "normal_plan" - plan order, "pos_profit" - position profit, "pos_loss" - position loss, "moving_plan" - Trailing TP/SL, "track_plan" - Trailing Stop)')
|
||
request['planType'] = planType
|
||
response = await self.privatePostApiMixV1PlanCancelPlan(self.extend(request, params))
|
||
else:
|
||
response = await self.privatePostApiMixV1OrderCancelOrder(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data, market)
|
||
|
||
async def cancel_all_orders(self, symbol: Str = None, params={}):
|
||
"""
|
||
cancels all open orders
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#cancel-all-orders
|
||
https://coincatch.github.io/github.io/en/spot/#batch-cancel-plan-orders
|
||
https://coincatch.github.io/github.io/en/mix/#batch-cancel-order
|
||
https://coincatch.github.io/github.io/en/mix/#cancel-order-by-symbol
|
||
https://coincatch.github.io/github.io/en/mix/#cancel-plan-order-tpsl-by-symbol
|
||
https://coincatch.github.io/github.io/en/mix/#cancel-all-trigger-order-tpsl
|
||
|
||
:param str [symbol]: unified symbol of the market the orders were made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: 'spot' or 'swap' - the type of the market to cancel orders for(default 'spot')
|
||
:param bool [params.trigger]: True for canceling a trigger orders(default False)
|
||
:param str [params.productType]: *swap only(if symbol is not provided* 'umcbl' or 'dmcbl' - the product type of the market to cancel orders for(default 'umcbl')
|
||
:param str [params.marginCoin]: *mandatory for swap non-trigger dmcb(if symbol is not provided)* the margin coin of the market to cancel orders for
|
||
:param str [params.planType]: *swap trigger only* the type of the plan order to cancel: 'profit_plan' - profit order, 'loss_plan' - loss order, 'normal_plan' - plan order, 'pos_profit' - position profit, 'pos_loss' - position loss, 'moving_plan' - Trailing TP/SL, 'track_plan' - Trailing Stop
|
||
:returns dict: response from the exchange
|
||
"""
|
||
methodName = 'cancelAllOrders'
|
||
await self.load_markets()
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request: dict = {}
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
trigger = False
|
||
trigger, params = self.handle_option_and_params_2(params, methodName, 'trigger', 'stop', trigger)
|
||
response = None
|
||
if marketType == 'spot':
|
||
if trigger:
|
||
if symbol is not None:
|
||
request['symbols'] = [market['id']]
|
||
response = await self.privatePostApiSpotV1PlanBatchCancelPlan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728670464735,
|
||
# "data": [
|
||
# {
|
||
# "orderId": "1228661660806787072",
|
||
# "clientOid": "1228661660752261120",
|
||
# "result": True
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_orders(data, market)
|
||
else:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for spot non-trigger orders')
|
||
request['symbol'] = market['id']
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725989560461,
|
||
# "data": "ETHUSDT_SPBL"
|
||
# }
|
||
#
|
||
response = await self.privatePostApiSpotV1TradeCancelSymbolOrder(self.extend(request, params))
|
||
elif marketType == 'swap':
|
||
productType = 'umcbl'
|
||
if symbol is not None:
|
||
request['symbol'] = market['id']
|
||
else:
|
||
productType = self.handle_option(methodName, 'productType', productType)
|
||
request['productType'] = productType # we need either symbol or productType
|
||
planType: Str = None
|
||
planType, params = self.handle_option_and_params(params, methodName, 'planType', planType)
|
||
if (trigger) or (planType is not None): # if trigger or stop-loss/take-profit orders
|
||
if planType is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a planType parameter for swap trigger orders("profit_plan" - profit order, "loss_plan" - loss order, "normal_plan" - plan order, "pos_profit" - position profit, "pos_loss" - position loss, "moving_plan" - Trailing TP/SL, "track_plan" - Trailing Stop)')
|
||
request['planType'] = planType
|
||
if symbol is not None:
|
||
response = await self.privatePostApiMixV1PlanCancelSymbolPlan(self.extend(request, params))
|
||
else:
|
||
response = await self.privatePostApiMixV1PlanCancelAllPlan(self.extend(request, params))
|
||
elif symbol is not None: # if non-trigger orders and symbol is provided
|
||
request['marginCoin'] = market['settleId']
|
||
response = await self.privatePostApiMixV1OrderCancelSymbolOrders(self.extend(request, params))
|
||
else: # if non-trigger orders and symbol is not provided
|
||
marginCoin: Str = None
|
||
if productType == 'umcbl':
|
||
marginCoin = 'USDT'
|
||
else:
|
||
marginCoin, params = self.handle_option_and_params(params, methodName, 'marginCoin', marginCoin)
|
||
if marginCoin is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a marginCoin parameter for dmcbl product type')
|
||
request['marginCoin'] = marginCoin
|
||
response = await self.privatePostApiMixV1OrderCancelAllOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1729104940774,
|
||
# "data": {
|
||
# "result": True,
|
||
# "order_ids": ["1230500426827522049"],
|
||
# "client_order_ids": ["1230500426898825216"],
|
||
# "fail_infos": []
|
||
# }
|
||
# }
|
||
#
|
||
result = self.get_result_from_batch_canceling_swap_orders(response)
|
||
return self.parse_orders(result, market)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
order = self.safe_order(response)
|
||
order['info'] = response
|
||
return [order]
|
||
|
||
async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
||
"""
|
||
cancel multiple non-trigger orders
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#cancel-order-in-batch-v2-single-instruments
|
||
|
||
:param str[] ids: order ids
|
||
:param str symbol: *is mandatory* unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str[] [params.clientOrderIds]: client order ids
|
||
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
methodName = 'cancelOrders'
|
||
# only non-trigger and not tp/sl orders can be canceled via cancelOrders
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request = {
|
||
'symbol': market['id'],
|
||
}
|
||
marketType = market['type']
|
||
clientOrderIds = self.safe_list(params, 'clientOrderIds')
|
||
if clientOrderIds is not None:
|
||
request['clientOids'] = clientOrderIds
|
||
params = self.omit(params, 'clientOrderIds')
|
||
elif ids is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires either ids argument or clientOrderIds parameter')
|
||
else:
|
||
request['orderIds'] = ids
|
||
response = None
|
||
result = None
|
||
if marketType == 'spot':
|
||
response = await self.privatePostApiSpotV1TradeCancelBatchOrdersV2(self.extend(request))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726491486352,
|
||
# "data": {
|
||
# "resultList": [
|
||
# {
|
||
# "orderId": "1219555778395160576",
|
||
# "clientOrderId": "e229d70a-bb16-4633-a45c-d7f4d3b5d2cf"
|
||
# }
|
||
# ],
|
||
# "failure": [
|
||
# {
|
||
# "orderId": "123124124",
|
||
# "clientOrderId": null,
|
||
# "errorMsg": "The order does not exist",
|
||
# "errorCode": "43001"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'resultList', [])
|
||
elif marketType == 'swap':
|
||
request['marginCoin'] = market['settleId']
|
||
response = await self.privatePostApiMixV1OrderCancelBatchOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1729101962321,
|
||
# "data": {
|
||
# "result": True,
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "order_ids": ["1226441551501418496", "1230506854262857729"],
|
||
# "client_order_ids": [],
|
||
# "fail_infos": []
|
||
# }
|
||
# }
|
||
#
|
||
result = self.get_result_from_batch_canceling_swap_orders(response)
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
return self.parse_orders(result, market)
|
||
|
||
def get_result_from_batch_canceling_swap_orders(self, response):
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = []
|
||
orderIds = self.safe_value(data, 'order_ids', [])
|
||
for i in range(0, len(orderIds)):
|
||
orderId = orderIds[i]
|
||
resultItem = {
|
||
'orderId': orderId,
|
||
}
|
||
result.append(resultItem)
|
||
return result
|
||
|
||
def parse_order(self, order, market=None) -> Order:
|
||
#
|
||
# createOrder spot
|
||
# {
|
||
# "orderId": "1217143186968068096",
|
||
# "clientOrderId": "8fa3eb89-2377-4519-a199-35d5db9ed262"
|
||
# }
|
||
#
|
||
# createOrder swap
|
||
# {
|
||
# "clientOid": "1225791137701519360",
|
||
# "orderId": "1225791137697325056"
|
||
# }
|
||
#
|
||
# privatePostApiSpotV1TradeOrderInfo, privatePostApiSpotV1TradeHistory
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217143186968068096",
|
||
# "clientOrderId": "8fa3eb89-2377-4519-a199-35d5db9ed262",
|
||
# "price": "0",
|
||
# "quantity": "10.0000000000000000",
|
||
# "orderType": "market",
|
||
# "side": "buy",
|
||
# "status": "full_fill",
|
||
# "fillPrice": "2340.5500000000000000",
|
||
# "fillQuantity": "0.0042000000000000",
|
||
# "fillTotalAmount": "9.8303100000000000",
|
||
# "enterPointSource": "API",
|
||
# "feeDetail": "{
|
||
# \"ETH\": {
|
||
# \"deduction\": False,
|
||
# \"feeCoinCode\": \"ETH\",
|
||
# \"totalDeductionFee\": 0,
|
||
# \"totalFee\": -0.0000042000000000},
|
||
# \"newFees\": {
|
||
# \"c\": 0,
|
||
# \"d\": 0,
|
||
# \"deduction\": False,
|
||
# \"r\": -0.0000042,
|
||
# \"t\": -0.0000042,
|
||
# \"totalDeductionFee\": 0
|
||
# }
|
||
# }",
|
||
# "orderSource": "market",
|
||
# "cTime": "1725915469877"
|
||
# }
|
||
#
|
||
# privatePostApiMixV1OrderDetail, privateGetApiMixV1OrderMarginCoinCurrent
|
||
# {
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "size": 0.01,
|
||
# "orderId": "1225791137697325056",
|
||
# "clientOid": "1225791137701519360",
|
||
# "filledQty": 0.01,
|
||
# "fee": -0.01398864,
|
||
# "price": null,
|
||
# "priceAvg": 2331.44,
|
||
# "state": "filled",
|
||
# "side": "close_long",
|
||
# "timeInForce": "normal",
|
||
# "totalProfits": -2.23680000,
|
||
# "posSide": "long",
|
||
# "marginCoin": "USDT",
|
||
# "filledAmount": 23.3144,
|
||
# "orderType": "market",
|
||
# "leverage": "5",
|
||
# "marginMode": "crossed",
|
||
# "reduceOnly": True,
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "close_long",
|
||
# "holdMode": "double_hold",
|
||
# "orderSource": "market",
|
||
# "cTime": "1727977302003",
|
||
# "uTime": "1727977303604"
|
||
# }
|
||
#
|
||
# privatePostApiSpotV1TradeOpenOrders
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217347655911653376",
|
||
# "clientOrderId": "c57c07d1-bd00-4167-95e2-9b22a55fbc28",
|
||
# "price": "2000.0000000000000000",
|
||
# "quantity": "0.0010000000000000",
|
||
# "orderType": "limit",
|
||
# "side": "buy",
|
||
# "status": "new",
|
||
# "fillPrice": "0",
|
||
# "fillQuantity": "0.0000000000000000",
|
||
# "fillTotalAmount": "0.0000000000000000",
|
||
# "enterPointSource": "API",
|
||
# "feeDetail": "",
|
||
# "orderSource": "normal",
|
||
# "cTime": "1725964219072"
|
||
# }
|
||
#
|
||
# privatePostApiSpotV1PlanCurrentPlan, privatePostApiSpotV1PlanHistoryPlan
|
||
# {
|
||
# "orderId": "1228669617606991872",
|
||
# "clientOid": "1228669617573437440",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "size": "50",
|
||
# "executePrice": "0",
|
||
# "triggerPrice": "4000",
|
||
# "status": "not_trigger",
|
||
# "orderType": "market",
|
||
# "side": "sell",
|
||
# "triggerType": "fill_price",
|
||
# "enterPointSource": "API",
|
||
# "placeType": null,
|
||
# "cTime": "1728663585092",
|
||
# "uTime": null
|
||
# }
|
||
#
|
||
# privateGetApiMixV1PlanCurrentPlan
|
||
# {
|
||
# "orderId": "1230779428914049025",
|
||
# "clientOid": "1230779428914049024",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "marginCoin": "USDT",
|
||
# "size": "0.01",
|
||
# "executePrice": "1000",
|
||
# "triggerPrice": "1200",
|
||
# "status": "not_trigger",
|
||
# "orderType": "limit",
|
||
# "planType": "normal_plan",
|
||
# "side": "buy_single",
|
||
# "triggerType": "fill_price",
|
||
# "presetTakeProfitPrice": "4000",
|
||
# "presetTakeLossPrice": "900",
|
||
# "rangeRate": "",
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "buy_single",
|
||
# "holdMode": "single_hold",
|
||
# "reduceOnly": False,
|
||
# "cTime": "1729166603306",
|
||
# "uTime": null
|
||
# }
|
||
#
|
||
marketId = self.safe_string(order, 'symbol')
|
||
marginCoin = self.safe_string(order, 'marginCoin')
|
||
market = self.safe_market_custom(marketId, market, marginCoin)
|
||
timestamp = self.safe_integer(order, 'cTime')
|
||
price = self.omit_zero(self.safe_string_2(order, 'price', 'executePrice')) # price is zero for market orders
|
||
priceAvg = self.omit_zero(self.safe_string(order, 'priceAvg'))
|
||
if price is None:
|
||
price = priceAvg
|
||
type = self.safe_string(order, 'orderType')
|
||
side = self.parse_order_side(self.safe_string_lower(order, 'side'))
|
||
amount = self.safe_string_2(order, 'quantity', 'size')
|
||
isTrigger = self.safe_string(order, 'triggerType') is not None
|
||
isMarketBuy = (type == 'market') and (side == 'buy')
|
||
if (market['spot']) and (isMarketBuy) and (not isTrigger):
|
||
amount = None # cost instead of amount is returned for market buy spot non-trigger orders
|
||
status = self.safe_string_2(order, 'status', 'state')
|
||
feeDetailString = self.safe_string(order, 'feeDetail')
|
||
fees = None
|
||
feeCurrency: Str = None
|
||
feeCost: Str = None
|
||
if feeDetailString is not None:
|
||
fees = self.parse_fee_detail_string(feeDetailString)
|
||
else:
|
||
feeCurrency = self.safe_currency_code(marginCoin) if marginCoin else None
|
||
feeCost = Precise.string_abs(self.safe_string(order, 'fee'))
|
||
timeInForce = self.parse_order_time_in_force(self.safe_string_lower(order, 'timeInForce'))
|
||
postOnly: Bool = None
|
||
if timeInForce is not None:
|
||
postOnly = timeInForce == 'PO'
|
||
triggerPrice = self.omit_zero(self.safe_string(order, 'triggerPrice'))
|
||
takeProfitPrice = self.omit_zero(self.safe_string(order, 'presetTakeProfitPrice'))
|
||
stopLossPrice = self.omit_zero(self.safe_string_2(order, 'presetTakeProfitPrice', 'presetTakeLossPrice'))
|
||
planType = self.safe_string(order, 'planType')
|
||
if planType == 'loss_plan':
|
||
stopLossPrice = triggerPrice
|
||
elif planType == 'profit_plan':
|
||
takeProfitPrice = triggerPrice
|
||
return self.safe_order({
|
||
'id': self.safe_string(order, 'orderId'),
|
||
'clientOrderId': self.safe_string_2(order, 'clientOrderId', 'clientOid'),
|
||
'datetime': self.iso8601(timestamp),
|
||
'timestamp': timestamp,
|
||
'lastTradeTimestamp': None,
|
||
'lastUpdateTimestamp': self.safe_integer(order, 'uTime'),
|
||
'status': self.parse_order_status(status),
|
||
'symbol': market['symbol'],
|
||
'type': type,
|
||
'timeInForce': timeInForce,
|
||
'side': side,
|
||
'price': price,
|
||
'average': priceAvg if priceAvg else self.safe_string(order, 'fillPrice'),
|
||
'amount': amount,
|
||
'filled': self.safe_string_2(order, 'fillQuantity', 'filledQty'),
|
||
'remaining': None,
|
||
'triggerPrice': triggerPrice,
|
||
'takeProfitPrice': takeProfitPrice,
|
||
'stopLossPrice': stopLossPrice,
|
||
'cost': self.safe_string_2(order, 'fillTotalAmount', 'filledAmount'),
|
||
'trades': None,
|
||
'fee': {
|
||
'currency': feeCurrency,
|
||
'cost': feeCost,
|
||
},
|
||
'fees': fees,
|
||
'reduceOnly': self.safe_bool(order, 'reduceOnly'),
|
||
'postOnly': postOnly,
|
||
'info': order,
|
||
}, market)
|
||
|
||
def parse_order_status(self, status: Str) -> Str:
|
||
satuses = {
|
||
'not_trigger': 'open',
|
||
'init': 'open',
|
||
'new': 'open',
|
||
'partially_filled': 'open',
|
||
'full_fill': 'closed',
|
||
'filled': 'closed',
|
||
'cancel': 'canceled',
|
||
'canceled': 'canceled',
|
||
'cancelled': 'canceled',
|
||
}
|
||
return self.safe_string(satuses, status, status)
|
||
|
||
def parse_order_side(self, side: Str) -> Str:
|
||
sides = {
|
||
'buy': 'buy',
|
||
'sell': 'sell',
|
||
'open_long': 'buy',
|
||
'open_short': 'sell',
|
||
'close_long': 'sell',
|
||
'close_short': 'buy',
|
||
'reduce_close_long': 'sell',
|
||
'reduce_close_short': 'buy',
|
||
'offset_close_long': 'sell',
|
||
'offset_close_short': 'buy',
|
||
'burst_close_long': 'sell',
|
||
'burst_close_short': 'buy',
|
||
'delivery_close_long': 'sell',
|
||
'delivery_close_short': 'buy',
|
||
'buy_single': 'buy',
|
||
'sell_single': 'sell',
|
||
}
|
||
return self.safe_string(sides, side, side)
|
||
|
||
def parse_order_time_in_force(self, timeInForce: Str) -> Str:
|
||
timeInForces = {
|
||
'normal': 'GTC',
|
||
'post_only': 'PO',
|
||
'iok': 'IOC',
|
||
'fok': 'FOK',
|
||
}
|
||
return self.safe_string(timeInForces, timeInForce, timeInForce)
|
||
|
||
def parse_fee_detail_string(self, feeDetailString: Str):
|
||
result = []
|
||
feeDetail = self.parse_json(feeDetailString)
|
||
if feeDetail:
|
||
keys = list(feeDetail.keys())
|
||
for i in range(0, len(keys)):
|
||
currencyId = self.safe_string(keys, i)
|
||
if currencyId in self.currencies_by_id:
|
||
currency = self.safe_currency_code(currencyId)
|
||
feeEntry = self.safe_dict(feeDetail, currencyId, {})
|
||
amount = Precise.string_abs(self.safe_string(feeEntry, 'totalFee'))
|
||
result.append({
|
||
'currency': currency,
|
||
'amount': amount,
|
||
})
|
||
return result
|
||
|
||
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
fetch all trades made by the user
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-transaction-details
|
||
https://coincatch.github.io/github.io/en/mix/#get-order-fill-detail
|
||
https://coincatch.github.io/github.io/en/mix/#get-producttype-order-fill-detail
|
||
|
||
:param str symbol: *is mandatory* unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
: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]: *swap markets only* the latest time in ms to fetch trades for, only supports the last 30 days timeframe
|
||
:param str [params.lastEndId]: *swap markets only* query the data after self tradeId
|
||
:returns Trade[]: a list of `trade structures <https://github.com/ccxt/ccxt/wiki/Manual#trade-structure>`
|
||
"""
|
||
methodName = 'fetchMyTrades'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
await self.load_markets()
|
||
market: Market = None
|
||
marketType = 'spot'
|
||
request: dict = {}
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
marketType = market['type']
|
||
request['symbol'] = market['id']
|
||
else:
|
||
marketType, params = self.handle_market_type_and_params(methodName, market, params, marketType)
|
||
if marketType == 'spot':
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument for spot markets')
|
||
response = None
|
||
requestLimit = limit
|
||
if marketType == 'spot':
|
||
maxSpotLimit = 500
|
||
if since is not None:
|
||
requestLimit = maxSpotLimit
|
||
if requestLimit is not None:
|
||
request['limit'] = requestLimit
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1725968747299,
|
||
# "data": [
|
||
# {
|
||
# "accountId": "1002820815393",
|
||
# "symbol": "ETHUSDT_SPBL",
|
||
# "orderId": "1217143186968068096",
|
||
# "fillId": "1217143193356505089",
|
||
# "orderType": "market",
|
||
# "side": "buy",
|
||
# "fillPrice": "2340.55",
|
||
# "fillQuantity": "0.0042",
|
||
# "fillTotalAmount": "9.83031",
|
||
# "feeCcy": "ETH",
|
||
# "fees": "-0.0000042",
|
||
# "takerMakerFlag": "taker",
|
||
# "cTime": "1725915471400"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privatePostApiSpotV1TradeFills(self.extend(request, params))
|
||
elif marketType == 'swap':
|
||
if since is not None:
|
||
params['startTime'] = since
|
||
else:
|
||
params['startTime'] = 0 # mandatory
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
else:
|
||
request['endTime'] = self.milliseconds() # mandatory
|
||
if symbol is not None:
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728306590704,
|
||
# "data": [
|
||
# {
|
||
# "tradeId": "1221355735285014530",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "orderId": "1221355728716259329",
|
||
# "price": "2555.12",
|
||
# "sizeQty": "0.01",
|
||
# "fee": "-0.01533072",
|
||
# "side": "open_long",
|
||
# "fillAmount": "25.5512",
|
||
# "profit": "0",
|
||
# "enterPointSource": "API",
|
||
# "tradeSide": "open_long",
|
||
# "holdMode": "double_hold",
|
||
# "takerMakerFlag": "taker",
|
||
# "cTime": "1726919819661"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiMixV1OrderFills(self.extend(request, params))
|
||
else:
|
||
productType = 'umcbl'
|
||
productType = self.handle_option(methodName, 'productType', productType)
|
||
request['productType'] = productType
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1728306372044,
|
||
# "data": [
|
||
# {
|
||
# "tradeId": "1225467075440189441",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "orderId": "1225467075288719360",
|
||
# "price": "2362.03",
|
||
# "sizeQty": "0.1",
|
||
# "fee": "-0.00005996",
|
||
# "side": "burst_close_long",
|
||
# "fillAmount": "236.203",
|
||
# "profit": "-0.0083359",
|
||
# "enterPointSource": "SYS",
|
||
# "tradeSide": "burst_close_long",
|
||
# "holdMode": "double_hold",
|
||
# "takerMakerFlag": "taker",
|
||
# "cTime": "1727900039539"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
response = await self.privateGetApiMixV1OrderAllFills(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + marketType + ' type of markets')
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_trades(data, market, since, limit)
|
||
|
||
async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
fetch all the trades made from a single order
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-transaction-details
|
||
|
||
: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>`
|
||
"""
|
||
methodName = 'fetchOrderTrades'
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument')
|
||
request: dict = {
|
||
'orderId': id,
|
||
'methodName': methodName,
|
||
}
|
||
return await self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
|
||
|
||
async def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
|
||
"""
|
||
fetches the margin mode of the trading pair
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-single-account
|
||
|
||
:param str symbol: unified symbol of the market to fetch the margin mode for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin mode structure <https://docs.ccxt.com/#/?id=margin-mode-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
}
|
||
response = await self.privateGetApiMixV1AccountAccount(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726669633799,
|
||
# "data": {
|
||
# "marginCoin": "ETH",
|
||
# "locked": "0",
|
||
# "available": "0.01",
|
||
# "crossMaxAvailable": "0.01",
|
||
# "fixedMaxAvailable": "0.01",
|
||
# "maxTransferOut": "0.01",
|
||
# "equity": "0.01",
|
||
# "usdtEquity": "22.97657025",
|
||
# "btcEquity": "0.000386195288",
|
||
# "crossRiskRate": "0",
|
||
# "crossMarginLeverage": 100,
|
||
# "fixedLongLeverage": 100,
|
||
# "fixedShortLeverage": 100,
|
||
# "marginMode": "crossed",
|
||
# "holdMode": "double_hold",
|
||
# "unrealizedPL": "0",
|
||
# "bonus": "0",
|
||
# "crossedUnrealizedPL": "0",
|
||
# "isolatedUnrealizedPL": ""
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_margin_mode(data, market)
|
||
|
||
def parse_margin_mode(self, marginMode: dict, market=None) -> MarginMode:
|
||
marginType = self.safe_string_lower(marginMode, 'marginMode')
|
||
return {
|
||
'info': marginMode,
|
||
'symbol': self.safe_symbol(None, market),
|
||
'marginMode': self.parse_margin_mode_type(marginType),
|
||
}
|
||
|
||
def parse_margin_mode_type(self, type: str) -> str:
|
||
types: dict = {
|
||
'crossed': 'cross',
|
||
'fixed': 'isolated',
|
||
}
|
||
return self.safe_string(types, type, type)
|
||
|
||
async def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
||
"""
|
||
set margin mode to 'cross' or 'isolated'
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#change-margin-mode
|
||
|
||
:param str marginMode: 'cross' or 'isolated'
|
||
: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 + ' setMarginMode() requires a symbol argument')
|
||
marginMode = marginMode.lower()
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' setMarginMode() is not supported for ' + market['type'] + ' type of markets')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
'marginMode': self.encode_margin_mode_type(marginMode),
|
||
}
|
||
response = await self.privatePostApiMixV1AccountSetMarginMode(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726670096099,
|
||
# "data": {
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "longLeverage": 10,
|
||
# "shortLeverage": 10,
|
||
# "crossMarginLeverage": null,
|
||
# "marginMode": "fixed"
|
||
# }
|
||
# }
|
||
#
|
||
return response
|
||
|
||
def encode_margin_mode_type(self, type: str) -> str:
|
||
types: dict = {
|
||
'cross': 'crossed',
|
||
'isolated': 'fixed',
|
||
}
|
||
return self.safe_string(types, type, type)
|
||
|
||
async def fetch_position_mode(self, symbol: Str = None, params={}):
|
||
"""
|
||
fetchs the position mode, hedged or one way
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-single-account
|
||
|
||
:param str symbol: unified symbol of the market to fetch entry for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an object detailing whether the market is in hedged or one-way mode
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchPositionMode() requires a symbol argument')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' fetchPositionMode() is not supported for ' + market['type'] + ' type of markets')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
}
|
||
response = await self.privateGetApiMixV1AccountAccount(self.extend(request, params)) # same endpoint
|
||
data = self.safe_dict(response, 'data', {})
|
||
holdMode = self.safe_string(data, 'holdMode')
|
||
return {
|
||
'info': response,
|
||
'hedged': holdMode == 'double_hold',
|
||
}
|
||
|
||
async def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
|
||
"""
|
||
set hedged to True or False for a market
|
||
|
||
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Set%20Position%20Mode
|
||
|
||
:param bool hedged: set to True to use dualSidePosition
|
||
:param str symbol: unified symbol of the market to fetch entry for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.productType]: 'umcbl' or 'dmcbl'(default 'umcbl' if symbol is not provided)
|
||
:returns dict: response from the exchange
|
||
"""
|
||
methodName = 'setPositionMode'
|
||
defaultProductType = 'umcbl'
|
||
await self.load_markets()
|
||
productType = self.safe_string(params, 'productType')
|
||
if productType is None:
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' setPositionMode() is not supported for ' + market['type'] + ' type of markets')
|
||
marketId = market['id']
|
||
parts = marketId.split('_')
|
||
productType = self.safe_string_lower(parts, 1, productType)
|
||
else:
|
||
productType = self.handle_option(methodName, 'productType', defaultProductType)
|
||
request: dict = {
|
||
'productType': productType,
|
||
'holdMode': 'double_hold' if hedged else 'single_hold',
|
||
}
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726677135005,
|
||
# "data": {
|
||
# "marginCoin": "ETH",
|
||
# "dualSidePosition": False
|
||
# }
|
||
# }
|
||
#
|
||
return await self.privatePostApiMixV1AccountSetPositionMode(self.extend(request, params))
|
||
|
||
async def fetch_leverage(self, symbol: str, params={}) -> Leverage:
|
||
"""
|
||
fetch the set leverage for a market
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-single-account
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' fetchLeverage() is not supported for ' + market['type'] + ' type of markets')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
}
|
||
response = await self.privateGetApiMixV1AccountAccount(self.extend(request, params)) # same endpoint
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_leverage(data, market)
|
||
|
||
async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
||
"""
|
||
set the level of leverage for a market
|
||
|
||
https://hashkeyglobal-apidoc.readme.io/reference/change-futures-leverage-trade
|
||
|
||
:param float leverage: the rate of leverage
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.side]: *for isolated margin mode with hedged position mode only* 'long' or 'short'
|
||
:returns dict: response from the exchange
|
||
"""
|
||
methodName = 'setLeverage'
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a symbol argument')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
'leverage': leverage,
|
||
}
|
||
side: Str = None
|
||
side, params = self.handle_option_and_params(params, methodName, 'side')
|
||
if side is not None:
|
||
request['holdSide'] = side
|
||
response = await self.privatePostApiMixV1AccountSetLeverage(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726680486657,
|
||
# "data": {
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "longLeverage": 2,
|
||
# "shortLeverage": 2,
|
||
# "crossMarginLeverage": 2,
|
||
# "marginMode": "crossed"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_leverage(data, market)
|
||
|
||
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
||
#
|
||
# fetchLeverage
|
||
# {
|
||
# "marginCoin": "ETH",
|
||
# "locked": "0",
|
||
# "available": "0.01",
|
||
# "crossMaxAvailable": "0.01",
|
||
# "fixedMaxAvailable": "0.01",
|
||
# "maxTransferOut": "0.01",
|
||
# "equity": "0.01",
|
||
# "usdtEquity": "22.97657025",
|
||
# "btcEquity": "0.000386195288",
|
||
# "crossRiskRate": "0",
|
||
# "crossMarginLeverage": 100,
|
||
# "fixedLongLeverage": 100,
|
||
# "fixedShortLeverage": 100,
|
||
# "marginMode": "crossed",
|
||
# "holdMode": "double_hold",
|
||
# "unrealizedPL": "0",
|
||
# "bonus": "0",
|
||
# "crossedUnrealizedPL": "0",
|
||
# "isolatedUnrealizedPL": ""
|
||
# }
|
||
#
|
||
# setLeverage
|
||
# {
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "longLeverage": 2,
|
||
# "shortLeverage": 2,
|
||
# "crossMarginLeverage": 2,
|
||
# "marginMode": "crossed"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(leverage, 'symbol')
|
||
market = self.safe_market_custom(marketId, market)
|
||
marginMode = self.parse_margin_mode_type(self.safe_string_lower(leverage, 'marginMode'))
|
||
longLeverage = self.safe_integer_2(leverage, 'fixedLongLeverage', 'longLeverage')
|
||
shortLeverage = self.safe_integer_2(leverage, 'fixedShortLeverage', 'shortLeverage')
|
||
crossMarginLeverage = self.safe_integer(leverage, 'crossMarginLeverage')
|
||
if marginMode == 'cross':
|
||
longLeverage = crossMarginLeverage
|
||
shortLeverage = crossMarginLeverage
|
||
return {
|
||
'info': leverage,
|
||
'symbol': market['symbol'],
|
||
'marginMode': marginMode,
|
||
'longLeverage': longLeverage,
|
||
'shortLeverage': shortLeverage,
|
||
}
|
||
|
||
async def modify_margin_helper(self, symbol: str, amount, type, params={}) -> MarginModification:
|
||
methodName = 'modifyMarginHelper'
|
||
methodName, params = self.handle_param_string(params, 'methodName', methodName)
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['type'] != 'swap':
|
||
raise NotSupported(self.id + ' ' + methodName + '() is not supported for ' + market['type'] + ' type of markets')
|
||
amount = self.amount_to_precision(symbol, amount)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
'amount': amount, # positive value for adding margin, negative for reducing
|
||
}
|
||
side: Str = None
|
||
side, params = self.handle_option_and_params(params, methodName, 'side')
|
||
if side is not None:
|
||
request['holdSide'] = side
|
||
response = await self.privatePostApiMixV1AccountSetMargin(self.extend(request, params))
|
||
# todo check response
|
||
# always returns error
|
||
# addMargin - "code":"45006","msg":"Insufficient position","requestTime":1729162281543,"data":null
|
||
# reduceMargin - "code":"40800","msg":"Insufficient amount of margin","requestTime":1729162362718,"data":null
|
||
if type == 'reduce':
|
||
amount = Precise.string_abs(amount)
|
||
return self.extend(self.parse_margin_modification(response, market), {
|
||
'amount': self.parse_number(amount),
|
||
'type': type,
|
||
})
|
||
|
||
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
|
||
#
|
||
#
|
||
msg = self.safe_string(data, 'msg')
|
||
status = 'ok' if (msg == 'success') else 'failed'
|
||
return {
|
||
'info': data,
|
||
'symbol': market['symbol'],
|
||
'type': None,
|
||
'marginMode': None,
|
||
'amount': None,
|
||
'total': None,
|
||
'code': market['quote'],
|
||
'status': status,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
}
|
||
|
||
async def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
||
"""
|
||
remove margin from a position
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#change-margin
|
||
|
||
: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
|
||
:param str [params.side]: *for isolated margin mode with hedged position mode only* 'long' or 'short'
|
||
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
|
||
"""
|
||
params['methodName'] = 'reduceMargin'
|
||
return await self.modify_margin_helper(symbol, -amount, 'reduce', params)
|
||
|
||
async def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
||
"""
|
||
add margin
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#change-margin
|
||
|
||
:param str symbol: unified market symbol
|
||
:param float amount: amount of margin to add
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.side]: *for isolated margin mode with hedged position mode only* 'long' or 'short'
|
||
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
|
||
"""
|
||
params['methodName'] = 'addMargin'
|
||
return await self.modify_margin_helper(symbol, amount, 'add', params)
|
||
|
||
async def fetch_position(self, symbol: str, params={}) -> Position:
|
||
"""
|
||
fetch data on a single open contract trade position
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-symbol-position
|
||
|
||
:param str symbol: unified market symbol of the market the position is held in, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
@param {str} [params.side] 'long' or 'short' *for non-hedged position mode only* (default 'long')
|
||
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
methodName = 'fetchPosition'
|
||
side = 'long'
|
||
side, params = self.handle_option_and_params(params, methodName, 'side')
|
||
positions = await self.fetch_positions_for_symbol(symbol, params)
|
||
arrayLength = len(positions)
|
||
if arrayLength > 1:
|
||
for i in range(0, len(positions)):
|
||
position = positions[i]
|
||
if position['side'] == side:
|
||
return position
|
||
return self.safe_dict(positions, 0, {})
|
||
|
||
async def fetch_positions_for_symbol(self, symbol: str, params={}) -> List[Position]:
|
||
"""
|
||
fetch open positions for a single market
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-symbol-position
|
||
|
||
fetch all open positions for specific symbol
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginCoin': market['settleId'],
|
||
}
|
||
response = await self.privateGetApiMixV1PositionSinglePositionV2(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726926959041,
|
||
# "data": [
|
||
# {
|
||
# "marginCoin": "USDT",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "holdSide": "long",
|
||
# "openDelegateCount": "0",
|
||
# "margin": "2.55512",
|
||
# "available": "0.01",
|
||
# "locked": "0",
|
||
# "total": "0.01",
|
||
# "leverage": 10,
|
||
# "achievedProfits": "0",
|
||
# "averageOpenPrice": "2555.12",
|
||
# "marginMode": "crossed",
|
||
# "holdMode": "double_hold",
|
||
# "unrealizedPL": "0.1371",
|
||
# "liquidationPrice": "-3433.328491",
|
||
# "keepMarginRate": "0.0033",
|
||
# "marketPrice": "2568.83",
|
||
# "marginRatio": "0.001666357648",
|
||
# "autoMargin": "off",
|
||
# "cTime": "1726919819686"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_positions(data, [symbol])
|
||
|
||
async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
||
"""
|
||
fetch all open positions
|
||
|
||
https://coincatch.github.io/github.io/en/mix/#get-all-position
|
||
|
||
:param str[] [symbols]: list of unified market symbols(all symbols must belong to the same product type)
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.productType]: 'umcbl' or 'dmcbl'(default 'umcbl' if symbols are not provided)
|
||
:param str [params.marginCoin]: the settle currency of the positions, needs to match the productType
|
||
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
methodName = 'fetchPositions'
|
||
await self.load_markets()
|
||
productType = 'umcbl'
|
||
if symbols is not None:
|
||
marketIds = self.market_ids(symbols)
|
||
productTypes = []
|
||
for i in range(0, len(marketIds)):
|
||
marketId = marketIds[i]
|
||
parts = marketId.split('_')
|
||
marketProductType = self.safe_string(parts, 1)
|
||
productTypes.append(marketProductType)
|
||
productTypes = self.unique(productTypes)
|
||
arrayLength = len(productTypes)
|
||
if arrayLength > 1:
|
||
raise BadSymbol(self.id + ' ' + methodName + '() requires all symbols to belong to the same product type(umcbl or dmcbl)')
|
||
else:
|
||
productType = productTypes[0]
|
||
else:
|
||
productType, params = self.handle_option_and_params(params, methodName, 'productType', productType)
|
||
request: dict = {
|
||
'productType': productType,
|
||
}
|
||
if productType == 'dmcbl':
|
||
marginCoin: Str = None
|
||
marginCoin, params = self.handle_option_and_params(params, methodName, 'marginCoin')
|
||
if marginCoin is not None:
|
||
currency = self.currency(marginCoin)
|
||
request['marginCoin'] = currency['id']
|
||
response = await self.privateGetApiMixV1PositionAllPositionV2(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1726933132054,
|
||
# "data": [
|
||
# {
|
||
# "marginCoin": "USDT",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "holdSide": "long",
|
||
# "openDelegateCount": "0",
|
||
# "margin": "2.55512",
|
||
# "available": "0.01",
|
||
# "locked": "0",
|
||
# "total": "0.01",
|
||
# "leverage": 10,
|
||
# "achievedProfits": "0",
|
||
# "averageOpenPrice": "2555.12",
|
||
# "marginMode": "crossed",
|
||
# "holdMode": "double_hold",
|
||
# "unrealizedPL": "0.0093",
|
||
# "liquidationPrice": "-3433.378333",
|
||
# "keepMarginRate": "0.0033",
|
||
# "marketPrice": "2556.05",
|
||
# "marginRatio": "0.001661599511",
|
||
# "autoMargin": "off",
|
||
# "cTime": "1726919819686",
|
||
# "uTime": "1726919819686"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_positions(data, symbols)
|
||
|
||
def parse_position(self, position: dict, market: Market = None):
|
||
#
|
||
# {
|
||
# "marginCoin": "USDT",
|
||
# "symbol": "ETHUSDT_UMCBL",
|
||
# "holdSide": "long",
|
||
# "openDelegateCount": "0",
|
||
# "margin": "2.55512",
|
||
# "available": "0.01",
|
||
# "locked": "0",
|
||
# "total": "0.01",
|
||
# "leverage": 10,
|
||
# "achievedProfits": "0",
|
||
# "averageOpenPrice": "2555.12",
|
||
# "marginMode": "crossed",
|
||
# "holdMode": "double_hold",
|
||
# "unrealizedPL": "0.0093",
|
||
# "liquidationPrice": "-3433.378333",
|
||
# "keepMarginRate": "0.0033",
|
||
# "marketPrice": "2556.05",
|
||
# "marginRatio": "0.001661599511",
|
||
# "autoMargin": "off",
|
||
# "cTime": "1726919819686",
|
||
# "uTime": "1726919819686"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(position, 'symbol')
|
||
settleId = self.safe_string(position, 'marginCoin')
|
||
market = self.safe_market_custom(marketId, market, settleId)
|
||
timestamp = self.safe_integer(position, 'cTime')
|
||
marginMode = self.safe_string(position, 'marginMode')
|
||
isHedged: Bool = None
|
||
holdMode = self.safe_string(position, 'holdMode')
|
||
if holdMode == 'double_hold':
|
||
isHedged = True
|
||
elif holdMode == 'single_hold':
|
||
isHedged = False
|
||
margin = self.safe_number(position, 'margin')
|
||
keepMarginRate = self.safe_string(position, 'keepMarginRate')
|
||
return self.safe_position({
|
||
'symbol': market['symbol'],
|
||
'id': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'contracts': self.safe_number(position, 'total'), # todo check
|
||
'contractSize': None,
|
||
'side': self.safe_string_lower(position, 'holdSide'),
|
||
'notional': margin, # todo check
|
||
'leverage': self.safe_integer(position, 'leverage'),
|
||
'unrealizedPnl': self.safe_number(position, 'unrealizedPL'),
|
||
'realizedPnl': self.safe_number(position, 'achievedProfits'),
|
||
'collateral': None, # todo check
|
||
'entryPrice': self.safe_number(position, 'averageOpenPrice'),
|
||
'markPrice': self.safe_number(position, 'marketPrice'),
|
||
'liquidationPrice': self.safe_number(position, 'liquidationPrice'),
|
||
'marginMode': self.parse_margin_mode_type(marginMode),
|
||
'hedged': isHedged,
|
||
'maintenanceMargin': None, # todo check
|
||
'maintenanceMarginPercentage': self.parse_number(Precise.string_mul(keepMarginRate, '100')), # todo check
|
||
'initialMargin': margin, # todo check
|
||
'initialMarginPercentage': None,
|
||
'marginRatio': self.safe_number(position, 'marginRatio'),
|
||
'lastUpdateTimestamp': self.safe_integer(position, 'uTime'),
|
||
'lastPrice': None,
|
||
'stopLossPrice': None,
|
||
'takeProfitPrice': None,
|
||
'percentage': None,
|
||
'info': position,
|
||
})
|
||
|
||
def safe_market_custom(self, marketId: Str, market: Market = None, settleId: Str = None) -> Market:
|
||
try:
|
||
market = self.safe_market(marketId, market)
|
||
except Exception as e:
|
||
# dmcbl markets have the same id and market type but different settleId
|
||
# so we need to resolve the market by settleId
|
||
marketsWithCurrentId = self.safe_list(self.markets_by_id, marketId, [])
|
||
if settleId is None:
|
||
market = marketsWithCurrentId[0] # if settleId is not provided, return the first market with the current id
|
||
else:
|
||
for i in range(0, len(marketsWithCurrentId)):
|
||
marketWithCurrentId = marketsWithCurrentId[i]
|
||
if marketWithCurrentId['settleId'] == settleId:
|
||
market = marketWithCurrentId
|
||
break
|
||
return market
|
||
|
||
async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch the history of changes, actions done by the user or operations that altered balance of the user
|
||
|
||
https://coincatch.github.io/github.io/en/spot/#get-bills
|
||
https://coincatch.github.io/github.io/en/mix/#get-business-account-bill
|
||
|
||
:param str [code]: unified currency code
|
||
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
||
:param int [limit]: max number of ledger entrys to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: *swap only* the latest time in ms to fetch entries for
|
||
:param str [params.type]: 'spot' or 'swap'(default 'spot')
|
||
:param str [params.after]: *spot only* billId, return the data less than self billId
|
||
:param str [params.before]: *spot only* billId, return the data greater than or equals to self billId
|
||
:param str [params.groupType]: *spot only*
|
||
:param str [params.bizType]: *spot only*
|
||
:param str [params.productType]: *swap only* 'umcbl' or 'dmcbl'(default 'umcbl' or 'dmcbl' if code is provided and code is not equal to 'USDT')
|
||
:param str [params.business]: *swap only*
|
||
:param str [params.lastEndId]: *swap only*
|
||
:param bool [params.next]: *swap only*
|
||
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
||
"""
|
||
methodName = 'fetchLedger'
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
marketType = 'spot'
|
||
marketType, params = self.handle_market_type_and_params(methodName, None, params, marketType)
|
||
result = None
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
if marketType == 'spot':
|
||
if currency is not None:
|
||
numericId = self.safe_string(currency, 'numericId')
|
||
request['coinId'] = numericId
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = await self.privatePostApiSpotV1AccountBills(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1727964749515,
|
||
# "data": [
|
||
# {
|
||
# "billId": "1220289012519190529",
|
||
# "coinId": 2,
|
||
# "coinName": "USDT",
|
||
# "groupType": "transfer",
|
||
# "bizType": "Transfer out",
|
||
# "quantity": "-40.00000000",
|
||
# "balance": "4.43878673",
|
||
# "fees": "0.00000000",
|
||
# "cTime": "1726665493092"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
result = self.safe_list(response, 'data', [])
|
||
elif marketType == 'swap':
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
else:
|
||
request['startTime'] = 0 # is mandatory
|
||
until: Int = None
|
||
until, params = self.handle_option_and_params(params, methodName, 'until')
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
else:
|
||
request['endTime'] = self.milliseconds() # is mandatory
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
productType = 'umcbl'
|
||
if code is None:
|
||
productType = self.handle_option(methodName, 'productType', productType)
|
||
elif code == 'USDT':
|
||
productType = 'umcbl'
|
||
else:
|
||
productType = 'dmcbl'
|
||
productType, params = self.handle_param_string(params, 'productType', productType)
|
||
request['productType'] = productType
|
||
response = await self.privateGetApiMixV1AccountAccountBusinessBill(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "00000",
|
||
# "msg": "success",
|
||
# "requestTime": 1727971607663,
|
||
# "data": {
|
||
# "result": [
|
||
# {
|
||
# "id": "1225766556446064640",
|
||
# "symbol": null,
|
||
# "marginCoin": "ETH",
|
||
# "amount": "-0.0016",
|
||
# "fee": "0",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "trans_to_exchange",
|
||
# "cTime": "1727971441425"
|
||
# },
|
||
# {
|
||
# "id": "1225467081664061441",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "amount": "-0.00052885",
|
||
# "fee": "0",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "risk_captital_user_transfer",
|
||
# "cTime": "1727900041024"
|
||
# },
|
||
# {
|
||
# "id": "1225467075440189441",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "amount": "-0.0083359",
|
||
# "fee": "-0.00005996",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "burst_long_loss_query",
|
||
# "cTime": "1727900039576"
|
||
# },
|
||
# {
|
||
# "id": "1221416895715303426",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "amount": "0.00004756",
|
||
# "fee": "0",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "contract_settle_fee",
|
||
# "cTime": "1726934401444"
|
||
# },
|
||
# {
|
||
# "id": "1221413703233871873",
|
||
# "symbol": "ETHUSD_DMCBL",
|
||
# "marginCoin": "ETH",
|
||
# "amount": "0",
|
||
# "fee": "-0.00005996",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "open_long",
|
||
# "cTime": "1726933640336"
|
||
# },
|
||
# {
|
||
# "id": "1220288640761122816",
|
||
# "symbol": null,
|
||
# "marginCoin": "ETH",
|
||
# "amount": "0.01",
|
||
# "fee": "0",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "trans_from_exchange",
|
||
# "cTime": "1726665404563"
|
||
# }
|
||
# ],
|
||
# "lastEndId": "1220288641021337600",
|
||
# "nextFlag": False,
|
||
# "preFlag": False
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'result', [])
|
||
else:
|
||
raise NotSupported(self.id + ' ' + methodName + '() does not support market type ' + marketType)
|
||
return self.parse_ledger(result, currency, since, limit)
|
||
|
||
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
||
#
|
||
# spot
|
||
# {
|
||
# "billId": "1220289012519190529",
|
||
# "coinId": 2,
|
||
# "coinName": "USDT",
|
||
# "groupType": "transfer",
|
||
# "bizType": "Transfer out",
|
||
# "quantity": "-40.00000000",
|
||
# "balance": "4.43878673",
|
||
# "fees": "0.00000000",
|
||
# "cTime": "1726665493092"
|
||
# }
|
||
#
|
||
# swap
|
||
# {
|
||
# "id": "1220288640761122816",
|
||
# "symbol": null,
|
||
# "marginCoin": "ETH",
|
||
# "amount": "0.01",
|
||
# "fee": "0",
|
||
# "feeByCoupon": "",
|
||
# "feeCoin": "ETH",
|
||
# "business": "trans_from_exchange",
|
||
# "cTime": "1726665404563"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(item, 'cTime')
|
||
settleId = self.safe_string_2(item, 'coinName', 'marginCoin')
|
||
market: Market = None
|
||
marketId = self.safe_string(item, 'symbol')
|
||
market = self.safe_market_custom(marketId, market, settleId)
|
||
amountString = self.safe_string_2(item, 'quantity', 'amount')
|
||
direction = 'in'
|
||
if Precise.string_lt(amountString, '0'):
|
||
direction = 'out'
|
||
amountString = Precise.string_mul(amountString, '-1')
|
||
fee = {
|
||
'cost': Precise.string_abs(self.safe_string_2(item, 'fee', 'fees')),
|
||
'currency': self.safe_string(item, 'feeCoin'),
|
||
}
|
||
return self.safe_ledger_entry({
|
||
'id': self.safe_string_2(item, 'billId', 'id'),
|
||
'info': item,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'account': None,
|
||
'direction': direction,
|
||
'referenceId': None,
|
||
'referenceAccount': None,
|
||
'type': self.parse_ledger_entry_type(self.safe_string_lower_2(item, 'bizType', 'business')),
|
||
'currency': self.safe_currency_code(settleId, currency),
|
||
'symbol': market['symbol'],
|
||
'amount': amountString,
|
||
'before': None,
|
||
'after': self.safe_string(item, 'balance'),
|
||
'status': 'ok',
|
||
'fee': fee,
|
||
}, currency)
|
||
|
||
def parse_ledger_entry_type(self, type: str) -> str:
|
||
types = {
|
||
'deposit': 'deposit',
|
||
'withdraw': 'withdrawal',
|
||
'buy': 'trade',
|
||
'sell': 'trade',
|
||
'deduction of handling fee': 'fee', # todo check
|
||
'transfer-in': 'transfer',
|
||
'transfer in': 'transfer',
|
||
'transfer out': 'transfer',
|
||
'rebate rewards': 'rebate', # todo check
|
||
'airdrop rewards': 'rebate', # todo check
|
||
'usdt contract rewards': 'rebate', # todo check
|
||
'mix contract rewards': 'rebate', # todo check
|
||
'system lock': 'system lock',
|
||
'user lock': 'user lock',
|
||
'open_long': 'trade',
|
||
'open_short': 'trade',
|
||
'close_long': 'trade',
|
||
'close_short': 'trade',
|
||
'trans_from_exchange': 'transfer',
|
||
'trans_to_exchange': 'transfer',
|
||
'contract_settle_fee': 'fee', # todo check sometimes it is positive, sometimes negative
|
||
'burst_long_loss_query': 'trade', # todo check
|
||
'burst_short_loss_query': 'trade', # todo check
|
||
}
|
||
return self.safe_string(types, type, type)
|
||
|
||
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
||
if not response:
|
||
return None # fallback to default error handler
|
||
message = self.safe_string(response, 'msg')
|
||
feedback = self.id + ' ' + body
|
||
messageCode = self.safe_string(response, 'code')
|
||
success = (message == 'success') or (message is None)
|
||
if url.find('batch') >= 0: # createOrders, cancelOrders
|
||
data = self.safe_dict(response, 'data', {})
|
||
failure = self.safe_list_2(data, 'failure', 'fail_infos', [])
|
||
if not self.is_empty(failure):
|
||
success = False
|
||
firstEntry = self.safe_dict(failure, 0, {})
|
||
messageCode = self.safe_string(firstEntry, 'errorCode')
|
||
message = self.safe_string(firstEntry, 'errorMsg')
|
||
if not success:
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], messageCode, feedback)
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
||
raise ExchangeError(feedback) # unknown message
|
||
return None
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
endpoint = '/' + path
|
||
if method == 'GET':
|
||
query = self.urlencode(params)
|
||
if len(query) != 0:
|
||
endpoint += '?' + query
|
||
if api == 'private':
|
||
self.check_required_credentials()
|
||
timestamp = self.number_to_string(self.milliseconds())
|
||
suffix = ''
|
||
if method != 'GET':
|
||
body = self.json(params)
|
||
suffix = body
|
||
payload = timestamp + method + endpoint + suffix
|
||
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
|
||
headers = {
|
||
'ACCESS-KEY': self.apiKey,
|
||
'ACCESS-SIGN': signature,
|
||
'ACCESS-TIMESTAMP': timestamp,
|
||
'ACCESS-PASSPHRASE': self.password,
|
||
'Content-Type': 'application/json',
|
||
'X-CHANNEL-API-CODE': self.safe_string(self.options, 'brokerId', '47cfy'),
|
||
}
|
||
url = self.urls['api'][api] + endpoint
|
||
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|