2861 lines
122 KiB
Python
2861 lines
122 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.base.exchange import Exchange
|
|
from ccxt.abstract.oxfun import ImplicitAPI
|
|
import hashlib
|
|
from ccxt.base.types import Account, Any, Balances, Bool, Currencies, Currency, DepositAddress, Int, LeverageTier, LeverageTiers, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, Transaction, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import AccountNotEnabled
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import MarketClosed
|
|
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 OperationFailed
|
|
from ccxt.base.errors import NetworkError
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import RequestTimeout
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
|
|
|
|
class oxfun(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(oxfun, self).describe(), {
|
|
'id': 'oxfun',
|
|
'name': 'OXFUN',
|
|
'countries': ['PA'], # Panama todo check
|
|
'version': 'v3',
|
|
'rateLimit': 120, # 100 requests per second and 25000 per 5 minutes
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createDepositAddress': False,
|
|
'createMarketBuyOrderWithCost': True,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createOrders': True,
|
|
'createPostOnlyOrder': True,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopLimitOrder': True,
|
|
'createStopMarketOrder': True,
|
|
'createStopOrder': True,
|
|
'deposit': False,
|
|
'editOrder': False,
|
|
'fetchAccounts': True,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': False,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrder': False,
|
|
'fetchClosedOrders': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDeposit': False,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDeposits': True,
|
|
'fetchDepositWithdrawFee': False,
|
|
'fetchDepositWithdrawFees': False,
|
|
'fetchFundingHistory': True,
|
|
'fetchFundingRate': True,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': True,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchL3OrderBook': False,
|
|
'fetchLedger': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': True,
|
|
'fetchMarketLeverageTiers': 'emulated',
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenOrder': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrderBooks': False,
|
|
'fetchOrders': False,
|
|
'fetchOrderTrades': False,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchStatus': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': False,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': False,
|
|
'fetchTradingLimits': False,
|
|
'fetchTransactionFee': False,
|
|
'fetchTransactionFees': False,
|
|
'fetchTransactions': False,
|
|
'fetchTransfers': True,
|
|
'fetchWithdrawal': False,
|
|
'fetchWithdrawals': True,
|
|
'fetchWithdrawalWhitelist': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'signIn': False,
|
|
'transfer': True,
|
|
'withdraw': True,
|
|
'ws': True,
|
|
},
|
|
'timeframes': {
|
|
'1m': '60s',
|
|
'5m': '300s',
|
|
'15m': '900s',
|
|
'30m': '1800s',
|
|
'1h': '3600s',
|
|
'2h': '7200s',
|
|
'4h': '14400s',
|
|
'1d': '86400s',
|
|
},
|
|
'urls': {
|
|
'logo': 'https://github.com/ccxt/ccxt/assets/43336371/6a196124-c1ee-4fae-8573-962071b61a85',
|
|
'referral': 'https://ox.fun/register?shareAccountId=5ZUD4a7G',
|
|
'api': {
|
|
'public': 'https://api.ox.fun',
|
|
'private': 'https://api.ox.fun',
|
|
},
|
|
'test': {
|
|
'public': 'https://stgapi.ox.fun',
|
|
'private': 'https://stgapi.ox.fun',
|
|
},
|
|
'www': 'https://ox.fun/',
|
|
'doc': 'https://docs.ox.fun/',
|
|
'fees': 'https://support.ox.fun/en/articles/8819866-trading-fees',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'v3/markets': 1,
|
|
'v3/assets': 1,
|
|
'v3/tickers': 1,
|
|
'v3/funding/estimates': 1,
|
|
'v3/candles': 1,
|
|
'v3/depth': 1,
|
|
'v3/markets/operational': 1,
|
|
'v3/exchange-trades': 1,
|
|
'v3/funding/rates': 1,
|
|
'v3/leverage/tiers': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'v3/account': 1,
|
|
'v3/account/names': 1,
|
|
'v3/wallet': 1, # retruns only FUNDING in OX
|
|
'v3/transfer': 1,
|
|
'v3/balances': 1,
|
|
'v3/positions': 1,
|
|
'v3/funding': 1,
|
|
'v3/deposit-addresses': 1,
|
|
'v3/deposit': 1,
|
|
'v3/withdrawal-addresses': 1,
|
|
'v3/withdrawal': 1,
|
|
'v3/withdrawal-fees': 1,
|
|
'v3/orders/status': 1,
|
|
'v3/orders/working': 1,
|
|
'v3/trades': 1,
|
|
},
|
|
'post': {
|
|
'v3/transfer': 1,
|
|
'v3/withdrawal': 1,
|
|
'v3/orders/place': 1,
|
|
},
|
|
'delete': {
|
|
'v3/orders/cancel': 1,
|
|
'v3/orders/cancel-all': 1,
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'tierBased': True,
|
|
'percentage': True,
|
|
'maker': self.parse_number('0.00020'),
|
|
'taker': self.parse_number('0.00070'),
|
|
'tiers': {
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.00020')],
|
|
[self.parse_number('2500000'), self.parse_number('0.00010')],
|
|
[self.parse_number('25000000'), self.parse_number('0')],
|
|
],
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.00070')],
|
|
[self.parse_number('2500000'), self.parse_number('0.00050')],
|
|
[self.parse_number('25000000'), self.parse_number('0.00040')],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
# exchange-specific options
|
|
'options': {
|
|
'sandboxMode': False,
|
|
'networks': {
|
|
'BTC': 'Bitcoin',
|
|
'ERC20': 'Ethereum',
|
|
'AVAX': 'Avalanche',
|
|
'SOL': 'Solana',
|
|
'ARB': 'Arbitrum',
|
|
'MATIC': 'Polygon',
|
|
'FTM': 'Fantom',
|
|
'BNB': 'BNBSmartChain',
|
|
'OPTIMISM': 'Optimism',
|
|
},
|
|
'networksById': {
|
|
'Bitcoin': 'BTC',
|
|
'Ethereum': 'ERC20',
|
|
'Avalanche': 'AVAX',
|
|
'Solana': 'SOL',
|
|
'Arbitrum': 'ARB',
|
|
'Polygon': 'MATIC',
|
|
'Fantom': 'FTM',
|
|
'Base': 'BASE',
|
|
'BNBSmartChain': 'BNB',
|
|
'Optimism': 'OPTIMISM',
|
|
},
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerDirection': False,
|
|
'triggerPriceType': None,
|
|
'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': {
|
|
'EXPIRE_MAKER': True,
|
|
'EXPIRE_TAKER': True,
|
|
'EXPIRE_BOTH': True,
|
|
'NONE': True,
|
|
},
|
|
'iceberg': True, # todo
|
|
},
|
|
'createOrders': {
|
|
'max': 10, # todo
|
|
},
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'daysBack': 100000, # todo
|
|
'untilDays': 7,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': None, # todo?
|
|
'fetchOHLCV': {
|
|
'limit': 500,
|
|
},
|
|
},
|
|
'spot': {
|
|
'extends': 'default',
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'exceptions': {
|
|
'exact': {
|
|
'-0010': OperationFailed, # {"event":null,"success":false,"message":"Validation failed","code":"0010","data":null} - failed transfer
|
|
'-429': RateLimitExceeded, # Rate limit reached
|
|
'-05001': AuthenticationError, # Your operation authority is invalid
|
|
'-10001': ExchangeError, # General networking failure
|
|
'-20000': BadRequest, # Signature is invalid
|
|
'-20001': BadRequest, # "success":false,"code":"20001","message":"marketCode is invalid"
|
|
'-20002': BadRequest, # Unexpected error, please check if your request data complies with the specification.
|
|
'-20003': NotSupported, # Unrecognized operation
|
|
'-20005': AuthenticationError, # Already logged in
|
|
'-20006': BadRequest, # Quantity must be greater than zero
|
|
'-20007': AuthenticationError, # You are accessing server too rapidly
|
|
'-20008': BadRequest, # clientOrderId must be greater than zero if provided
|
|
'-20009': BadRequest, # JSON data format is invalid
|
|
'-20010': ArgumentsRequired, # Either clientOrderId or orderId is required
|
|
'-20011': ArgumentsRequired, # marketCode is required
|
|
'-20012': ArgumentsRequired, # side is required
|
|
'-20013': ArgumentsRequired, # orderType is required
|
|
'-20014': BadRequest, # clientOrderId is not long type
|
|
'-20015': BadSymbol, # marketCode is invalid
|
|
'-20016': BadRequest, # side is invalid
|
|
'-20017': BadRequest, # orderType is invalid
|
|
'-20018': BadRequest, # timeInForce is invalid
|
|
'-20019': BadRequest, # orderId is invalid
|
|
'-20020': BadRequest, # stopPrice or limitPrice is invalid
|
|
'-20021': BadRequest, # price is invalid
|
|
'-20022': ArgumentsRequired, # price is required for LIMIT order
|
|
'-20023': ArgumentsRequired, # timestamp is required
|
|
'-20024': ExchangeError, # timestamp exceeds the threshold
|
|
'-20025': AuthenticationError, # API key is invalid
|
|
'-20026': BadRequest, # Token is invalid or expired
|
|
'-20027': BadRequest, # The length of the message exceeds the maximum length
|
|
'-20028': BadRequest, # price or stopPrice or limitPrice must be greater than zero
|
|
'-20029': BadRequest, # stopPrice must be less than limitPrice for Buy Stop Order
|
|
'-20030': BadRequest, # limitPrice must be less than stopPrice for Sell Stop Order
|
|
'-20031': MarketClosed, # The marketCode is closed for trading temporarily
|
|
'-20032': NetworkError, # Failed to submit due to timeout in server side
|
|
'-20033': BadRequest, # triggerType is invalid
|
|
'-20034': BadRequest, # The size of tag must be less than 32
|
|
'-20050': ExchangeError, # selfTradePreventionMode is invalid
|
|
'-30001': BadRequest, # {"success":false,"code":"30001","message":"Required parameter 'marketCode' is missing"}
|
|
'-35034': AuthenticationError, # {"success":false,"code":"35034","message":"Wallet API is not functioning properly, please try again or contact support."}
|
|
'-35046': AuthenticationError, # {"success":false,"code":"35046","message":"Error. Please refresh the page."}
|
|
'-40001': ExchangeError, # Alert from the server
|
|
'-50001': ExchangeError, # Unknown server error
|
|
'-300001': AccountNotEnabled, # Invalid account status xxx, please contact administration if any questions
|
|
'-300011': InvalidOrder, # Repo market orders are not allowed during the auction window
|
|
'-300012': InvalidOrder, # Repo bids above 0 and offers below 0 are not allowed during the auction window
|
|
'-100005': OrderNotFound, # Open order not found
|
|
'-100006': InvalidOrder, # Open order is not owned by the user
|
|
'-100008': BadRequest, # Quantity cannot be less than the quantity increment xxx
|
|
'-100015': NetworkError, # recvWindow xxx has expired
|
|
'-710001': ExchangeError, # System failure, exception thrown -> xxx
|
|
'-710002': BadRequest, # The price is lower than the minimum
|
|
'-710003': BadRequest, # The price is higher than the maximum
|
|
'-710004': BadRequest, # Position quantity exceeds the limit
|
|
'-710005': InsufficientFunds, # Insufficient margin
|
|
'-710006': InsufficientFunds, # Insufficient balance
|
|
'-710007': InsufficientFunds, # Insufficient position
|
|
'-000101': NetworkError, # Internal server is unavailable temporary, try again later
|
|
'-000201': NetworkError, # Trade service is busy, try again later
|
|
},
|
|
'broad': {
|
|
'-20001': OperationFailed, # Operation failed, please contact system administrator
|
|
'-200050': RequestTimeout, # The market is not active
|
|
},
|
|
},
|
|
})
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bitmex
|
|
|
|
https://docs.ox.fun/?json#get-v3-markets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
responseFromMarkets, responseFromTickers = [self.publicGetV3Markets(params), self.publicGetV3Tickers(params)]
|
|
marketsFromMarkets = self.safe_list(responseFromMarkets, 'data', [])
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# marketCode: 'OX-USD-SWAP-LIN',
|
|
# name: 'OX/USD Perp',
|
|
# referencePair: 'OX/USDT',
|
|
# base: 'OX',
|
|
# counter: 'USD',
|
|
# type: 'FUTURE',
|
|
# tickSize: '0.00001',
|
|
# minSize: '1',
|
|
# listedAt: '1704766320000',
|
|
# upperPriceBound: '0.02122',
|
|
# lowerPriceBound: '0.01142',
|
|
# markPrice: '0.01632',
|
|
# indexPrice: '0.01564',
|
|
# lastUpdatedAt: '1714762235569'
|
|
# },
|
|
# {
|
|
# marketCode: 'BTC-USD-SWAP-LIN',
|
|
# name: 'BTC/USD Perp',
|
|
# referencePair: 'BTC/USDT',
|
|
# base: 'BTC',
|
|
# counter: 'USD',
|
|
# type: 'FUTURE',
|
|
# tickSize: '1',
|
|
# minSize: '0.0001',
|
|
# listedAt: '1704686640000',
|
|
# upperPriceBound: '67983',
|
|
# lowerPriceBound: '55621',
|
|
# markPrice: '61802',
|
|
# indexPrice: '61813',
|
|
# lastUpdatedAt: '1714762234765'
|
|
# },
|
|
# {
|
|
# "marketCode": "MILK-OX",
|
|
# "name": "MILK/OX",
|
|
# "referencePair": "MILK/OX",
|
|
# "base": "MILK",
|
|
# "counter": "OX",
|
|
# "type": "SPOT",
|
|
# "tickSize": "0.0001",
|
|
# "minSize": "1",
|
|
# "listedAt": "1706608500000",
|
|
# "upperPriceBound": "1.0000",
|
|
# "lowerPriceBound": "-1.0000",
|
|
# "markPrice": "0.0269",
|
|
# "indexPrice": "0.0269",
|
|
# "lastUpdatedAt": "1714757402185"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
marketsFromTickers = self.safe_list(responseFromTickers, 'data', [])
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "marketCode": "DYM-USD-SWAP-LIN",
|
|
# "markPrice": "3.321",
|
|
# "open24h": "3.315",
|
|
# "high24h": "3.356",
|
|
# "low24h": "3.255",
|
|
# "volume24h": "0",
|
|
# "currencyVolume24h": "0",
|
|
# "openInterest": "1768.1",
|
|
# "lastTradedPrice": "3.543",
|
|
# "lastTradedQuantity": "1.0",
|
|
# "lastUpdatedAt": "1714853388102"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
markets = self.array_concat(marketsFromMarkets, marketsFromTickers)
|
|
return self.parse_markets(markets)
|
|
|
|
def parse_markets(self, markets) -> List[Market]:
|
|
marketIds = []
|
|
result = []
|
|
for i in range(0, len(markets)):
|
|
market = markets[i]
|
|
marketId = self.safe_string(market, 'marketCode')
|
|
if not (self.in_array(marketId, marketIds)):
|
|
marketIds.append(marketId)
|
|
result.append(self.parse_market(market))
|
|
return result
|
|
|
|
def parse_market(self, market) -> Market:
|
|
id = self.safe_string(market, 'marketCode', '')
|
|
parts = id.split('-')
|
|
baseId = self.safe_string(parts, 0)
|
|
quoteId = self.safe_string(parts, 1)
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
symbol = base + '/' + quote
|
|
type = self.safe_string_lower(market, 'type', 'spot') # markets from v3/tickers are spot and have no type
|
|
settleId: Str = None
|
|
settle: Str = None
|
|
isFuture = (type == 'future') # the exchange has only perpetual futures
|
|
if isFuture:
|
|
type = 'swap'
|
|
settleId = 'OX'
|
|
settle = self.safe_currency_code('OX')
|
|
symbol = symbol + ':' + settle
|
|
isSpot = type == 'spot'
|
|
return self.safe_market_structure({
|
|
'id': id,
|
|
'numericId': None,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': type,
|
|
'spot': isSpot,
|
|
'margin': False,
|
|
'swap': isFuture,
|
|
'future': False,
|
|
'option': False,
|
|
'active': True,
|
|
'contract': isFuture,
|
|
'linear': True if isFuture else None,
|
|
'inverse': False if isFuture else None,
|
|
'taker': self.fees['trading']['taker'],
|
|
'maker': self.fees['trading']['maker'],
|
|
'contractSize': 1 if isFuture else None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': None, # todo find it out
|
|
'price': self.safe_number(market, 'tickSize'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': self.safe_number(market, 'minSize'),
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': self.safe_integer(market, 'listedAt'),
|
|
'index': None,
|
|
'info': market,
|
|
})
|
|
|
|
def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://docs.ox.fun/?json#get-v3-assets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
response = self.publicGetV3Assets(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "asset": "OX",
|
|
# "isCollateral": True,
|
|
# "loanToValue": "1.000000000",
|
|
# "loanToValueFactor": "0.000000000",
|
|
# "networkList": [
|
|
# {
|
|
# "network": "BNBSmartChain",
|
|
# "tokenId": "0x78a0A62Fba6Fb21A83FE8a3433d44C73a4017A6f",
|
|
# "transactionPrecision": "18",
|
|
# "isWithdrawalFeeChargedToUser": True,
|
|
# "canDeposit": True,
|
|
# "canWithdraw": False,
|
|
# "minDeposit": "0.00010",
|
|
# "minWithdrawal": "0.00010"
|
|
# },
|
|
# {
|
|
# "network": "Polygon",
|
|
# "tokenId": "0x78a0A62Fba6Fb21A83FE8a3433d44C73a4017A6f",
|
|
# "transactionPrecision": "18",
|
|
# "isWithdrawalFeeChargedToUser": True,
|
|
# "canDeposit": True,
|
|
# "canWithdraw": False,
|
|
# "minDeposit": "0.00010",
|
|
# "minWithdrawal": "0.00010"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# {
|
|
# "asset": "BTC",
|
|
# "isCollateral": True,
|
|
# "loanToValue": "0.950000000",
|
|
# "loanToValueFactor": "0.000000000",
|
|
# "networkList": [
|
|
# {
|
|
# "network": "Bitcoin",
|
|
# "transactionPrecision": "8",
|
|
# "isWithdrawalFeeChargedToUser": True,
|
|
# "canDeposit": True,
|
|
# "canWithdraw": True,
|
|
# "minDeposit": "0.00010",
|
|
# "minWithdrawal": "0.00010"
|
|
# }
|
|
# ]
|
|
# },
|
|
# {
|
|
# "asset": "USDT.ARB",
|
|
# "isCollateral": True,
|
|
# "loanToValue": "0.950000000",
|
|
# "loanToValueFactor": "0.000000000",
|
|
# "networkList": [
|
|
# {
|
|
# "network": "Arbitrum",
|
|
# "tokenId": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
# "transactionPrecision": "18",
|
|
# "isWithdrawalFeeChargedToUser": True,
|
|
# "canDeposit": True,
|
|
# "canWithdraw": True,
|
|
# "minDeposit": "0.00010",
|
|
# "minWithdrawal": "0.00010"
|
|
# }
|
|
# ]
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
result: dict = {}
|
|
for i in range(0, len(data)):
|
|
currency = data[i]
|
|
fullId = self.safe_string(currency, 'asset', '')
|
|
parts = fullId.split('.')
|
|
id = parts[0]
|
|
code = self.safe_currency_code(id)
|
|
if not (code in result):
|
|
result[code] = {
|
|
'id': id,
|
|
'code': code,
|
|
'precision': None,
|
|
'type': None,
|
|
'name': None,
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'fee': None,
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'deposit': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'networks': {},
|
|
'info': [],
|
|
}
|
|
chains = self.safe_list(currency, 'networkList', [])
|
|
for j in range(0, len(chains)):
|
|
chain = chains[j]
|
|
networkId = self.safe_string(chain, 'network')
|
|
networkCode = self.network_id_to_code(networkId)
|
|
result[code]['networks'][networkCode] = {
|
|
'id': networkId,
|
|
'network': networkCode,
|
|
'margin': None,
|
|
'deposit': self.safe_bool(chain, 'canDeposit'),
|
|
'withdraw': self.safe_bool(chain, 'canWithdraw'),
|
|
'active': None,
|
|
'fee': None,
|
|
'precision': self.parse_number(self.parse_precision(self.safe_string(chain, 'transactionPrecision'))),
|
|
'limits': {
|
|
'deposit': {
|
|
'min': self.safe_number(chain, 'minDeposit'),
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': self.safe_number(chain, 'minWithdrawal'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'info': chain,
|
|
}
|
|
infos = self.safe_list(result[code], 'info', [])
|
|
infos.append(currency)
|
|
result[code]['info'] = infos
|
|
# only after all entries are formed in currencies, restructure each entry
|
|
allKeys = list(result.keys())
|
|
for i in range(0, len(allKeys)):
|
|
code = allKeys[i]
|
|
result[code] = self.safe_currency_structure(result[code]) # self is needed after adding network entry
|
|
return result
|
|
|
|
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://docs.ox.fun/?json#get-v3-tickers
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
response = self.publicGetV3Tickers(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "marketCode": "NII-USDT",
|
|
# "markPrice": "0",
|
|
# "open24h": "0",
|
|
# "high24h": "0",
|
|
# "low24h": "0",
|
|
# "volume24h": "0",
|
|
# "currencyVolume24h": "0",
|
|
# "openInterest": "0",
|
|
# "lastTradedPrice": "0",
|
|
# "lastTradedQuantity": "0",
|
|
# "lastUpdatedAt": "1714853388621"
|
|
# },
|
|
# {
|
|
# "marketCode": "GEC-USDT",
|
|
# "markPrice": "0",
|
|
# "open24h": "0",
|
|
# "high24h": "0",
|
|
# "low24h": "0",
|
|
# "volume24h": "0",
|
|
# "currencyVolume24h": "0",
|
|
# "openInterest": "0",
|
|
# "lastTradedPrice": "0",
|
|
# "lastTradedQuantity": "0",
|
|
# "lastUpdatedAt": "1714853388621"
|
|
# },
|
|
# {
|
|
# "marketCode": "DYM-USD-SWAP-LIN",
|
|
# "markPrice": "3.321",
|
|
# "open24h": "3.315",
|
|
# "high24h": "3.356",
|
|
# "low24h": "3.255",
|
|
# "volume24h": "0",
|
|
# "currencyVolume24h": "0",
|
|
# "openInterest": "1768.1",
|
|
# "lastTradedPrice": "3.543",
|
|
# "lastTradedQuantity": "1.0",
|
|
# "lastUpdatedAt": "1714853388102"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
tickers = self.safe_list(response, 'data', [])
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
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://docs.ox.fun/?json#get-v3-tickers
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
}
|
|
response = self.publicGetV3Tickers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "markPrice": "64276",
|
|
# "open24h": "63674",
|
|
# "high24h": "64607",
|
|
# "low24h": "62933",
|
|
# "volume24h": "306317655.80000",
|
|
# "currencyVolume24h": "48.06810",
|
|
# "openInterest": "72.39250",
|
|
# "lastTradedPrice": "64300.0",
|
|
# "lastTradedQuantity": "1.0",
|
|
# "lastUpdatedAt": "1714925196034"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
ticker = self.safe_dict(data, 0, {})
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
def parse_ticker(self, ticker, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "markPrice": "64276",
|
|
# "open24h": "63674",
|
|
# "high24h": "64607",
|
|
# "low24h": "62933",
|
|
# "volume24h": "306317655.80000",
|
|
# "currencyVolume24h": "48.06810",
|
|
# "openInterest": "72.39250",
|
|
# "lastTradedPrice": "64300.0",
|
|
# "lastTradedQuantity": "1.0",
|
|
# "lastUpdatedAt": "1714925196034"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(ticker, 'lastUpdatedAt')
|
|
marketId = self.safe_string(ticker, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
last = self.safe_string(ticker, 'lastTradedPrice')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(ticker, 'high24h'),
|
|
'low': self.safe_string(ticker, 'low24h'),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': self.safe_string(ticker, 'open24h'),
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'currencyVolume24h'),
|
|
'quoteVolume': None, # the exchange returns cost in OX
|
|
'markPrice': self.safe_string(ticker, 'markPrice'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.ox.fun/?json#get-v3-candles
|
|
|
|
: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(default 24 hours ago)
|
|
:param int [limit]: the maximum amount of candles to fetch(default 200, max 500)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch(default now)
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
timeframe = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
'timeframe': timeframe,
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
elif since is not None:
|
|
request['endTime'] = self.sum(since, 7 * 24 * 60 * 60 * 1000) # for the exchange not to raise an exception if since is younger than 7 days
|
|
response = self.publicGetV3Candles(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "timeframe": "3600s",
|
|
# "data": [
|
|
# {
|
|
# "open": "0.03240000",
|
|
# "high": "0.03240000",
|
|
# "low": "0.03240000",
|
|
# "close": "0.03240000",
|
|
# "volume": "0",
|
|
# "currencyVolume": "0",
|
|
# "openedAt": "1714906800000"
|
|
# },
|
|
# {
|
|
# "open": "0.03240000",
|
|
# "high": "0.03240000",
|
|
# "low": "0.03240000",
|
|
# "close": "0.03240000",
|
|
# "volume": "0",
|
|
# "currencyVolume": "0",
|
|
# "openedAt": "1714903200000"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(response, 'data', [])
|
|
return self.parse_ohlcvs(result, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "open": "0.03240000",
|
|
# "high": "0.03240000",
|
|
# "low": "0.03240000",
|
|
# "close": "0.03240000",
|
|
# "volume": "0",
|
|
# "currencyVolume": "0",
|
|
# "openedAt": "1714906800000"
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 'openedAt'),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'currencyVolume'),
|
|
]
|
|
|
|
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://docs.ox.fun/?json#get-v3-depth
|
|
|
|
: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(default 5, max 100)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['level'] = limit
|
|
response = self.publicGetV3Depth(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "level": "5",
|
|
# "data": {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "lastUpdatedAt": "1714933499266",
|
|
# "asks": [
|
|
# [64073.0, 8.4622],
|
|
# [64092.0, 8.1912],
|
|
# [64111.0, 8.0669],
|
|
# [64130.0, 11.7195],
|
|
# [64151.0, 10.1798]
|
|
# ],
|
|
# "bids": [
|
|
# [64022.0, 10.1292],
|
|
# [64003.0, 8.1619],
|
|
# [64000.0, 1.0],
|
|
# [63984.0, 12.7724],
|
|
# [63963.0, 11.0073]
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
timestamp = self.safe_integer(data, 'lastUpdatedAt')
|
|
return self.parse_order_book(data, market['symbol'], timestamp)
|
|
|
|
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
|
"""
|
|
fetch the current funding rates for multiple markets
|
|
|
|
https://docs.ox.fun/?json#get-v3-funding-estimates
|
|
|
|
:param str[] symbols: unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: an array of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
response = self.publicGetV3FundingEstimates(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "marketCode": "OX-USD-SWAP-LIN",
|
|
# "fundingAt": "1715515200000",
|
|
# "estFundingRate": "0.000200000"
|
|
# },
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "fundingAt": "1715515200000",
|
|
# "estFundingRate": "0.000003"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_funding_rates(data, symbols)
|
|
|
|
def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
|
"""
|
|
fetch the current funding rates for a symbol
|
|
|
|
https://docs.ox.fun/?json#get-v3-funding-estimates
|
|
|
|
:param str symbol: unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: an array of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'marketCode': self.market_id(symbol),
|
|
}
|
|
response = self.publicGetV3FundingEstimates(self.extend(request, params))
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
first = self.safe_dict(data, 0, {})
|
|
return self.parse_funding_rate(first, self.market(symbol))
|
|
|
|
def parse_funding_rate(self, fundingRate, market: Market = None) -> FundingRate:
|
|
#
|
|
# {
|
|
# "marketCode": "OX-USD-SWAP-LIN",
|
|
# "fundingAt": "1715515200000",
|
|
# "estFundingRate": "0.000200000"
|
|
# }
|
|
#
|
|
symbol = self.safe_string(fundingRate, 'marketCode')
|
|
market = self.market(symbol)
|
|
estFundingRateTimestamp = self.safe_integer(fundingRate, 'fundingAt')
|
|
return {
|
|
'info': fundingRate,
|
|
'symbol': market['symbol'],
|
|
'markPrice': None,
|
|
'indexPrice': None,
|
|
'interestRate': self.parse_number('0'),
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': estFundingRateTimestamp,
|
|
'datetime': self.iso8601(estFundingRateTimestamp),
|
|
'fundingRate': self.safe_number(fundingRate, 'estFundingRate'),
|
|
'fundingTimestamp': None,
|
|
'fundingDatetime': None,
|
|
'nextFundingRate': None,
|
|
'nextFundingTimestamp': None,
|
|
'nextFundingDatetime': None,
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'interval': None,
|
|
}
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
Fetches the history of funding rates
|
|
|
|
https://docs.ox.fun/?json#get-v3-funding-rates
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch(default 24 hours ago)
|
|
:param int [limit]: the maximum amount of trades to fetch(default 200, max 500)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest trade to fetch(default now)
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
response = self.publicGetV3FundingRates(self.extend(request, params))
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# marketCode: 'NEAR-USD-SWAP-LIN',
|
|
# fundingRate: '-0.000010000',
|
|
# createdAt: '1715428870755'
|
|
# },
|
|
# {
|
|
# marketCode: 'ENA-USD-SWAP-LIN',
|
|
# fundingRate: '0.000150000',
|
|
# createdAt: '1715428868616'
|
|
# },
|
|
# ...
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_funding_rate_histories(data, market, since, limit)
|
|
|
|
def parse_funding_rate_history(self, info, market: Market = None):
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# marketCode: 'NEAR-USD-SWAP-LIN',
|
|
# fundingRate: '-0.000010000',
|
|
# createdAt: '1715428870755'
|
|
# },
|
|
# {
|
|
# marketCode: 'ENA-USD-SWAP-LIN',
|
|
# fundingRate: '0.000150000',
|
|
# createdAt: '1715428868616'
|
|
# },
|
|
# ...
|
|
# }
|
|
#
|
|
marketId = self.safe_string(info, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
timestamp = self.safe_integer(info, 'createdAt')
|
|
return {
|
|
'info': info,
|
|
'symbol': symbol,
|
|
'fundingRate': self.safe_number(info, 'fundingRate'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
|
|
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches the history of funding payments
|
|
|
|
https://docs.ox.fun/?json#get-v3-funding
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch(default 24 hours ago)
|
|
:param int [limit]: the maximum amount of trades to fetch(default 200, max 500)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest trade to fetch(default now)
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
response = self.privateGetV3Funding(self.extend(request, params))
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# id: '966709913041305605',
|
|
# marketCode: 'ETH-USD-SWAP-LIN',
|
|
# payment: '-0.00430822',
|
|
# fundingRate: '0.000014',
|
|
# position: '0.001',
|
|
# indexPrice: '3077.3',
|
|
# createdAt: '1715086852890'
|
|
# },
|
|
# {
|
|
# id: '966698111997509637',
|
|
# marketCode: 'ETH-USD-SWAP-LIN',
|
|
# payment: '-0.0067419',
|
|
# fundingRate: '0.000022',
|
|
# position: '0.001',
|
|
# indexPrice: '3064.5',
|
|
# createdAt: '1715083251516'
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(response, 'data', [])
|
|
return self.parse_incomes(result, market, since, limit)
|
|
|
|
def parse_income(self, income, market: Market = None):
|
|
#
|
|
# {
|
|
# id: '966709913041305605',
|
|
# marketCode: 'ETH-USD-SWAP-LIN',
|
|
# payment: '-0.00430822',
|
|
# fundingRate: '0.000014',
|
|
# position: '0.001',
|
|
# indexPrice: '3077.3',
|
|
# createdAt: '1715086852890'
|
|
# },
|
|
#
|
|
marketId = self.safe_string(income, 'marketCode')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
amount = self.safe_number(income, 'payment')
|
|
code = self.safe_currency_code('OX')
|
|
id = self.safe_string(income, 'id')
|
|
timestamp = self.safe_timestamp(income, 'createdAt')
|
|
rate = self.safe_number(income, 'fundingRate')
|
|
return {
|
|
'info': income,
|
|
'symbol': symbol,
|
|
'code': code,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'id': id,
|
|
'amount': amount,
|
|
'rate': rate,
|
|
}
|
|
|
|
def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
|
|
"""
|
|
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes, if a market has a leverage tier of 0, then the leverage tiers cannot be obtained for self market
|
|
|
|
https://docs.ox.fun/?json#get-v3-leverage-tiers
|
|
|
|
:param str[] [symbols]: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
response = self.publicGetV3LeverageTiers(params)
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# marketCode: 'SOL-USD-SWAP-LIN',
|
|
# tiers: [
|
|
# {
|
|
# tier: '1',
|
|
# leverage: '10',
|
|
# positionFloor: '0',
|
|
# positionCap: '200000000',
|
|
# initialMargin: '0.1',
|
|
# maintenanceMargin: '0.05',
|
|
# maintenanceAmount: '0'
|
|
# },
|
|
# {
|
|
# tier: '2',
|
|
# leverage: '5',
|
|
# positionFloor: '200000000',
|
|
# positionCap: '280000000',
|
|
# initialMargin: '0.2',
|
|
# maintenanceMargin: '0.1',
|
|
# maintenanceAmount: '7000000'
|
|
# },
|
|
# {
|
|
# tier: '3',
|
|
# leverage: '4',
|
|
# positionFloor: '280000000',
|
|
# positionCap: '460000000',
|
|
# initialMargin: '0.25',
|
|
# maintenanceMargin: '0.125',
|
|
# maintenanceAmount: '14000000'
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_leverage_tiers(data, symbols, 'marketCode')
|
|
|
|
def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
|
|
#
|
|
# {
|
|
# marketCode: 'SOL-USD-SWAP-LIN',
|
|
# tiers: [
|
|
# {
|
|
# tier: '1',
|
|
# leverage: '10',
|
|
# positionFloor: '0',
|
|
# positionCap: '200000000',
|
|
# initialMargin: '0.1',
|
|
# maintenanceMargin: '0.05',
|
|
# maintenanceAmount: '0'
|
|
# ...
|
|
# ]
|
|
# },
|
|
#
|
|
marketId = self.safe_string(info, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
listOfTiers = self.safe_list(info, 'tiers', [])
|
|
tiers = []
|
|
for j in range(0, len(listOfTiers)):
|
|
tier = listOfTiers[j]
|
|
tiers.append({
|
|
'tier': self.safe_number(tier, 'tier'),
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'currency': market['settle'],
|
|
'minNotional': self.safe_number(tier, 'positionFloor'),
|
|
'maxNotional': self.safe_number(tier, 'positionCap'),
|
|
'maintenanceMarginRate': self.safe_number(tier, 'maintenanceMargin'),
|
|
'maxLeverage': self.safe_number(tier, 'leverage'),
|
|
'info': tier,
|
|
})
|
|
return tiers
|
|
|
|
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://docs.ox.fun/?json#get-v3-exchange-trades
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch(default 24 hours ago)
|
|
:param int [limit]: the maximum amount of trades to fetch(default 200, max 500)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest trade to fetch(default now)
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
elif since is not None:
|
|
request['endTime'] = self.sum(since, 7 * 24 * 60 * 60 * 1000) # for the exchange not to raise an exception if since is younger than 7 days
|
|
response = self.publicGetV3ExchangeTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "matchPrice": "63900",
|
|
# "matchQuantity": "1",
|
|
# "side": "SELL",
|
|
# "matchType": "TAKER",
|
|
# "matchedAt": "1714934112352"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_trades(data, market, since, limit)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://docs.ox.fun/?json#get-v3-trades
|
|
|
|
:param str symbol: 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(default 200, max 500)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest trade to fetch(default now)
|
|
:returns Trade[]: a list of `trade structures <https://github.com/ccxt/ccxt/wiki/Manual#trade-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['marketCode'] = market['id']
|
|
if since is not None: # startTime and endTime must be within 7 days of each other
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
elif since is not None:
|
|
request['endTime'] = self.sum(since, 7 * 24 * 60 * 60 * 1000) # for the exchange not to raise an exception if since is younger than 7 days
|
|
response = self.privateGetV3Trades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "orderId": "1000104903698",
|
|
# "clientOrderId": "1715000260094",
|
|
# "matchId": "400017129522773178",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "side": "BUY",
|
|
# "matchedQuantity": "0.001",
|
|
# "matchPrice": "3100.2",
|
|
# "total": "310.02",
|
|
# "orderMatchType": "MAKER",
|
|
# "feeAsset": "OX",
|
|
# "fee": "0.062004",
|
|
# "source": "0",
|
|
# "matchedAt": "1715000267420"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result = self.safe_list(response, 'data', [])
|
|
return self.parse_trades(result, market, since, limit)
|
|
|
|
def parse_trade(self, trade, market: Market = None) -> Trade:
|
|
#
|
|
# public fetchTrades
|
|
#
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "matchPrice": "63900",
|
|
# "matchQuantity": "1",
|
|
# "side": "SELL",
|
|
# "matchType": "TAKER",
|
|
# "matchedAt": "1714934112352"
|
|
# }
|
|
#
|
|
#
|
|
# private fetchMyTrades
|
|
#
|
|
# {
|
|
# "orderId": "1000104903698",
|
|
# "clientOrderId": "1715000260094",
|
|
# "matchId": "400017129522773178",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "side": "BUY",
|
|
# "matchedQuantity": "0.001",
|
|
# "matchPrice": "3100.2",
|
|
# "total": "310.02",
|
|
# "orderMatchType": "MAKER",
|
|
# "feeAsset": "OX",
|
|
# "fee": "0.062004",
|
|
# "source": "0",
|
|
# "matchedAt": "1715000267420"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(trade, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
timestamp = self.safe_integer(trade, 'matchedAt')
|
|
fee = {
|
|
'cost': self.safe_string(trade, 'fee'),
|
|
'currency': self.safe_currency_code(self.safe_string(trade, 'feeAsset')),
|
|
}
|
|
return self.safe_trade({
|
|
'id': self.safe_string(trade, 'matchId'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'type': None,
|
|
'order': self.safe_string(trade, 'orderId'),
|
|
'side': self.safe_string_lower(trade, 'side'),
|
|
'takerOrMaker': self.safe_string_lower_2(trade, 'matchType', 'orderMatchType'),
|
|
'price': self.safe_string(trade, 'matchPrice'),
|
|
'amount': self.safe_string_2(trade, 'matchQuantity', 'matchedQuantity'),
|
|
'cost': None, # the exchange returns total cost in OX
|
|
'fee': fee,
|
|
'info': trade,
|
|
}, market)
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://docs.ox.fun/?json#get-v3-balances
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.asset]: currency id, if empty the exchange returns info about all currencies
|
|
:param str [params.subAcc]: Name of sub account. If no subAcc is given, then the response contains only the account linked to the API-Key.
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.privateGetV3Balances(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "accountId": "106490",
|
|
# "name": "main",
|
|
# "balances": [
|
|
# {
|
|
# "asset": "OX",
|
|
# "total": "-7.55145065000",
|
|
# "available": "-71.16445065000",
|
|
# "reserved": "0",
|
|
# "lastUpdatedAt": "1715000448946"
|
|
# },
|
|
# {
|
|
# "asset": "ETH",
|
|
# "total": "0.01",
|
|
# "available": "0.01",
|
|
# "reserved": "0",
|
|
# "lastUpdatedAt": "1714914512750"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
balance = data[0]
|
|
subAcc = self.safe_string(params, 'subAcc')
|
|
if subAcc is not None:
|
|
for i in range(0, len(data)):
|
|
b = data[i]
|
|
name = self.safe_string(b, 'name')
|
|
if name == subAcc:
|
|
balance = b
|
|
break
|
|
return self.parse_balance(balance)
|
|
|
|
def parse_balance(self, balance) -> Balances:
|
|
#
|
|
# {
|
|
# "accountId": "106490",
|
|
# "name": "main",
|
|
# "balances": [
|
|
# {
|
|
# "asset": "OX",
|
|
# "total": "-7.55145065000",
|
|
# "available": "-71.16445065000",
|
|
# "reserved": "0",
|
|
# "lastUpdatedAt": "1715000448946"
|
|
# },
|
|
# {
|
|
# "asset": "ETH",
|
|
# "total": "0.01",
|
|
# "available": "0.01",
|
|
# "reserved": "0",
|
|
# "lastUpdatedAt": "1714914512750"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
result: dict = {
|
|
'info': balance,
|
|
}
|
|
balances = self.safe_list(balance, 'balances', [])
|
|
for i in range(0, len(balances)):
|
|
balanceEntry = balances[i]
|
|
currencyId = self.safe_string(balanceEntry, 'asset')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['total'] = self.safe_string(balanceEntry, 'total')
|
|
account['free'] = self.safe_string(balanceEntry, 'available')
|
|
account['used'] = self.safe_string(balanceEntry, 'reserved')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def fetch_accounts(self, params={}) -> List[Account]:
|
|
"""
|
|
fetch subaccounts associated with a profile
|
|
|
|
https://docs.ox.fun/?json#get-v3-account-names
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `account structures <https://docs.ccxt.com/#/?id=account-structure>` indexed by the account type
|
|
"""
|
|
self.load_markets()
|
|
# self endpoint can only be called using API keys paired with the parent account! Returns all active subaccounts.
|
|
response = self.privateGetV3AccountNames(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "accountId": "106526",
|
|
# "name": "testSubAccount"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_accounts(data, params)
|
|
|
|
def parse_account(self, account):
|
|
#
|
|
# {
|
|
# "accountId": "106526",
|
|
# "name": "testSubAccount"
|
|
# },
|
|
#
|
|
return {
|
|
'id': self.safe_string(account, 'accountId'),
|
|
'type': None,
|
|
'code': None,
|
|
'info': account,
|
|
}
|
|
|
|
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
|
|
https://docs.ox.fun/?json#post-v3-transfer
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account id to transfer from
|
|
:param str toAccount: account id to transfer to
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
# transferring funds between sub-accounts is restricted to API keys linked to the parent account.
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'quantity': self.currency_to_precision(code, amount),
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
}
|
|
response = self.privatePostV3Transfer(self.extend(request, params))
|
|
#
|
|
# {
|
|
# timestamp: 1715430036267,
|
|
# datetime: '2024-05-11T12:20:36.267Z',
|
|
# currency: 'OX',
|
|
# amount: 10,
|
|
# fromAccount: '106464',
|
|
# toAccount: '106570',
|
|
# info: {
|
|
# asset: 'OX',
|
|
# quantity: '10',
|
|
# fromAccount: '106464',
|
|
# toAccount: '106570',
|
|
# transferredAt: '1715430036267'
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_transfer(data, currency)
|
|
|
|
def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch a history of internal transfers made on an account
|
|
|
|
https://docs.ox.fun/?json#get-v3-transfer
|
|
|
|
: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)
|
|
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
# API keys linked to the parent account can get all account transfers, while API keys linked to a sub-account can only see transfers where the sub-account is either the "fromAccount" or "toAccount"
|
|
self.load_markets()
|
|
request: dict = {}
|
|
currency: Currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['asset'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
elif since is not None:
|
|
request['endTime'] = self.sum(since, 7 * 24 * 60 * 60 * 1000) # for the exchange not to raise an exception if since is younger than 7 days
|
|
response = self.privateGetV3Transfer(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "asset": "USDT",
|
|
# "quantity": "5",
|
|
# "fromAccount": "106490",
|
|
# "toAccount": "106526",
|
|
# "id": "966706320886267905",
|
|
# "status": "COMPLETED",
|
|
# "transferredAt": "1715085756708"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_transfers(data, currency, since, limit)
|
|
|
|
def parse_transfer(self, transfer, currency: Currency = None):
|
|
#
|
|
# fetchTransfers
|
|
#
|
|
# {
|
|
# "asset": "USDT",
|
|
# "quantity": "5",
|
|
# "fromAccount": "106490",
|
|
# "toAccount": "106526",
|
|
# "id": "966706320886267905",
|
|
# "status": "COMPLETED",
|
|
# "transferredAt": "1715085756708"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(transfer, 'transferredAt')
|
|
currencyId = self.safe_string(transfer, 'asset')
|
|
return {
|
|
'id': self.safe_string(transfer, 'id'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'currency': self.safe_currency_code(currencyId, currency),
|
|
'amount': self.safe_number(transfer, 'quantity'),
|
|
'fromAccount': self.safe_string(transfer, 'fromAccount'),
|
|
'toAccount': self.safe_string(transfer, 'toAccount'),
|
|
'status': self.parse_transfer_status(self.safe_string(transfer, 'status')),
|
|
'info': transfer,
|
|
}
|
|
|
|
def parse_transfer_status(self, status):
|
|
statuses: dict = {
|
|
'COMPLETED': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://docs.ox.fun/?json#get-v3-deposit-addresses
|
|
|
|
: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>`
|
|
"""
|
|
networkCode = self.safe_string(params, 'network')
|
|
networkId = self.network_code_to_id(networkCode, code)
|
|
if networkId is None:
|
|
raise BadRequest(self.id + ' fetchDepositAddress() require network parameter')
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'network': networkId,
|
|
}
|
|
params = self.omit(params, 'network')
|
|
response = self.privateGetV3DepositAddresses(self.extend(request, params))
|
|
#
|
|
# {"success":true,"data":{"address":"0x998dEc76151FB723963Bd8AFD517687b38D33dE8"}}
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_deposit_address(data, currency)
|
|
|
|
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
|
#
|
|
# {"address":"0x998dEc76151FB723963Bd8AFD517687b38D33dE8"}
|
|
#
|
|
address = self.safe_string(depositAddress, 'address')
|
|
self.check_address(address)
|
|
return {
|
|
'info': depositAddress,
|
|
'currency': currency['code'],
|
|
'network': None,
|
|
'address': address,
|
|
'tag': None,
|
|
}
|
|
|
|
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://docs.ox.fun/?json#get-v3-deposit
|
|
|
|
: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)
|
|
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
currency: Currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['asset'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
response = self.privateGetV3Deposit(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "asset":"USDC",
|
|
# "network":"Ethereum",
|
|
# "address": "0x998dEc76151FB723963Bd8AFD517687b38D33dE8",
|
|
# "quantity":"50",
|
|
# "id":"5914",
|
|
# "status": "COMPLETED",
|
|
# "txId":"0xf5e79663830a0c6f94d46638dcfbc134566c12facf1832396f81ecb55d3c75dc",
|
|
# "creditedAt":"1714821645154"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
for i in range(0, len(data)):
|
|
data[i]['type'] = 'deposit'
|
|
return self.parse_transactions(data, currency, since, limit)
|
|
|
|
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://docs.ox.fun/?json#get-v3-withdrawal
|
|
|
|
: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)
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
currency: Currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['asset'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since # startTime and endTime must be within 7 days of each other
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
request['endTime'] = until
|
|
params = self.omit(params, 'until')
|
|
response = self.privateGetV3Withdrawal(self.extend(request, params))
|
|
#
|
|
# {
|
|
# success: True,
|
|
# data: [
|
|
# {
|
|
# id: '968163212989431811',
|
|
# asset: 'OX',
|
|
# network: 'Arbitrum',
|
|
# address: '0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9',
|
|
# quantity: '11.7444',
|
|
# fee: '1.744400000',
|
|
# status: 'COMPLETED',
|
|
# txId: '0xe96b2d128b737fdbca927edf355cff42202e65b0fb960e64ffb9bd68c121f69f',
|
|
# requestedAt: '1715530365450',
|
|
# completedAt: '1715530527000'
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
for i in range(0, len(data)):
|
|
data[i]['type'] = 'withdrawal'
|
|
return self.parse_transactions(data, currency, since, limit)
|
|
|
|
def parse_transactions(self, transactions, currency: Currency = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
result = []
|
|
for i in range(0, len(transactions)):
|
|
transactions[i] = self.extend(transactions[i], params)
|
|
transaction = self.parse_transaction(transactions[i], currency)
|
|
result.append(transaction)
|
|
result = self.sort_by(result, 'timestamp')
|
|
code = currency['code'] if (currency is not None) else None
|
|
return self.filter_by_currency_since_limit(result, code, since, limit)
|
|
|
|
def parse_transaction(self, transaction, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits
|
|
# {
|
|
# "asset":"USDC",
|
|
# "network":"Ethereum",
|
|
# "address": "0x998dEc76151FB723963Bd8AFD517687b38D33dE8",
|
|
# "quantity":"50",
|
|
# "id":"5914",
|
|
# "status": "COMPLETED",
|
|
# "txId":"0xf5e79663830a0c6f94d46638dcfbc134566c12facf1832396f81ecb55d3c75dc",
|
|
# "creditedAt":"1714821645154"
|
|
# }
|
|
#
|
|
# fetchWithdrawals
|
|
# {
|
|
# id: '968163212989431811',
|
|
# asset: 'OX',
|
|
# network: 'Arbitrum',
|
|
# address: '0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9',
|
|
# quantity: '11.7444',
|
|
# fee: '1.744400000',
|
|
# status: 'COMPLETED',
|
|
# txId: '0xe96b2d128b737fdbca927edf355cff42202e65b0fb960e64ffb9bd68c121f69f',
|
|
# requestedAt: '1715530365450',
|
|
# completedAt: '1715530527000'
|
|
# }
|
|
#
|
|
# withdraw
|
|
# {
|
|
# "id": "968364664449302529",
|
|
# "asset": "OX",
|
|
# "network": "Arbitrum",
|
|
# "address": "0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9",
|
|
# "quantity": "10",
|
|
# "externalFee": False,
|
|
# "fee": "1.6728",
|
|
# "status": "PENDING",
|
|
# "requestedAt": "1715591843616"
|
|
# }
|
|
#
|
|
id = self.safe_string(transaction, 'id')
|
|
type = self.safe_string(transaction, 'type')
|
|
transaction = self.omit(transaction, 'type')
|
|
address: Str = None
|
|
addressTo: Str = None
|
|
status: Str = None
|
|
if type == 'deposit':
|
|
address = self.safe_string(transaction, 'address')
|
|
status = self.parse_deposit_status(self.safe_string(transaction, 'status'))
|
|
elif type == 'withdrawal':
|
|
addressTo = self.safe_string(transaction, 'address')
|
|
status = self.parse_withdrawal_status(self.safe_string(transaction, 'status'))
|
|
txid = self.safe_string(transaction, 'txId')
|
|
currencyId = self.safe_string(transaction, 'asset')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
network = self.safe_string(transaction, 'network')
|
|
networkCode = self.network_id_to_code(network)
|
|
timestamp = self.safe_integer_2(transaction, 'creditedAt', 'requestedAt')
|
|
amount = self.safe_number(transaction, 'quantity')
|
|
feeCost = self.safe_number(transaction, 'fee')
|
|
fee = None
|
|
if feeCost is not None:
|
|
fee = {
|
|
'cost': feeCost,
|
|
'currency': code,
|
|
}
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': txid,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': networkCode,
|
|
'address': address,
|
|
'addressTo': addressTo,
|
|
'addressFrom': None,
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'tagFrom': None,
|
|
'type': type,
|
|
'amount': amount,
|
|
'currency': code,
|
|
'status': status,
|
|
'updated': None,
|
|
'internal': None,
|
|
'comment': None,
|
|
'fee': fee,
|
|
}
|
|
|
|
def parse_deposit_status(self, status):
|
|
statuses: dict = {
|
|
'COMPLETED': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_withdrawal_status(self, status):
|
|
statuses: dict = {
|
|
'COMPLETED': 'ok',
|
|
'PROCESSING': 'pending',
|
|
'IN SWEEPING': 'pending',
|
|
'PENDING': 'pending',
|
|
'ON HOLD': 'pending',
|
|
'CANCELED': 'canceled',
|
|
'FAILED': 'failed',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://docs.ox.fun/?json#post-v3-withdrawal
|
|
|
|
: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
|
|
:param bool [params.externalFee]: if False, then the fee is taken from the quantity, also with the burn fee for asset SOLO
|
|
|
|
EXCHANGE SPECIFIC PARAMETERS
|
|
:param str [params.tfaType]: GOOGLE, or AUTHY_SECRET, or YUBIKEY, for 2FA
|
|
:param str [params.code]: 2FA code
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
stringAmount = self.currency_to_precision(code, amount)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'address': address,
|
|
'quantity': stringAmount,
|
|
}
|
|
if tag is not None:
|
|
request['memo'] = tag
|
|
networkCode: Str = None
|
|
networkCode, params = self.handle_network_code_and_params(params)
|
|
if networkCode is not None:
|
|
request['network'] = self.network_code_to_id(networkCode)
|
|
request['externalFee'] = False
|
|
response = self.privatePostV3Withdrawal(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": {
|
|
# "id": "968364664449302529",
|
|
# "asset": "OX",
|
|
# "network": "Arbitrum",
|
|
# "address": "0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9",
|
|
# "quantity": "10",
|
|
# "externalFee": False,
|
|
# "fee": "1.6728",
|
|
# "status": "PENDING",
|
|
# "requestedAt": "1715591843616"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
data['type'] = 'withdrawal'
|
|
return self.parse_transaction(data, currency)
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://docs.ox.fun/?json#get-v3-positions
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.subAcc]:
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
# Calling self endpoint using an API key pair linked to the parent account with the parameter "subAcc"
|
|
# allows the caller to include positions of additional sub-accounts in the response.
|
|
# This feature does not work when using API key pairs linked to a sub-account
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
response = self.privateGetV3Positions(params)
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "accountId": "106490",
|
|
# "name": "main",
|
|
# "positions": [
|
|
# {
|
|
# "marketCode": "BTC-USD-SWAP-LIN",
|
|
# "baseAsset": "BTC",
|
|
# "counterAsset": "USD",
|
|
# "position": "0.00010",
|
|
# "entryPrice": "64300.0",
|
|
# "markPrice": "63278",
|
|
# "positionPnl": "-10.1900",
|
|
# "estLiquidationPrice": "0",
|
|
# "lastUpdatedAt": "1714915841448"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# {
|
|
# "accountId": "106526",
|
|
# "name": "testSubAccount",
|
|
# "positions": [
|
|
# {
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "baseAsset": "ETH",
|
|
# "counterAsset": "USD",
|
|
# "position": "0.001",
|
|
# "entryPrice": "3080.5",
|
|
# "markPrice": "3062.0",
|
|
# "positionPnl": "-1.8500",
|
|
# "estLiquidationPrice": "0",
|
|
# "lastUpdatedAt": "1715089678013"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
allPositions = []
|
|
for i in range(0, len(data)):
|
|
account = data[i]
|
|
positions = self.safe_list(account, 'positions', [])
|
|
allPositions = self.array_concat(allPositions, positions)
|
|
return self.parse_positions(allPositions, symbols)
|
|
|
|
def parse_position(self, position, market: Market = None):
|
|
#
|
|
# {
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "baseAsset": "ETH",
|
|
# "counterAsset": "USD",
|
|
# "position": "0.001",
|
|
# "entryPrice": "3080.5",
|
|
# "markPrice": "3062.0",
|
|
# "positionPnl": "-1.8500",
|
|
# "estLiquidationPrice": "0",
|
|
# "lastUpdatedAt": "1715089678013"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': None,
|
|
'symbol': market['symbol'],
|
|
'notional': None,
|
|
'marginMode': 'cross',
|
|
'liquidationPrice': self.safe_number(position, 'estLiquidationPrice'),
|
|
'entryPrice': self.safe_number(position, 'entryPrice'),
|
|
'unrealizedPnl': self.safe_number(position, 'positionPnl'),
|
|
'realizedPnl': None,
|
|
'percentage': None,
|
|
'contracts': self.safe_number(position, 'position'),
|
|
'contractSize': None,
|
|
'markPrice': self.safe_number(position, 'markPrice'),
|
|
'lastPrice': None,
|
|
'side': None,
|
|
'hedged': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'lastUpdateTimestamp': self.safe_integer(position, 'lastUpdatedAt'),
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'collateral': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'leverage': None,
|
|
'marginRatio': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
})
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
|
"""
|
|
create a trade order
|
|
|
|
https://docs.ox.fun/?json#post-v3-orders-place
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market', 'limit', 'STOP_LIMIT' or 'STOP_MARKET'
|
|
: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 int [params.clientOrderId]: a unique id for the order
|
|
:param int [params.timestamp]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected.
|
|
:param int [params.recvWindow]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used.
|
|
:param str [params.responseType]: FULL or ACK
|
|
:param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders
|
|
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
|
|
:param float [params.limitPrice]: Limit price for the STOP_LIMIT order
|
|
:param bool [params.postOnly]: if True, the order will only be posted if it will be a maker order
|
|
:param str [params.timeInForce]: GTC(default), IOC, FOK, PO, MAKER_ONLY or MAKER_ONLY_REPRICE(reprices order to the best maker only price if the specified price were to lead to a taker trade)
|
|
:param str [params.selfTradePrevention]: NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH for more info check here {@link https://docs.ox.fun/?json#self-trade-prevention-modes}
|
|
:param str [params.displayQuantity]: for an iceberg order, pass both quantity and displayQuantity fields in the order request
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'responseType': self.safe_string(params, 'responseType', 'FULL'),
|
|
'timestamp': self.safe_integer(params, 'timestamp', self.milliseconds()),
|
|
}
|
|
params = self.omit(params, ['responseType', 'timestamp'])
|
|
recvWindow = self.safe_integer(params, 'recvWindow')
|
|
if recvWindow is not None:
|
|
request['recvWindow'] = recvWindow
|
|
params = self.omit(params, 'recvWindow')
|
|
orderRequest = self.create_order_request(symbol, type, side, amount, price, params)
|
|
request['orders'] = [orderRequest]
|
|
response = self.privatePostV3OrdersPlace(request)
|
|
#
|
|
# accepted market order responseType FULL
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "notice": "OrderMatched",
|
|
# "accountId": "106490",
|
|
# "orderId": "1000109901865",
|
|
# "submitted": True,
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "OX-USDT",
|
|
# "status": "FILLED",
|
|
# "side": "SELL",
|
|
# "isTriggered": False,
|
|
# "quantity": "150.0",
|
|
# "amount": "0.0",
|
|
# "remainQuantity": "0.0",
|
|
# "matchId": "100017047880451399",
|
|
# "matchPrice": "0.01465",
|
|
# "matchQuantity": "150.0",
|
|
# "feeInstrumentId": "USDT",
|
|
# "fees": "0.0015382500",
|
|
# "orderType": "MARKET",
|
|
# "createdAt": "1715592472236",
|
|
# "lastMatchedAt": "1715592472200",
|
|
# "displayQuantity": "150.0"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# accepted limit order responseType FULL
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "notice": "OrderOpened",
|
|
# "accountId": "106490",
|
|
# "orderId": "1000111482406",
|
|
# "submitted": True,
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "status": "OPEN",
|
|
# "side": "SELL",
|
|
# "price": "4000.0",
|
|
# "isTriggered": False,
|
|
# "quantity": "0.01",
|
|
# "amount": "0.0",
|
|
# "orderType": "LIMIT",
|
|
# "timeInForce": "GTC",
|
|
# "createdAt": "1715763507682",
|
|
# "displayQuantity": "0.01"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# accepted order responseType ACK
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "accountId": "106490",
|
|
# "orderId": "1000109892193",
|
|
# "submitted": True,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "BUY",
|
|
# "price": "0.01961",
|
|
# "isTriggered": False,
|
|
# "quantity": "100",
|
|
# "orderType": "MARKET",
|
|
# "timeInForce": "IOC",
|
|
# "createdAt": "1715591529057",
|
|
# "selfTradePreventionMode": "NONE"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# rejected order(balance insufficient)
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "code": "710001",
|
|
# "message": "System failure, exception thrown -> null",
|
|
# "submitted": False,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "BUY",
|
|
# "price": "0.01961",
|
|
# "amount": "100",
|
|
# "orderType": "MARKET",
|
|
# "timeInForce": "IOC",
|
|
# "createdAt": "1715591678835",
|
|
# "source": 11,
|
|
# "selfTradePreventionMode": "NONE"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# rejected order(bad request)
|
|
# {
|
|
# "success": True,
|
|
# "data": [
|
|
# {
|
|
# "code": "20044",
|
|
# "message": "Amount is not supported for self order type",
|
|
# "submitted": False,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "SELL",
|
|
# "amount": "200",
|
|
# "orderType": "MARKET",
|
|
# "createdAt": "1715592079986",
|
|
# "source": 11
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
order = self.safe_dict(data, 0, {})
|
|
return self.parse_order(order)
|
|
|
|
def create_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
|
|
"""
|
|
create a list of trade orders
|
|
|
|
https://docs.ox.fun/?json#post-v3-orders-place
|
|
|
|
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.timestamp]: *for all orders* in milliseconds. If orders reach the matching engine and the current timestamp exceeds timestamp + recvWindow, then all orders will be rejected.
|
|
:param int [params.recvWindow]: *for all orders* in milliseconds. If orders reach the matching engine and the current timestamp exceeds timestamp + recvWindow, then all orders will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used.
|
|
:param str [params.responseType]: *for all orders* FULL or ACK
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
ordersRequests = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
symbol = self.safe_string(rawOrder, '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)
|
|
ordersRequests.append(orderRequest)
|
|
request: dict = {
|
|
'responseType': 'FULL',
|
|
'timestamp': self.milliseconds(),
|
|
'orders': ordersRequests,
|
|
}
|
|
response = self.privatePostV3OrdersPlace(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(data)
|
|
|
|
def create_order_request(self, symbol: str, type: str, side: str, amount, price=None, params={}):
|
|
"""
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market', 'limit', 'STOP_LIMIT' or 'STOP_MARKET'
|
|
: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 int [params.clientOrderId]: a unique id for the order
|
|
:param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders
|
|
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
|
|
:param float [params.limitPrice]: Limit price for the STOP_LIMIT order
|
|
:param bool [params.postOnly]: if True, the order will only be posted if it will be a maker order
|
|
:param str [params.timeInForce]: GTC(default), IOC, FOK, PO, MAKER_ONLY or MAKER_ONLY_REPRICE(reprices order to the best maker only price if the specified price were to lead to a taker trade)
|
|
:param str [params.selfTradePrevention]: NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH for more info check here {@link https://docs.ox.fun/?json#self-trade-prevention-modes}
|
|
:param str [params.displayQuantity]: for an iceberg order, pass both quantity and displayQuantity fields in the order request
|
|
"""
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'marketCode': market['id'],
|
|
'side': side.upper(),
|
|
'source': 1000,
|
|
}
|
|
cost = self.safe_string_2(params, 'cost', 'amount')
|
|
if cost is not None:
|
|
request['amount'] = cost # todo costToPrecision
|
|
params = self.omit(params, ['cost', 'amount'])
|
|
else:
|
|
request['quantity'] = amount # todo amountToPrecision
|
|
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
|
orderType = type.upper()
|
|
if triggerPrice is not None:
|
|
if orderType == 'MARKET':
|
|
orderType = 'STOP_MARKET'
|
|
elif orderType == 'LIMIT':
|
|
orderType = 'STOP_LIMIT'
|
|
request['stopPrice'] = triggerPrice # todo priceToPrecision
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice'])
|
|
request['orderType'] = orderType
|
|
if orderType == 'STOP_LIMIT':
|
|
request['limitPrice'] = price # todo priceToPrecision
|
|
elif price is not None:
|
|
request['price'] = price # todo priceToPrecision
|
|
postOnly: Bool = None
|
|
isMarketOrder = (orderType == 'MARKET') or (orderType == 'STOP_MARKET')
|
|
postOnly, params = self.handle_post_only(isMarketOrder, False, params)
|
|
timeInForce = self.safe_string_upper(params, 'timeInForce')
|
|
if postOnly and (timeInForce != 'MAKER_ONLY_REPRICE'):
|
|
request['timeInForce'] = 'MAKER_ONLY'
|
|
selfTradePrevention = None
|
|
selfTradePrevention, params = self.handle_option_and_params(params, 'createOrder', 'selfTradePrevention')
|
|
if selfTradePrevention is not None:
|
|
request['selfTradePreventionMode'] = selfTradePrevention.upper()
|
|
return self.extend(request, params)
|
|
|
|
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://open.big.one/docs/spot_orders.html#create-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>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['spot']:
|
|
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
|
|
request: dict = {
|
|
'cost': cost,
|
|
}
|
|
return self.create_order(symbol, 'market', 'buy', None, None, self.extend(request, params))
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
|
"""
|
|
|
|
https://docs.ox.fun/?json#get-v3-orders-status
|
|
|
|
fetches information on an order made by the user
|
|
:param str id: a unique id for the order
|
|
:param str [symbol]: not used by oxfun fetchOrder
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.clientOrderId]: the client order id of the order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'orderId': id,
|
|
}
|
|
response = self.privateGetV3OrdersStatus(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": {
|
|
# "orderId": "1000111762980",
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "status": "OPEN",
|
|
# "side": "BUY",
|
|
# "price": "2700.0",
|
|
# "isTriggered": False,
|
|
# "remainQuantity": "0.01",
|
|
# "totalQuantity": "0.01",
|
|
# "amount": "0",
|
|
# "displayQuantity": "0.01",
|
|
# "cumulativeMatchedQuantity": "0",
|
|
# "orderType": "STOP_LIMIT",
|
|
# "timeInForce": "GTC",
|
|
# "source": "11",
|
|
# "createdAt": "1715794191277"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(data)
|
|
|
|
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://docs.ox.fun/?json#get-v3-orders-working
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open orders structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.orderId]: a unique id for the order
|
|
:param int [params.clientOrderId]: the client order id of the order
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
response = self.privateGetV3OrdersWorking(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(data, market, since, limit)
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://docs.ox.fun/?json#delete-v3-orders-cancel
|
|
|
|
: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 int [params.clientOrderId]: a unique id for the order
|
|
:param int [params.timestamp]: in milliseconds
|
|
:param int [params.recvWindow]: in milliseconds
|
|
:param str [params.responseType]: 'FULL' or 'ACK'
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
|
market = self.market(symbol)
|
|
marketId = market['id']
|
|
request: dict = {
|
|
'timestamp': self.milliseconds(),
|
|
'responseType': 'FULL',
|
|
}
|
|
orderRequest = {
|
|
'marketCode': marketId,
|
|
'orderId': id,
|
|
}
|
|
clientOrderId = self.safe_integer(params, 'clientOrderId')
|
|
if clientOrderId is not None:
|
|
orderRequest['clientOrderId'] = clientOrderId
|
|
request['orders'] = [orderRequest]
|
|
response = self.privateDeleteV3OrdersCancel(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
order = self.safe_dict(data, 0, {})
|
|
return self.parse_order(order)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders
|
|
|
|
https://docs.ox.fun/?json#delete-v3-orders-cancel-all
|
|
|
|
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: response from exchange
|
|
"""
|
|
request: dict = {}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['marketCode'] = market['id']
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": {"notice": "Orders queued for cancelation"}
|
|
# }
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "data": {"notice": "No working orders found"}
|
|
# }
|
|
#
|
|
response = self.privateDeleteV3OrdersCancelAll(self.extend(request, params))
|
|
return [self.safe_order({'info': response})]
|
|
|
|
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://docs.ox.fun/?json#delete-v3-orders-cancel
|
|
|
|
:param str[] ids: order ids
|
|
:param str [symbol]: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.timestamp]: in milliseconds
|
|
:param int [params.recvWindow]: in milliseconds
|
|
:param str [params.responseType]: 'FULL' or 'ACK'
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
marketId = market['id']
|
|
request: dict = {
|
|
'timestamp': self.milliseconds(),
|
|
'responseType': 'FULL',
|
|
}
|
|
orders = []
|
|
for i in range(0, len(ids)):
|
|
order = {
|
|
'marketCode': marketId,
|
|
'orderId': ids[i],
|
|
}
|
|
orders.append(order)
|
|
request['orders'] = orders
|
|
response = self.privateDeleteV3OrdersCancel(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(data, market)
|
|
|
|
def parse_order(self, order, market: Market = None) -> Order:
|
|
#
|
|
# accepted market order responseType FULL
|
|
# {
|
|
# "notice": "OrderMatched",
|
|
# "accountId": "106490",
|
|
# "orderId": "1000109901865",
|
|
# "submitted": True,
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "OX-USDT",
|
|
# "status": "FILLED",
|
|
# "side": "SELL",
|
|
# "isTriggered": False,
|
|
# "quantity": "150.0",
|
|
# "amount": "0.0",
|
|
# "remainQuantity": "0.0",
|
|
# "matchId": "100017047880451399",
|
|
# "matchPrice": "0.01465",
|
|
# "matchQuantity": "150.0",
|
|
# "feeInstrumentId": "USDT",
|
|
# "fees": "0.0015382500",
|
|
# "orderType": "MARKET",
|
|
# "createdAt": "1715592472236",
|
|
# "lastMatchedAt": "1715592472200",
|
|
# "displayQuantity": "150.0"
|
|
# }
|
|
#
|
|
# accepted limit order responseType FULL
|
|
# {
|
|
# "notice": "OrderOpened",
|
|
# "accountId": "106490",
|
|
# "orderId": "1000111482406",
|
|
# "submitted": True,
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "status": "OPEN",
|
|
# "side": "SELL",
|
|
# "price": "4000.0",
|
|
# "isTriggered": False,
|
|
# "quantity": "0.01",
|
|
# "amount": "0.0",
|
|
# "orderType": "LIMIT",
|
|
# "timeInForce": "GTC",
|
|
# "createdAt": "1715763507682",
|
|
# "displayQuantity": "0.01"
|
|
# }
|
|
#
|
|
# accepted order responseType ACK
|
|
# {
|
|
# "accountId": "106490",
|
|
# "orderId": "1000109892193",
|
|
# "submitted": True,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "BUY",
|
|
# "price": "0.01961",
|
|
# "isTriggered": False,
|
|
# "quantity": "100",
|
|
# "orderType": "MARKET",
|
|
# "timeInForce": "IOC",
|
|
# "createdAt": "1715591529057",
|
|
# "selfTradePreventionMode": "NONE"
|
|
# }
|
|
#
|
|
# rejected order(balance insufficient)
|
|
# {
|
|
# "code": "710001",
|
|
# "message": "System failure, exception thrown -> null",
|
|
# "submitted": False,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "BUY",
|
|
# "price": "0.01961",
|
|
# "amount": "100",
|
|
# "orderType": "MARKET",
|
|
# "timeInForce": "IOC",
|
|
# "createdAt": "1715591678835",
|
|
# "source": 11,
|
|
# "selfTradePreventionMode": "NONE"
|
|
# }
|
|
#
|
|
# rejected order(bad request)
|
|
# {
|
|
# "code": "20044",
|
|
# "message": "Amount is not supported for self order type",
|
|
# "submitted": False,
|
|
# "marketCode": "OX-USDT",
|
|
# "side": "SELL",
|
|
# "amount": "200",
|
|
# "orderType": "MARKET",
|
|
# "createdAt": "1715592079986",
|
|
# "source": 11
|
|
# }
|
|
#
|
|
# fetchOrder
|
|
# {
|
|
# "orderId": "1000111762980",
|
|
# "clientOrderId": "0",
|
|
# "marketCode": "ETH-USD-SWAP-LIN",
|
|
# "status": "OPEN",
|
|
# "side": "BUY",
|
|
# "price": "2700.0",
|
|
# "isTriggered": False,
|
|
# "remainQuantity": "0.01",
|
|
# "totalQuantity": "0.01",
|
|
# "amount": "0",
|
|
# "displayQuantity": "0.01",
|
|
# "cumulativeMatchedQuantity": "0",
|
|
# "orderType": "STOP_LIMIT",
|
|
# "timeInForce": "GTC",
|
|
# "source": "11",
|
|
# "createdAt": "1715794191277"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(order, 'marketCode')
|
|
market = self.safe_market(marketId, market)
|
|
timestamp = self.safe_integer(order, 'createdAt')
|
|
fee = None
|
|
feeCurrency = self.safe_string(order, 'feeInstrumentId')
|
|
if feeCurrency is not None:
|
|
fee = {
|
|
'currency': self.safe_currency_code(feeCurrency),
|
|
'cost': self.safe_number(order, 'fees'),
|
|
}
|
|
status = self.safe_string(order, 'status')
|
|
code = self.safe_integer(order, 'code') # rejected orders have code of the error
|
|
if code is not None:
|
|
status = 'rejected'
|
|
triggerPrice = self.safe_string(order, 'stopPrice')
|
|
return self.safe_order({
|
|
'id': self.safe_string(order, 'orderId'),
|
|
'clientOrderId': self.safe_string(order, 'clientOrderId'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': self.safe_integer(order, 'lastMatchedAt'),
|
|
'lastUpdateTimestamp': self.safe_integer(order, 'lastModifiedAt'),
|
|
'status': self.parse_order_status(status),
|
|
'symbol': market['symbol'],
|
|
'type': self.parse_order_type(self.safe_string(order, 'orderType')),
|
|
'timeInForce': self.parse_order_time_in_force(self.safe_string(order, 'timeInForce')), # only for limit orders
|
|
'side': self.safe_string_lower(order, 'side'),
|
|
'price': self.safe_string_n(order, ['price', 'matchPrice', 'limitPrice']),
|
|
'average': None,
|
|
'amount': self.safe_string_2(order, 'totalQuantity', 'quantity'),
|
|
'filled': self.safe_string_2(order, 'cumulativeMatchedQuantity', 'matchQuantity'),
|
|
'remaining': self.safe_string(order, 'remainQuantity'),
|
|
'triggerPrice': triggerPrice,
|
|
'stopLossPrice': triggerPrice,
|
|
'cost': self.omit_zero(self.safe_string(order, 'amount')),
|
|
'trades': None,
|
|
'fee': fee,
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_order_status(self, status):
|
|
statuses: dict = {
|
|
'OPEN': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'PARTIAL_FILL': 'open',
|
|
'FILLED': 'closed',
|
|
'CANCELED': 'canceled',
|
|
'CANCELED_BY_USER': 'canceled',
|
|
'CANCELED_BY_MAKER_ONLY': 'rejected',
|
|
'CANCELED_BY_FOK': 'rejected',
|
|
'CANCELED_ALL_BY_IOC': 'rejected',
|
|
'CANCELED_PARTIAL_BY_IOC': 'canceled',
|
|
'CANCELED_BY_SELF_TRADE_PROTECTION': 'rejected',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order_type(self, type):
|
|
types: dict = {
|
|
'LIMIT': 'limit',
|
|
'STOP_LIMIT': 'limit',
|
|
'MARKET': 'market',
|
|
'STOP_MARKET': 'market',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def parse_order_time_in_force(self, type):
|
|
types: dict = {
|
|
'GTC': 'GTC',
|
|
'IOC': 'IOC',
|
|
'FOK': 'FOK',
|
|
'MAKER_ONLY': 'PO',
|
|
'MAKER_ONLY_REPRICE': 'PO',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
baseUrl = self.urls['api'][api]
|
|
url = baseUrl + '/' + path
|
|
queryString = ''
|
|
if method == 'GET':
|
|
queryString = self.urlencode(params)
|
|
if len(queryString) != 0:
|
|
url += '?' + queryString
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
timestamp = self.milliseconds()
|
|
isoDatetime = self.iso8601(timestamp)
|
|
datetimeParts = isoDatetime.split('.')
|
|
datetime = datetimeParts[0]
|
|
nonce = self.nonce()
|
|
urlParts = baseUrl.split('//')
|
|
if (method == 'POST') or (method == 'DELETE'):
|
|
body = self.json(params)
|
|
queryString = body
|
|
msgString = datetime + '\n' + str(nonce) + '\n' + method + '\n' + urlParts[1] + '\n/' + path + '\n' + queryString
|
|
signature = self.hmac(self.encode(msgString), self.encode(self.secret), hashlib.sha256, 'base64')
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
'AccessKey': self.apiKey,
|
|
'Timestamp': datetime,
|
|
'Signature': signature,
|
|
'Nonce': str(nonce),
|
|
}
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None
|
|
if code != 200:
|
|
responseCode = self.safe_string(response, 'code', None)
|
|
feedback = self.id + ' ' + body
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], responseCode, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|