Files
ccxt_with_mt5/ccxt/async_support/hyperliquid.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

3886 lines
164 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.hyperliquid import ImplicitAPI
import asyncio
import math
from ccxt.base.types import Any, Balances, Currencies, Currency, Int, LedgerEntry, MarginModification, Market, Num, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, MarketInterface, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
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.decimal_to_precision import ROUND
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class hyperliquid(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(hyperliquid, self).describe(), {
'id': 'hyperliquid',
'name': 'Hyperliquid',
'countries': [],
'version': 'v1',
'rateLimit': 50, # 1200 requests per minute, 20 request per second
'certified': True,
'pro': True,
'dex': True,
'has': {
'CORS': None,
'spot': True,
'margin': False,
'swap': True,
'future': True,
'option': False,
'addMargin': True,
'borrowCrossMargin': False,
'borrowIsolatedMargin': False,
'cancelAllOrders': False,
'cancelAllOrdersAfter': True,
'cancelOrder': True,
'cancelOrders': True,
'cancelOrdersForSymbols': True,
'closeAllPositions': False,
'closePosition': False,
'createMarketBuyOrderWithCost': False,
'createMarketOrderWithCost': False,
'createMarketSellOrderWithCost': False,
'createOrder': True,
'createOrders': True,
'createOrderWithTakeProfitAndStopLoss': True,
'createReduceOnlyOrder': True,
'createStopOrder': True,
'createTriggerOrder': True,
'editOrder': True,
'editOrders': True,
'fetchAccounts': False,
'fetchBalance': True,
'fetchBorrowInterest': False,
'fetchBorrowRateHistories': False,
'fetchBorrowRateHistory': False,
'fetchCanceledAndClosedOrders': True,
'fetchCanceledOrders': True,
'fetchClosedOrders': True,
'fetchCrossBorrowRate': False,
'fetchCrossBorrowRates': False,
'fetchCurrencies': True,
'fetchDepositAddress': False,
'fetchDepositAddresses': False,
'fetchDeposits': True,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': False,
'fetchFundingHistory': True,
'fetchFundingRate': False,
'fetchFundingRateHistory': True,
'fetchFundingRates': True,
'fetchIndexOHLCV': False,
'fetchIsolatedBorrowRate': False,
'fetchIsolatedBorrowRates': False,
'fetchLedger': True,
'fetchLeverage': False,
'fetchLeverageTiers': False,
'fetchLiquidations': False,
'fetchMarginMode': None,
'fetchMarketLeverageTiers': False,
'fetchMarkets': True,
'fetchMarkOHLCV': False,
'fetchMyLiquidations': False,
'fetchMyTrades': True,
'fetchOHLCV': True,
'fetchOpenInterest': True,
'fetchOpenInterestHistory': False,
'fetchOpenInterests': True,
'fetchOpenOrders': True,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchOrders': True,
'fetchOrderTrades': False,
'fetchPosition': True,
'fetchPositionMode': False,
'fetchPositions': True,
'fetchPositionsRisk': False,
'fetchPremiumIndexOHLCV': False,
'fetchTicker': 'emulated',
'fetchTickers': True,
'fetchTime': False,
'fetchTrades': True,
'fetchTradingFee': True,
'fetchTradingFees': False,
'fetchTransfer': False,
'fetchTransfers': False,
'fetchWithdrawal': False,
'fetchWithdrawals': True,
'reduceMargin': True,
'repayCrossMargin': False,
'repayIsolatedMargin': False,
'sandbox': True,
'setLeverage': True,
'setMarginMode': True,
'setPositionMode': False,
'transfer': True,
'withdraw': True,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'8h': '8h',
'12h': '12h',
'1d': '1d',
'3d': '3d',
'1w': '1w',
'1M': '1M',
},
'hostname': 'hyperliquid.xyz',
'urls': {
'logo': 'https://github.com/ccxt/ccxt/assets/43336371/b371bc6c-4a8c-489f-87f4-20a913dd8d4b',
'api': {
'public': 'https://api.{hostname}',
'private': 'https://api.{hostname}',
},
'test': {
'public': 'https://api.hyperliquid-testnet.xyz',
'private': 'https://api.hyperliquid-testnet.xyz',
},
'www': 'https://hyperliquid.xyz',
'doc': 'https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api',
'fees': 'https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees',
'referral': 'https://app.hyperliquid.xyz/',
},
'api': {
'public': {
'post': {
'info': {
'cost': 20,
'byType': {
'l2Book': 2,
'allMids': 2,
'clearinghouseState': 2,
'orderStatus': 2,
'spotClearinghouseState': 2,
'exchangeStatus': 2,
'candleSnapshot': 4,
},
},
},
},
'private': {
'post': {
'exchange': 1,
},
},
},
'fees': {
'swap': {
'taker': self.parse_number('0.00045'),
'maker': self.parse_number('0.00015'),
},
'spot': {
'taker': self.parse_number('0.0007'),
'maker': self.parse_number('0.0004'),
},
},
'requiredCredentials': {
'apiKey': False,
'secret': False,
'walletAddress': True,
'privateKey': True,
},
'exceptions': {
'exact': {
},
'broad': {
'Price must be divisible by tick size.': InvalidOrder,
'Order must have minimum value of $10': InvalidOrder,
'Insufficient margin to place order.': InsufficientFunds,
'Reduce only order would increase position.': InvalidOrder,
'Post only order would have immediately matched,': InvalidOrder,
'Order could not immediately match against any resting orders.': InvalidOrder,
'Invalid TP/SL price.': InvalidOrder,
'No liquidity available for market order.': InvalidOrder,
'Order was never placed, already canceled, or filled.': OrderNotFound,
'User or API Wallet ': InvalidOrder,
'Order has invalid size': InvalidOrder,
'Order price cannot be more than 80% away from the reference price': InvalidOrder,
'Order has zero size.': InvalidOrder,
'Insufficient spot balance asset': InsufficientFunds,
'Insufficient balance for withdrawal': InsufficientFunds,
'Insufficient balance for token transfer': InsufficientFunds,
},
},
'precisionMode': TICK_SIZE,
'commonCurrencies': {
},
'options': {
'defaultType': 'swap',
'sandboxMode': False,
'defaultSlippage': 0.05,
'zeroAddress': '0x0000000000000000000000000000000000000000',
'spotCurrencyMapping': {
'UDZ': '2Z',
'UBONK': 'BONK',
'UBTC': 'BTC',
'UETH': 'ETH',
'UFART': 'FARTCOIN',
'HPENGU': 'PENGU',
'UPUMP': 'PUMP',
'USOL': 'SOL',
'UUUSPX': 'SPX',
'USDT0': 'USDT',
'XAUT0': 'XAUT',
'UXPL': 'XPL',
},
},
'features': {
'default': {
'sandbox': True,
'createOrder': {
'marginMode': False,
'triggerPrice': False,
'triggerPriceType': None,
'triggerDirection': False,
'stopLossPrice': False,
'takeProfitPrice': False,
'attachedStopLossTakeProfit': {
'triggerPriceType': {
'last': False,
'mark': False,
'index': False,
},
'triggerPrice': True,
'type': True,
'price': True,
},
'timeInForce': {
'IOC': True,
'FOK': False,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'leverage': False,
'marketBuyByCost': False,
'marketBuyRequiresPrice': False,
'selfTradePrevention': False,
'iceberg': False,
},
'createOrders': {
'max': 1000,
},
'fetchMyTrades': {
'marginMode': False,
'limit': 2000,
'daysBack': None,
'untilDays': None,
'symbolRequired': True,
},
'fetchOrder': {
'marginMode': False,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOpenOrders': {
'marginMode': False,
'limit': 2000,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOrders': {
'marginMode': False,
'limit': 2000,
'daysBack': None,
'untilDays': None,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchClosedOrders': {
'marginMode': False,
'limit': 2000,
'daysBack': None,
'daysBackCanceled': None,
'untilDays': None,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOHLCV': {
'limit': 5000,
},
},
'spot': {
'extends': 'default',
},
'forPerps': {
'extends': 'default',
'createOrder': {
'stopLossPrice': True,
'takeProfitPrice': True,
'attachedStopLossTakeProfit': None, # todo, in two orders
},
},
'swap': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
'future': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
},
})
def set_sandbox_mode(self, enabled):
super(hyperliquid, self).set_sandbox_mode(enabled)
self.options['sandboxMode'] = enabled
def market(self, symbol: str) -> MarketInterface:
if self.markets is None:
raise ExchangeError(self.id + ' markets not loaded')
if symbol in self.markets:
market = self.markets[symbol]
if market['spot']:
baseName = self.safe_string(market, 'baseName')
spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
if baseName in spotCurrencyMapping:
unifiedBaseName = self.safe_string(spotCurrencyMapping, baseName)
quote = self.safe_string(market, 'quote')
newSymbol = self.safe_currency_code(unifiedBaseName) + '/' + quote
if newSymbol in self.markets:
return self.markets[newSymbol]
res = super(hyperliquid, self).market(symbol)
return res
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
if marketId is not None:
if (self.markets_by_id is not None) and (marketId in self.markets_by_id):
markets = self.markets_by_id[marketId]
numMarkets = len(markets)
if numMarkets == 1:
return markets[0]
else:
if numMarkets > 2:
raise ExchangeError(self.id + ' safeMarket() found more than two markets with the same market id ' + marketId)
firstMarket = markets[0]
secondMarket = markets[1]
if self.safe_string(firstMarket, 'type') != self.safe_string(secondMarket, 'type'):
raise ExchangeError(self.id + ' safeMarket() found two different market types with the same market id ' + marketId)
baseCurrency = self.safe_string(firstMarket, 'base')
spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
if baseCurrency in spotCurrencyMapping:
return secondMarket
return firstMarket
return super(hyperliquid, self).safe_market(marketId, market, delimiter, marketType)
async def fetch_currencies(self, params={}) -> Currencies:
"""
fetches all available currencies on an exchange
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an associative dictionary of currencies
"""
if self.check_required_credentials(False):
await self.initialize_client()
request: dict = {
'type': 'meta',
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "universe": [
# {
# "maxLeverage": 50,
# "name": "SOL",
# "onlyIsolated": False,
# "szDecimals": 2
# }
# ]
# }
# ]
#
meta = self.safe_list(response, 'universe', [])
result: dict = {}
for i in range(0, len(meta)):
data = self.safe_dict(meta, i, {})
id = i
name = self.safe_string(data, 'name')
code = self.safe_currency_code(name)
result[code] = self.safe_currency_structure({
'id': id,
'name': name,
'code': code,
'precision': None,
'info': data,
'active': None,
'deposit': None,
'withdraw': None,
'networks': None,
'fee': None,
'type': 'crypto',
'limits': {
'amount': {
'min': None,
'max': None,
},
'withdraw': {
'min': None,
'max': None,
},
},
})
return result
async def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for hyperliquid
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
rawPromises = [
self.fetch_swap_markets(params),
self.fetch_spot_markets(params),
]
promises = await asyncio.gather(*rawPromises)
swapMarkets = promises[0]
spotMarkets = promises[1]
return self.array_concat(swapMarkets, spotMarkets)
async def fetch_swap_markets(self, params={}) -> List[Market]:
"""
retrieves data on all swap markets for hyperliquid
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
request: dict = {
'type': 'metaAndAssetCtxs',
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "universe": [
# {
# "maxLeverage": 50,
# "name": "SOL",
# "onlyIsolated": False,
# "szDecimals": 2
# }
# ]
# },
# [
# {
# "dayNtlVlm": "9450588.2273",
# "funding": "0.0000198",
# "impactPxs": [
# "108.04",
# "108.06"
# ],
# "markPx": "108.04",
# "midPx": "108.05",
# "openInterest": "10764.48",
# "oraclePx": "107.99",
# "premium": "0.00055561",
# "prevDayPx": "111.81"
# }
# ]
# ]
#
#
meta = self.safe_dict(response, 0, {})
universe = self.safe_list(meta, 'universe', [])
assetCtxs = self.safe_list(response, 1, [])
result = []
for i in range(0, len(universe)):
data = self.extend(
self.safe_dict(universe, i, {}),
self.safe_dict(assetCtxs, i, {})
)
data['baseId'] = i
result.append(data)
return self.parse_markets(result)
def calculate_price_precision(self, price: float, amountPrecision: float, maxDecimals: float):
"""
Helper function to calculate the Hyperliquid DECIMAL_PLACES price precision
:param float price: the price to use in the calculation
:param int amountPrecision: the amountPrecision to use in the calculation
:param int maxDecimals: the maxDecimals to use in the calculation
:returns int: The calculated price precision
"""
pricePrecision = 0
priceStr = self.number_to_string(price)
if priceStr is None:
return 0
priceSplitted = priceStr.split('.')
if Precise.string_eq(priceStr, '0'):
# Significant digits is always hasattr(self, 5) case
significantDigits = 5
# Integer digits is always hasattr(self, 0) case(0 doesn't count)
integerDigits = 0
# Calculate the price precision
pricePrecision = min(maxDecimals - amountPrecision, significantDigits - integerDigits)
elif Precise.string_gt(priceStr, '0') and Precise.string_lt(priceStr, '1'):
# Significant digits, always hasattr(self, 5) case
significantDigits = 5
# Get the part after the decimal separator
decimalPart = self.safe_string(priceSplitted, 1, '')
# Count the number of leading zeros in the decimal part
leadingZeros = 0
while((leadingZeros <= len(decimalPart)) and (decimalPart[leadingZeros] == '0')):
leadingZeros = leadingZeros + 1
# Calculate price precision based on leading zeros and significant digits
pricePrecision = leadingZeros + significantDigits
# Calculate the price precision based on maxDecimals - szDecimals and the calculated price precision from the previous step
pricePrecision = min(maxDecimals - amountPrecision, pricePrecision)
else:
# Count the numbers before the decimal separator
integerPart = self.safe_string(priceSplitted, 0, '')
# Get significant digits, take the max() of 5 and the integer digits count
significantDigits = max(5, len(integerPart))
# Calculate price precision based on maxDecimals - szDecimals and significantDigits - len(integerPart)
pricePrecision = min(maxDecimals - amountPrecision, significantDigits - len(integerPart))
return self.parse_to_int(pricePrecision)
async def fetch_spot_markets(self, params={}) -> List[Market]:
"""
retrieves data on all spot markets for hyperliquid
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
request: dict = {
'type': 'spotMetaAndAssetCtxs',
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "tokens": [
# {
# "name": "USDC",
# "szDecimals": 8,
# "weiDecimals" 8,
# "index": 0,
# "tokenId": "0x6d1e7cde53ba9467b783cb7c530ce054",
# "isCanonical": True,
# "evmContract":null,
# "fullName":null
# },
# {
# "name": "PURR",
# "szDecimals": 0,
# "weiDecimals": 5,
# "index": 1,
# "tokenId": "0xc1fb593aeffbeb02f85e0308e9956a90",
# "isCanonical": True,
# "evmContract":null,
# "fullName":null
# }
# ],
# "universe": [
# {
# "name": "PURR/USDC",
# "tokens": [1, 0],
# "index": 0,
# "isCanonical": True
# }
# ]
# },
# [
# {
# "dayNtlVlm":"8906.0",
# "markPx":"0.14",
# "midPx":"0.209265",
# "prevDayPx":"0.20432"
# }
# ]
# ]
#
first = self.safe_dict(response, 0, {})
second = self.safe_list(response, 1, [])
meta = self.safe_list(first, 'universe', [])
tokens = self.safe_list(first, 'tokens', [])
markets = []
for i in range(0, len(meta)):
market = self.safe_dict(meta, i, {})
index = self.safe_integer(market, 'index')
extraData = self.safe_dict(second, index, {})
marketName = self.safe_string(market, 'name')
# if marketName.find('/') < 0:
# # there are some weird spot markets in testnet, eg @2
# continue
# }
# marketParts = marketName.split('/')
# baseName = self.safe_string(marketParts, 0)
# quoteId = self.safe_string(marketParts, 1)
fees = self.safe_dict(self.fees, 'spot', {})
taker = self.safe_number(fees, 'taker')
maker = self.safe_number(fees, 'maker')
tokensPos = self.safe_list(market, 'tokens', [])
baseTokenPos = self.safe_integer(tokensPos, 0)
quoteTokenPos = self.safe_integer(tokensPos, 1)
baseTokenInfo = self.safe_dict(tokens, baseTokenPos, {})
quoteTokenInfo = self.safe_dict(tokens, quoteTokenPos, {})
baseName = self.safe_string(baseTokenInfo, 'name')
quoteId = self.safe_string(quoteTokenInfo, 'name')
# do spot currency mapping
spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
mappedBaseName = self.safe_string(spotCurrencyMapping, baseName, baseName)
mappedQuoteId = self.safe_string(spotCurrencyMapping, quoteId, quoteId)
mappedBase = self.safe_currency_code(mappedBaseName)
mappedQuote = self.safe_currency_code(mappedQuoteId)
mappedSymbol = mappedBase + '/' + mappedQuote
innerBaseTokenInfo = self.safe_dict(baseTokenInfo, 'spec', baseTokenInfo)
# innerQuoteTokenInfo = self.safe_dict(quoteTokenInfo, 'spec', quoteTokenInfo)
amountPrecisionStr = self.safe_string(innerBaseTokenInfo, 'szDecimals')
amountPrecision = int(amountPrecisionStr)
price = self.safe_number(extraData, 'midPx')
pricePrecision = 0
if price is not None:
pricePrecision = self.calculate_price_precision(price, amountPrecision, 8)
pricePrecisionStr = self.number_to_string(pricePrecision)
# quotePrecision = self.parse_number(self.parse_precision(self.safe_string(innerQuoteTokenInfo, 'szDecimals')))
baseId = self.number_to_string(index + 10000)
entry = {
'id': marketName,
'symbol': mappedSymbol,
'base': mappedBase,
'quote': mappedQuote,
'settle': None,
'baseId': baseId,
'baseName': baseName,
'quoteId': quoteId,
'settleId': None,
'type': 'spot',
'spot': True,
'subType': None,
'margin': None,
'swap': False,
'future': False,
'option': False,
'active': True,
'contract': False,
'linear': None,
'inverse': None,
'taker': taker,
'maker': maker,
'contractSize': None,
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
},
'limits': {
'leverage': {
'min': None,
'max': None,
},
'amount': {
'min': None,
'max': None,
},
'price': {
'min': None,
'max': None,
},
'cost': {
'min': self.parse_number('10'),
'max': None,
},
},
'created': None,
'info': self.extend(extraData, market),
}
markets.append(self.safe_market_structure(entry))
# backward support
base = self.safe_currency_code(baseName)
quote = self.safe_currency_code(quoteId)
newEntry = self.extend({}, entry)
symbol = base + '/' + quote
if symbol != mappedSymbol:
newEntry['symbol'] = symbol
newEntry['base'] = base
newEntry['quote'] = quote
newEntry['baseName'] = baseName
markets.append(self.safe_market_structure(newEntry))
return markets
def parse_market(self, market: dict) -> Market:
#
# {
# "maxLeverage": "50",
# "name": "ETH",
# "onlyIsolated": False,
# "szDecimals": "4",
# "dayNtlVlm": "1709813.11535",
# "funding": "0.00004807",
# "impactPxs": [
# "2369.3",
# "2369.6"
# ],
# "markPx": "2369.6",
# "midPx": "2369.45",
# "openInterest": "1815.4712",
# "oraclePx": "2367.3",
# "premium": "0.00090821",
# "prevDayPx": "2381.5"
# }
#
quoteId = 'USDC'
baseName = self.safe_string(market, 'name')
base = self.safe_currency_code(baseName)
quote = self.safe_currency_code(quoteId)
baseId = self.safe_string(market, 'baseId')
settleId = 'USDC'
settle = self.safe_currency_code(settleId)
symbol = base + '/' + quote
contract = True
swap = True
if contract:
if swap:
symbol = symbol + ':' + settle
fees = self.safe_dict(self.fees, 'swap', {})
taker = self.safe_number(fees, 'taker')
maker = self.safe_number(fees, 'maker')
amountPrecisionStr = self.safe_string(market, 'szDecimals')
amountPrecision = int(amountPrecisionStr)
price = self.safe_number(market, 'markPx', 0)
pricePrecision = 0
if price is not None:
pricePrecision = self.calculate_price_precision(price, amountPrecision, 6)
pricePrecisionStr = self.number_to_string(pricePrecision)
isDelisted = self.safe_bool(market, 'isDelisted')
active = True
if isDelisted is not None:
active = not isDelisted
return self.safe_market_structure({
'id': baseId,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'baseName': baseName,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': False,
'margin': None,
'swap': swap,
'future': False,
'option': False,
'active': active,
'contract': contract,
'linear': True,
'inverse': False,
'taker': taker,
'maker': maker,
'contractSize': self.parse_number('1'),
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
},
'limits': {
'leverage': {
'min': None,
'max': self.safe_integer(market, 'maxLeverage'),
},
'amount': {
'min': None,
'max': None,
},
'price': {
'min': None,
'max': None,
},
'cost': {
'min': self.parse_number('10'),
'max': None,
},
},
'created': None,
'info': market,
})
async def fetch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-balances
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.type]: wallet type, ['spot', 'swap'], defaults to swap
:param str [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchBalance', params)
type = None
type, params = self.handle_market_type_and_params('fetchBalance', None, params)
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params)
isSpot = (type == 'spot')
request: dict = {
'type': 'spotClearinghouseState' if (isSpot) else 'clearinghouseState',
'user': userAddress,
}
response = await self.publicPostInfo(self.extend(request, params))
#
# {
# "assetPositions": [],
# "crossMaintenanceMarginUsed": "0.0",
# "crossMarginSummary": {
# "accountValue": "100.0",
# "totalMarginUsed": "0.0",
# "totalNtlPos": "0.0",
# "totalRawUsd": "100.0"
# },
# "marginSummary": {
# "accountValue": "100.0",
# "totalMarginUsed": "0.0",
# "totalNtlPos": "0.0",
# "totalRawUsd": "100.0"
# },
# "time": "1704261007014",
# "withdrawable": "100.0"
# }
# spot
#
# {
# "balances":[
# {
# "coin":"USDC",
# "hold":"0.0",
# "total":"1481.844"
# },
# {
# "coin":"PURR",
# "hold":"0.0",
# "total":"999.65004"
# }
# }
#
balances = self.safe_list(response, 'balances')
if balances is not None:
spotBalances: dict = {'info': response}
for i in range(0, len(balances)):
balance = balances[i]
code = self.safe_currency_code(self.safe_string(balance, 'coin'))
account = self.account()
total = self.safe_string(balance, 'total')
used = self.safe_string(balance, 'hold')
account['total'] = total
account['used'] = used
spotBalances[code] = account
return self.safe_balance(spotBalances)
data = self.safe_dict(response, 'marginSummary', {})
usdcBalance = {
'total': self.safe_number(data, 'accountValue'),
}
if (marginMode is not None) and (marginMode == 'isolated'):
usdcBalance['free'] = self.safe_number(response, 'withdrawable')
else:
usdcBalance['used'] = self.safe_number(data, 'totalMarginUsed')
result: dict = {
'info': response,
'USDC': usdcBalance,
}
timestamp = self.safe_integer(response, 'time')
result['timestamp'] = timestamp
result['datetime'] = self.iso8601(timestamp)
return self.safe_balance(result)
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#l2-book-snapshot
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
market = self.market(symbol)
request: dict = {
'type': 'l2Book',
'coin': market['baseName'] if market['swap'] else market['id'],
}
response = await self.publicPostInfo(self.extend(request, params))
#
# {
# "coin": "ETH",
# "levels": [
# [
# {
# "n": "2",
# "px": "2216.2",
# "sz": "74.0637"
# }
# ],
# [
# {
# "n": "2",
# "px": "2216.5",
# "sz": "70.5893"
# }
# ]
# ],
# "time": "1704290104840"
# }
#
data = self.safe_list(response, 'levels', [])
result: dict = {
'bids': self.safe_list(data, 0, []),
'asks': self.safe_list(data, 1, []),
}
timestamp = self.safe_integer(response, 'time')
return self.parse_order_book(result, market['symbol'], timestamp, 'bids', 'asks', 'px', 'sz')
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
:param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.type]: 'spot' or 'swap', by default fetches both
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
# at self stage, to get tickers data, we use fetchMarkets endpoints
response = []
type = self.safe_string(params, 'type')
params = self.omit(params, 'type')
if type == 'spot':
response = await self.fetch_spot_markets(params)
elif type == 'swap':
response = await self.fetch_swap_markets(params)
else:
response = await self.fetch_markets(params)
# same response "fetchMarkets"
result: dict = {}
for i in range(0, len(response)):
market = response[i]
info = market['info']
ticker = self.parse_ticker(info, market)
symbol = self.safe_string(ticker, 'symbol')
result[symbol] = ticker
return self.filter_by_array_tickers(result, 'symbol', symbols)
async def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
"""
retrieves data on all swap markets for hyperliquid
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
:param str[] [symbols]: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
request: dict = {
'type': 'metaAndAssetCtxs',
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "universe": [
# {
# "maxLeverage": 50,
# "name": "SOL",
# "onlyIsolated": False,
# "szDecimals": 2
# }
# ]
# },
# [
# {
# "dayNtlVlm": "9450588.2273",
# "funding": "0.0000198",
# "impactPxs": [
# "108.04",
# "108.06"
# ],
# "markPx": "108.04",
# "midPx": "108.05",
# "openInterest": "10764.48",
# "oraclePx": "107.99",
# "premium": "0.00055561",
# "prevDayPx": "111.81"
# }
# ]
# ]
#
#
meta = self.safe_dict(response, 0, {})
universe = self.safe_list(meta, 'universe', [])
assetCtxs = self.safe_list(response, 1, [])
result = []
for i in range(0, len(universe)):
data = self.extend(
self.safe_dict(universe, i, {}),
self.safe_dict(assetCtxs, i, {})
)
result.append(data)
return self.parse_funding_rates(result, symbols)
def parse_funding_rate(self, info, market: Market = None) -> FundingRate:
#
# {
# "maxLeverage": "50",
# "name": "ETH",
# "onlyIsolated": False,
# "szDecimals": "4",
# "dayNtlVlm": "1709813.11535",
# "funding": "0.00004807",
# "impactPxs": [
# "2369.3",
# "2369.6"
# ],
# "markPx": "2369.6",
# "midPx": "2369.45",
# "openInterest": "1815.4712",
# "oraclePx": "2367.3",
# "premium": "0.00090821",
# "prevDayPx": "2381.5"
# }
#
base = self.safe_string(info, 'name')
marketId = self.coin_to_market_id(base)
symbol = self.safe_symbol(marketId, market)
funding = self.safe_number(info, 'funding')
markPx = self.safe_number(info, 'markPx')
oraclePx = self.safe_number(info, 'oraclePx')
fundingTimestamp = (int(math.floor(self.milliseconds()) / 60 / 60 / 1000) + 1) * 60 * 60 * 1000
return {
'info': info,
'symbol': symbol,
'markPrice': markPx,
'indexPrice': oraclePx,
'interestRate': None,
'estimatedSettlePrice': None,
'timestamp': None,
'datetime': None,
'fundingRate': funding,
'fundingTimestamp': fundingTimestamp,
'fundingDatetime': self.iso8601(fundingTimestamp),
'nextFundingRate': None,
'nextFundingTimestamp': None,
'nextFundingDatetime': None,
'previousFundingRate': None,
'previousFundingTimestamp': None,
'previousFundingDatetime': None,
'interval': '1h',
}
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
#
# {
# "prevDayPx": "3400.5",
# "dayNtlVlm": "511297257.47936022",
# "markPx": "3464.7",
# "midPx": "3465.05",
# "oraclePx": "3460.1", # only in swap
# "openInterest": "64638.1108", # only in swap
# "premium": "0.00141614", # only in swap
# "funding": "0.00008727", # only in swap
# "impactPxs": ["3465.0", "3465.1"], # only in swap
# "coin": "PURR", # only in spot
# "circulatingSupply": "998949190.03400207", # only in spot
# },
#
bidAsk = self.safe_list(ticker, 'impactPxs')
return self.safe_ticker({
'symbol': market['symbol'],
'timestamp': None,
'datetime': None,
'previousClose': self.safe_number(ticker, 'prevDayPx'),
'close': self.safe_number(ticker, 'midPx'),
'bid': self.safe_number(bidAsk, 0),
'ask': self.safe_number(bidAsk, 1),
'quoteVolume': self.safe_number(ticker, 'dayNtlVlm'),
'info': ticker,
}, market)
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#candle-snapshot
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents, support '1m', '15m', '1h', '1d'
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest candle to fetch
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
await self.load_markets()
market = self.market(symbol)
until = self.safe_integer(params, 'until', self.milliseconds())
useTail = since is None
originalSince = since
if since is None:
if limit is not None:
# optimization if limit is provided
timeframeInMilliseconds = self.parse_timeframe(timeframe) * 1000
since = self.sum(until, timeframeInMilliseconds * limit * -1)
if since < 0:
since = 0
useTail = False
else:
since = 0
params = self.omit(params, ['until'])
request: dict = {
'type': 'candleSnapshot',
'req': {
'coin': market['baseName'] if market['swap'] else market['id'],
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
'startTime': since,
'endTime': until,
},
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "T": 1704287699999,
# "c": "2226.4",
# "h": "2247.9",
# "i": "15m",
# "l": "2224.6",
# "n": 46,
# "o": "2247.9",
# "s": "ETH",
# "t": 1704286800000,
# "v": "591.6427"
# }
# ]
#
return self.parse_ohlcvs(response, market, timeframe, originalSince, limit, useTail)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# {
# "T": 1704287699999,
# "c": "2226.4",
# "h": "2247.9",
# "i": "15m",
# "l": "2224.6",
# "n": 46,
# "o": "2247.9",
# "s": "ETH",
# "t": 1704286800000,
# "v": "591.6427"
# }
#
return [
self.safe_integer(ohlcv, 't'),
self.safe_number(ohlcv, 'o'),
self.safe_number(ohlcv, 'h'),
self.safe_number(ohlcv, 'l'),
self.safe_number(ohlcv, 'c'),
self.safe_number(ohlcv, 'v'),
]
async def fetch_trades(self, symbol: Str, since: Int = None, limit: Int = None, params={}):
"""
get the list of most recent trades for a particular symbol
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trades structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest trade
:param str [params.address]: wallet address that made trades
:param str [params.user]: wallet address that made trades
:param str [params.subAccountAddress]: sub account user address
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchTrades', params)
await self.load_markets()
market = self.safe_market(symbol)
request: dict = {
'user': userAddress,
}
if since is not None:
request['type'] = 'userFillsByTime'
request['startTime'] = since
else:
request['type'] = 'userFills'
until = self.safe_integer(params, 'until')
params = self.omit(params, 'until')
if until is not None:
request['endTime'] = until
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "closedPnl": "0.19343",
# "coin": "ETH",
# "crossed": True,
# "dir": "Close Long",
# "fee": "0.050062",
# "hash": "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
# "liquidationMarkPx": null,
# "oid": 3929354691,
# "px": "2381.1",
# "side": "A",
# "startPosition": "0.0841",
# "sz": "0.0841",
# "tid": 128423918764978,
# "time": 1704262888911
# }
# ]
#
return self.parse_trades(response, market, since, limit)
def amount_to_precision(self, symbol, amount):
market = self.market(symbol)
return self.decimal_to_precision(amount, ROUND, market['precision']['amount'], self.precisionMode, self.paddingMode)
def price_to_precision(self, symbol: str, price) -> str:
market = self.market(symbol)
priceStr = self.number_to_string(price)
integerPart = priceStr.split('.')[0]
significantDigits = max(5, len(integerPart))
result = self.decimal_to_precision(price, ROUND, significantDigits, SIGNIFICANT_DIGITS, self.paddingMode)
maxDecimals = 8 if market['spot'] else 6
subtractedValue = maxDecimals - self.precision_from_string(self.safe_string(market['precision'], 'amount'))
return self.decimal_to_precision(result, ROUND, subtractedValue, DECIMAL_PLACES, self.paddingMode)
def hash_message(self, message):
return '0x' + self.hash(message, 'keccak', 'hex')
def sign_hash(self, hash, privateKey):
signature = self.ecdsa(hash[-64:], privateKey[-64:], 'secp256k1', None)
return {
'r': '0x' + signature['r'],
's': '0x' + signature['s'],
'v': self.sum(27, signature['v']),
}
def sign_message(self, message, privateKey):
return self.sign_hash(self.hash_message(message), privateKey[-64:])
def construct_phantom_agent(self, hash, isTestnet=True):
source = 'b' if (isTestnet) else 'a'
return {
'source': source,
'connectionId': hash,
}
def action_hash(self, action, vaultAddress, nonce):
dataBinary = self.packb(action)
dataHex = self.binary_to_base16(dataBinary)
data = dataHex
data += '00000' + self.int_to_base16(nonce)
if vaultAddress is None:
data += '00'
else:
data += '01'
data += vaultAddress
return self.hash(self.base16_to_binary(data), 'keccak', 'binary')
def sign_l1_action(self, action, nonce, vaultAdress=None) -> object:
hash = self.action_hash(action, vaultAdress, nonce)
isTestnet = self.safe_bool(self.options, 'sandboxMode', False)
phantomAgent = self.construct_phantom_agent(hash, isTestnet)
# data: Dict = {
# 'domain': {
# 'chainId': 1337,
# 'name': 'Exchange',
# 'verifyingContract': '0x0000000000000000000000000000000000000000',
# 'version': '1',
# },
# 'types': {
# 'Agent': [
# {'name': 'source', 'type': 'string'},
# {'name': 'connectionId', 'type': 'bytes32'},
# ],
# 'EIP712Domain': [
# {'name': 'name', 'type': 'string'},
# {'name': 'version', 'type': 'string'},
# {'name': 'chainId', 'type': 'uint256'},
# {'name': 'verifyingContract', 'type': 'address'},
# ],
# },
# 'primaryType': 'Agent',
# 'message': phantomAgent,
# }
zeroAddress = self.safe_string(self.options, 'zeroAddress')
chainId = 1337 # check self out
domain: dict = {
'chainId': chainId,
'name': 'Exchange',
'verifyingContract': zeroAddress,
'version': '1',
}
messageTypes: dict = {
'Agent': [
{'name': 'source', 'type': 'string'},
{'name': 'connectionId', 'type': 'bytes32'},
],
}
msg = self.eth_encode_structured_data(domain, messageTypes, phantomAgent)
signature = self.sign_message(msg, self.privateKey)
return signature
def sign_user_signed_action(self, messageTypes, message):
zeroAddress = self.safe_string(self.options, 'zeroAddress')
chainId = 421614 # check self out
domain: dict = {
'chainId': chainId,
'name': 'HyperliquidSignTransaction',
'verifyingContract': zeroAddress,
'version': '1',
}
msg = self.eth_encode_structured_data(domain, messageTypes, message)
signature = self.sign_message(msg, self.privateKey)
return signature
def build_usd_send_sig(self, message):
messageTypes: dict = {
'HyperliquidTransaction:UsdSend': [
{'name': 'hyperliquidChain', 'type': 'string'},
{'name': 'destination', 'type': 'string'},
{'name': 'amount', 'type': 'string'},
{'name': 'time', 'type': 'uint64'},
],
}
return self.sign_user_signed_action(messageTypes, message)
def build_usd_class_send_sig(self, message):
messageTypes: dict = {
'HyperliquidTransaction:UsdClassTransfer': [
{'name': 'hyperliquidChain', 'type': 'string'},
{'name': 'amount', 'type': 'string'},
{'name': 'toPerp', 'type': 'bool'},
{'name': 'nonce', 'type': 'uint64'},
],
}
return self.sign_user_signed_action(messageTypes, message)
def build_withdraw_sig(self, message):
messageTypes: dict = {
'HyperliquidTransaction:Withdraw': [
{'name': 'hyperliquidChain', 'type': 'string'},
{'name': 'destination', 'type': 'string'},
{'name': 'amount', 'type': 'string'},
{'name': 'time', 'type': 'uint64'},
],
}
return self.sign_user_signed_action(messageTypes, message)
def build_approve_builder_fee_sig(self, message):
messageTypes: dict = {
'HyperliquidTransaction:ApproveBuilderFee': [
{'name': 'hyperliquidChain', 'type': 'string'},
{'name': 'maxFeeRate', 'type': 'string'},
{'name': 'builder', 'type': 'address'},
{'name': 'nonce', 'type': 'uint64'},
],
}
return self.sign_user_signed_action(messageTypes, message)
async def set_ref(self):
if self.safe_bool(self.options, 'refSet', False):
return True
self.options['refSet'] = True
action = {
'type': 'setReferrer',
'code': self.safe_string(self.options, 'ref', 'CCXT1'),
}
nonce = self.milliseconds()
signature = self.sign_l1_action(action, nonce)
request: dict = {
'action': action,
'nonce': nonce,
'signature': signature,
}
response = None
try:
response = await self.privatePostExchange(request)
return response
except Exception as e:
response = None # ignore self
return response
async def approve_builder_fee(self, builder: str, maxFeeRate: str):
nonce = self.milliseconds()
isSandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
payload: dict = {
'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
'maxFeeRate': maxFeeRate,
'builder': builder,
'nonce': nonce,
}
sig = self.build_approve_builder_fee_sig(payload)
action = {
'hyperliquidChain': payload['hyperliquidChain'],
'signatureChainId': '0x66eee',
'maxFeeRate': payload['maxFeeRate'],
'builder': payload['builder'],
'nonce': nonce,
'type': 'approveBuilderFee',
}
request: dict = {
'action': action,
'nonce': nonce,
'signature': sig,
'vaultAddress': None,
}
#
# {
# "status": "ok",
# "response": {
# "type": "default"
# }
# }
#
return await self.privatePostExchange(request)
async def initialize_client(self):
try:
await asyncio.gather(*[self.handle_builder_fee_approval(), self.set_ref()])
except Exception as e:
return False
return True
async def handle_builder_fee_approval(self):
buildFee = self.safe_bool(self.options, 'builderFee', True)
if not buildFee:
return False # skip if builder fee is not enabled
approvedBuilderFee = self.safe_bool(self.options, 'approvedBuilderFee', False)
if approvedBuilderFee:
return True # skip if builder fee is already approved
try:
builder = self.safe_string(self.options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6')
maxFeeRate = self.safe_string(self.options, 'feeRate', '0.01%')
await self.approve_builder_fee(builder, maxFeeRate)
self.options['approvedBuilderFee'] = True
except Exception as e:
self.options['builderFee'] = False # disable builder fee if an error occurs
return True
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
create a trade order
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of currency you want to trade in units of base currency
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.timeInForce]: 'Gtc', 'Ioc', 'Alo'
:param bool [params.postOnly]: True or False whether the order is post-only
:param bool [params.reduceOnly]: True or False whether the order is reduce-only
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
:param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
:param str [params.slippage]: the slippage for market order
:param str [params.vaultAddress]: the vault address for order
:param str [params.subAccountAddress]: sub account user address
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
order, globalParams = self.parse_create_edit_order_args(None, symbol, type, side, amount, price, params)
orders = await self.create_orders([order], globalParams)
return orders[0]
async def create_orders(self, orders: List[OrderRequest], params={}):
"""
create a list of trade orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.initialize_client()
request = self.create_orders_request(orders, params)
response = await self.privatePostExchange(request)
#
# {
# "status": "ok",
# "response": {
# "type": "order",
# "data": {
# "statuses": [
# {
# "resting": {
# "oid": 5063830287
# }
# }
# ]
# }
# }
# }
#
responseObj = self.safe_dict(response, 'response', {})
data = self.safe_dict(responseObj, 'data', {})
statuses = self.safe_list(data, 'statuses', [])
ordersToBeParsed = []
for i in range(0, len(statuses)):
order = statuses[i]
if order == 'waitingForTrigger':
ordersToBeParsed.append({'status': order}) # tp/sl orders can return a string like "waitingForTrigger",
else:
ordersToBeParsed.append(order)
return self.parse_orders(ordersToBeParsed, None)
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: str, price: Str = None, params={}):
market = self.market(symbol)
type = type.upper()
side = side.upper()
isMarket = (type == 'MARKET')
isBuy = (side == 'BUY')
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_id')
slippage = self.safe_string(params, 'slippage')
defaultTimeInForce = 'ioc' if (isMarket) else 'gtc'
postOnly = self.safe_bool(params, 'postOnly', False)
if postOnly:
defaultTimeInForce = 'alo'
timeInForce = self.safe_string_lower(params, 'timeInForce', defaultTimeInForce)
timeInForce = self.capitalize(timeInForce)
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
stopLossPrice = self.safe_string(params, 'stopLossPrice', triggerPrice)
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
isTrigger = (stopLossPrice or takeProfitPrice)
px = None
if isMarket:
if price is None:
raise ArgumentsRequired(self.id + ' market orders require price to calculate the max slippage price. Default slippage can be set in options(default is 5%).')
px = Precise.string_mul(price, Precise.string_add('1', slippage)) if (isBuy) else Precise.string_mul(price, Precise.string_sub('1', slippage))
px = self.price_to_precision(symbol, px) # round after adding slippage
else:
px = self.price_to_precision(symbol, price)
sz = self.amount_to_precision(symbol, amount)
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
orderType: dict = {}
if isTrigger:
isTp = False
if takeProfitPrice is not None:
triggerPrice = self.price_to_precision(symbol, takeProfitPrice)
isTp = True
else:
triggerPrice = self.price_to_precision(symbol, stopLossPrice)
orderType['trigger'] = {
'isMarket': isMarket,
'triggerPx': triggerPrice,
'tpsl': 'tp' if (isTp) else 'sl',
}
else:
orderType['limit'] = {
'tif': timeInForce,
}
params = self.omit(params, ['clientOrderId', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce', 'client_id', 'reduceOnly', 'postOnly'])
orderObj: dict = {
'a': self.parse_to_int(market['baseId']),
'b': isBuy,
'p': px,
's': sz,
'r': reduceOnly,
't': orderType,
# 'c': clientOrderId,
}
if clientOrderId is not None:
orderObj['c'] = clientOrderId
return orderObj
def create_orders_request(self, orders, params={}) -> dict:
"""
create a list of trade orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.check_required_credentials()
defaultSlippage = self.safe_string(self.options, 'defaultSlippage')
defaultSlippage = self.safe_string(params, 'slippage', defaultSlippage)
hasClientOrderId = False
for i in range(0, len(orders)):
rawOrder = orders[i]
orderParams = self.safe_dict(rawOrder, 'params', {})
clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
if clientOrderId is not None:
hasClientOrderId = True
if hasClientOrderId:
for i in range(0, len(orders)):
rawOrder = orders[i]
orderParams = self.safe_dict(rawOrder, 'params', {})
clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
if clientOrderId is None:
raise ArgumentsRequired(self.id + ' createOrders() all orders must have clientOrderId if at least one has a clientOrderId')
params = self.omit(params, ['slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce'])
nonce = self.milliseconds()
orderReq = []
grouping = 'na'
for i in range(0, len(orders)):
rawOrder = orders[i]
marketId = self.safe_string(rawOrder, 'symbol')
market = self.market(marketId)
symbol = market['symbol']
type = self.safe_string_upper(rawOrder, 'type')
side = self.safe_string_upper(rawOrder, 'side')
amount = self.safe_string(rawOrder, 'amount')
price = self.safe_string(rawOrder, 'price')
orderParams = self.safe_dict(rawOrder, 'params', {})
slippage = self.safe_string(orderParams, 'slippage', defaultSlippage)
orderParams['slippage'] = slippage
stopLoss = self.safe_value(orderParams, 'stopLoss')
takeProfit = self.safe_value(orderParams, 'takeProfit')
isTrigger = (stopLoss or takeProfit)
orderParams = self.omit(orderParams, ['stopLoss', 'takeProfit'])
mainOrderObj: dict = self.create_order_request(symbol, type, side, amount, price, orderParams)
orderReq.append(mainOrderObj)
if isTrigger:
# grouping opposed orders for sl/tp
stopLossOrderTriggerPrice = self.safe_string_n(stopLoss, ['triggerPrice', 'stopPrice'])
stopLossOrderType = self.safe_string(stopLoss, 'type', 'limit')
stopLossOrderLimitPrice = self.safe_string_n(stopLoss, ['price', 'stopLossPrice'], stopLossOrderTriggerPrice)
takeProfitOrderTriggerPrice = self.safe_string_n(takeProfit, ['triggerPrice', 'stopPrice'])
takeProfitOrderType = self.safe_string(takeProfit, 'type', 'limit')
takeProfitOrderLimitPrice = self.safe_string_n(takeProfit, ['price', 'takeProfitPrice'], takeProfitOrderTriggerPrice)
grouping = 'normalTpsl'
orderParams = self.omit(orderParams, ['stopLoss', 'takeProfit'])
triggerOrderSide = ''
if side == 'BUY':
triggerOrderSide = 'sell'
else:
triggerOrderSide = 'buy'
if takeProfit is not None:
orderObj: dict = self.create_order_request(symbol, takeProfitOrderType, triggerOrderSide, amount, takeProfitOrderLimitPrice, self.extend(orderParams, {
'takeProfitPrice': takeProfitOrderTriggerPrice,
'reduceOnly': True,
}))
orderReq.append(orderObj)
if stopLoss is not None:
orderObj: dict = self.create_order_request(symbol, stopLossOrderType, triggerOrderSide, amount, stopLossOrderLimitPrice, self.extend(orderParams, {
'stopLossPrice': stopLossOrderTriggerPrice,
'reduceOnly': True,
}))
orderReq.append(orderObj)
vaultAddress = None
vaultAddress, params = self.handle_option_and_params(params, 'createOrder', 'vaultAddress')
vaultAddress = self.format_vault_address(vaultAddress)
orderAction: dict = {
'type': 'order',
'orders': orderReq,
'grouping': grouping,
}
if self.safe_bool(self.options, 'approvedBuilderFee', False):
wallet = self.safe_string_lower(self.options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6')
orderAction['builder'] = {'b': wallet, 'f': self.safe_integer(self.options, 'feeInt', 10)}
signature = self.sign_l1_action(orderAction, nonce, vaultAddress)
request: dict = {
'action': orderAction,
'nonce': nonce,
'signature': signature,
# 'vaultAddress': vaultAddress,
}
if vaultAddress is not None:
params = self.omit(params, 'vaultAddress')
request['vaultAddress'] = vaultAddress
return request
async def cancel_order(self, id: str, symbol: Str = None, params={}):
"""
cancels an open order
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
:param str id: order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
:param str [params.vaultAddress]: the vault address for order
:param str [params.subAccountAddress]: sub account user address
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
orders = await self.cancel_orders([id], symbol, params)
return self.safe_dict(orders, 0)
async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
"""
cancel multiple orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
:param str[] ids: order ids
:param str [symbol]: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param string|str[] [params.clientOrderId]: client order ids,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.check_required_credentials()
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
await self.load_markets()
await self.initialize_client()
request = self.cancel_orders_request(ids, symbol, params)
response = await self.privatePostExchange(request)
#
# {
# "status":"ok",
# "response":{
# "type":"cancel",
# "data":{
# "statuses":[
# "success"
# ]
# }
# }
# }
#
innerResponse = self.safe_dict(response, 'response')
data = self.safe_dict(innerResponse, 'data')
statuses = self.safe_list(data, 'statuses')
orders = []
for i in range(0, len(statuses)):
status = statuses[i]
orders.append(self.safe_order({
'info': status,
'status': status,
}))
return orders
def cancel_orders_request(self, ids: List[str], symbol: Str = None, params={}) -> dict:
"""
build the request payload for cancelling multiple orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
:param str[] ids: order ids
:param str symbol: unified market symbol
:param dict [params]:
:returns dict: the raw request object to be sent to the exchange
"""
market = self.market(symbol)
clientOrderId = self.safe_value_2(params, 'clientOrderId', 'client_id')
params = self.omit(params, ['clientOrderId', 'client_id'])
nonce = self.milliseconds()
request: dict = {
'nonce': nonce,
# 'vaultAddress': vaultAddress,
}
cancelReq = []
cancelAction: dict = {
'type': '',
'cancels': [],
}
baseId = self.parse_to_numeric(market['baseId'])
if clientOrderId is not None:
if not isinstance(clientOrderId, list):
clientOrderId = [clientOrderId]
cancelAction['type'] = 'cancelByCloid'
for i in range(0, len(clientOrderId)):
cancelReq.append({
'asset': baseId,
'cloid': clientOrderId[i],
})
else:
cancelAction['type'] = 'cancel'
for i in range(0, len(ids)):
cancelReq.append({
'a': baseId,
'o': self.parse_to_numeric(ids[i]),
})
cancelAction['cancels'] = cancelReq
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'cancelOrders', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
request['action'] = cancelAction
request['signature'] = signature
if vaultAddress is not None:
params = self.omit(params, 'vaultAddress')
request['vaultAddress'] = vaultAddress
return request
async def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
"""
cancel multiple orders for multiple symbols
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
:param CancellationRequest[] orders: each order should contain the parameters required by cancelOrder namely id and symbol, example [{"id": "a", "symbol": "BTC/USDT"}, {"id": "b", "symbol": "ETH/USDT"}]
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.check_required_credentials()
await self.load_markets()
await self.initialize_client()
nonce = self.milliseconds()
request: dict = {
'nonce': nonce,
# 'vaultAddress': vaultAddress,
}
cancelReq = []
cancelAction: dict = {
'type': '',
'cancels': [],
}
cancelByCloid = False
for i in range(0, len(orders)):
order = orders[i]
clientOrderId = self.safe_string(order, 'clientOrderId')
if clientOrderId is not None:
cancelByCloid = True
id = self.safe_string(order, 'id')
symbol = self.safe_string(order, 'symbol')
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrdersForSymbols() requires a symbol argument in each order')
if id is not None and cancelByCloid:
raise BadRequest(self.id + ' cancelOrdersForSymbols() all orders must have either id or clientOrderId')
assetKey = 'asset' if cancelByCloid else 'a'
idKey = 'cloid' if cancelByCloid else 'o'
market = self.market(symbol)
cancelObj: dict = {}
cancelObj[assetKey] = self.parse_to_numeric(market['baseId'])
cancelObj[idKey] = clientOrderId if cancelByCloid else self.parse_to_numeric(id)
cancelReq.append(cancelObj)
cancelAction['type'] = 'cancelByCloid' if cancelByCloid else 'cancel'
cancelAction['cancels'] = cancelReq
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'cancelOrdersForSymbols', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
request['action'] = cancelAction
request['signature'] = signature
if vaultAddress is not None:
params = self.omit(params, 'vaultAddress')
request['vaultAddress'] = vaultAddress
response = await self.privatePostExchange(request)
#
# {
# "status":"ok",
# "response":{
# "type":"cancel",
# "data":{
# "statuses":[
# "success"
# ]
# }
# }
# }
#
return [self.safe_order({'info': response})]
async def cancel_all_orders_after(self, timeout: Int, params={}):
"""
dead man's switch, cancel all orders after the given timeout
:param number timeout: time in milliseconds, 0 represents cancel the timer
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: the api result
"""
self.check_required_credentials()
await self.load_markets()
await self.initialize_client()
params = self.omit(params, ['clientOrderId', 'client_id'])
nonce = self.milliseconds()
request: dict = {
'nonce': nonce,
# 'vaultAddress': vaultAddress,
}
cancelAction: dict = {
'type': 'scheduleCancel',
'time': nonce + timeout,
}
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'cancelAllOrdersAfter', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
request['action'] = cancelAction
request['signature'] = signature
if vaultAddress is not None:
params = self.omit(params, 'vaultAddress')
request['vaultAddress'] = vaultAddress
response = await self.privatePostExchange(request)
#
# {
# "status":"err",
# "response":"Cannot set scheduled cancel time until enough volume traded. Required: $1000000. Traded: $373.47205."
# }
#
return response
def edit_orders_request(self, orders, params={}):
self.check_required_credentials()
hasClientOrderId = False
for i in range(0, len(orders)):
rawOrder = orders[i]
orderParams = self.safe_dict(rawOrder, 'params', {})
clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
if clientOrderId is not None:
hasClientOrderId = True
if hasClientOrderId:
for i in range(0, len(orders)):
rawOrder = orders[i]
orderParams = self.safe_dict(rawOrder, 'params', {})
clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
if clientOrderId is None:
raise ArgumentsRequired(self.id + ' editOrders() all orders must have clientOrderId if at least one has a clientOrderId')
params = self.omit(params, ['slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce'])
modifies = []
for i in range(0, len(orders)):
rawOrder = orders[i]
id = self.safe_string(rawOrder, 'id')
marketId = self.safe_string(rawOrder, 'symbol')
market = self.market(marketId)
symbol = market['symbol']
type = self.safe_string_upper(rawOrder, 'type')
isMarket = (type == 'MARKET')
side = self.safe_string_upper(rawOrder, 'side')
isBuy = (side == 'BUY')
amount = self.safe_string(rawOrder, 'amount')
price = self.safe_string(rawOrder, 'price')
orderParams = self.safe_dict(rawOrder, 'params', {})
defaultSlippage = self.safe_string(self.options, 'defaultSlippage')
slippage = self.safe_string(orderParams, 'slippage', defaultSlippage)
defaultTimeInForce = 'ioc' if (isMarket) else 'gtc'
postOnly = self.safe_bool(orderParams, 'postOnly', False)
if postOnly:
defaultTimeInForce = 'alo'
timeInForce = self.safe_string_lower(orderParams, 'timeInForce', defaultTimeInForce)
timeInForce = self.capitalize(timeInForce)
clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
triggerPrice = self.safe_string_2(orderParams, 'triggerPrice', 'stopPrice')
stopLossPrice = self.safe_string(orderParams, 'stopLossPrice', triggerPrice)
takeProfitPrice = self.safe_string(orderParams, 'takeProfitPrice')
isTrigger = (stopLossPrice or takeProfitPrice)
reduceOnly = self.safe_bool(orderParams, 'reduceOnly', False)
orderParams = self.omit(orderParams, ['slippage', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'clientOrderId', 'client_id', 'postOnly', 'reduceOnly'])
px = self.number_to_string(price)
if isMarket:
px = Precise.string_mul(px, Precise.string_add('1', slippage)) if (isBuy) else Precise.string_mul(px, Precise.string_sub('1', slippage))
px = self.price_to_precision(symbol, px)
else:
px = self.price_to_precision(symbol, px)
sz = self.amount_to_precision(symbol, amount)
orderType: dict = {}
if isTrigger:
isTp = False
if takeProfitPrice is not None:
triggerPrice = self.price_to_precision(symbol, takeProfitPrice)
isTp = True
else:
triggerPrice = self.price_to_precision(symbol, stopLossPrice)
orderType['trigger'] = {
'isMarket': isMarket,
'triggerPx': triggerPrice,
'tpsl': 'tp' if (isTp) else 'sl',
}
else:
orderType['limit'] = {
'tif': timeInForce,
}
if triggerPrice is None:
triggerPrice = '0'
orderReq: dict = {
'a': self.parse_to_int(market['baseId']),
'b': isBuy,
'p': px,
's': sz,
'r': reduceOnly,
't': orderType,
# 'c': clientOrderId,
}
if clientOrderId is not None:
orderReq['c'] = clientOrderId
modifyReq: dict = {
'oid': self.parse_to_int(id),
'order': orderReq,
}
modifies.append(modifyReq)
nonce = self.milliseconds()
modifyAction: dict = {
'type': 'batchModify',
'modifies': modifies,
}
vaultAddress = None
vaultAddress, params = self.handle_option_and_params(params, 'editOrder', 'vaultAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(modifyAction, nonce, vaultAddress)
request: dict = {
'action': modifyAction,
'nonce': nonce,
'signature': signature,
# 'vaultAddress': vaultAddress,
}
if vaultAddress is not None:
request['vaultAddress'] = vaultAddress
return request
async def edit_order(self, id: str, symbol: str, type: str, side: str, amount: Num = None, price: Num = None, params={}):
"""
edit a trade order
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-orders
:param str id: cancel order id
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of currency you want to trade in units of base currency
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.timeInForce]: 'Gtc', 'Ioc', 'Alo'
:param bool [params.postOnly]: True or False whether the order is post-only
:param bool [params.reduceOnly]: True or False whether the order is reduce-only
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
:param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
:param str [params.vaultAddress]: the vault address for order
:param str [params.subAccountAddress]: sub account user address
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
if id is None:
raise ArgumentsRequired(self.id + ' editOrder() requires an id argument')
order, globalParams = self.parse_create_edit_order_args(id, symbol, type, side, amount, price, params)
orders = await self.edit_orders([order], globalParams)
return orders[0]
async def edit_orders(self, orders: List[OrderRequest], params={}):
"""
edit a list of trade orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-orders
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
await self.initialize_client()
request = self.edit_orders_request(orders, params)
response = await self.privatePostExchange(request)
#
# {
# "status": "ok",
# "response": {
# "type": "order",
# "data": {
# "statuses": [
# {
# "resting": {
# "oid": 5063830287
# }
# }
# ]
# }
# }
# }
# when the order is filled immediately
# {
# "status":"ok",
# "response":{
# "type":"order",
# "data":{
# "statuses":[
# {
# "filled":{
# "totalSz":"0.1",
# "avgPx":"100.84",
# "oid":6195281425
# }
# }
# ]
# }
# }
# }
#
responseObject = self.safe_dict(response, 'response', {})
dataObject = self.safe_dict(responseObject, 'data', {})
statuses = self.safe_list(dataObject, 'statuses', [])
return self.parse_orders(statuses)
async def create_vault(self, name: str, description: str, initialUsd: int, params={}):
"""
creates a value
:param str name: The name of the vault
:param str description: The description of the vault
:param number initialUsd: The initialUsd of the vault
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: the api result
"""
self.check_required_credentials()
await self.load_markets()
nonce = self.milliseconds()
request: dict = {
'nonce': nonce,
}
usd = self.parse_to_int(Precise.string_mul(self.number_to_string(initialUsd), '1000000'))
action: dict = {
'type': 'createVault',
'name': name,
'description': description,
'initialUsd': usd,
'nonce': nonce,
}
signature = self.sign_l1_action(action, nonce)
request['action'] = action
request['signature'] = signature
response = await self.privatePostExchange(self.extend(request, params))
#
# {
# "status": "ok",
# "response": {
# "type": "createVault",
# "data": "0x04fddcbc9ce80219301bd16f18491bedf2a8c2b8"
# }
# }
#
return response
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical funding rate prices
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-historical-funding-rates
:param str symbol: unified symbol of the market to fetch the funding rate history for
:param int [since]: timestamp in ms of the earliest funding rate to fetch
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest funding rate
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
await self.load_markets()
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
market = self.market(symbol)
request: dict = {
'type': 'fundingHistory',
'coin': market['baseName'],
}
if since is not None:
request['startTime'] = since
else:
maxLimit = 500 if (limit is None) else limit
request['startTime'] = self.milliseconds() - maxLimit * 60 * 60 * 1000
until = self.safe_integer(params, 'until')
params = self.omit(params, 'until')
if until is not None:
request['endTime'] = until
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "coin": "ETH",
# "fundingRate": "0.0000125",
# "premium": "0.00057962",
# "time": 1704290400031
# }
# ]
#
result = []
for i in range(0, len(response)):
entry = response[i]
timestamp = self.safe_integer(entry, 'time')
result.append({
'info': entry,
'symbol': self.safe_symbol(None, market),
'fundingRate': self.safe_number(entry, 'fundingRate'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
})
sorted = self.sort_by(result, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently open orders
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-open-orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.method]: 'openOrders' or 'frontendOpenOrders' default is 'frontendOpenOrders'
:param str [params.subAccountAddress]: sub account user address
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchOpenOrders', params)
method = None
method, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'method', 'frontendOpenOrders')
await self.load_markets()
market = self.safe_market(symbol)
request: dict = {
'type': method,
'user': userAddress,
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "coin": "ETH",
# "limitPx": "2000.0",
# "oid": 3991946565,
# "origSz": "0.1",
# "side": "B",
# "sz": "0.1",
# "timestamp": 1704346468838
# }
# ]
#
orderWithStatus = []
for i in range(0, len(response)):
order = response[i]
extendOrder = {}
if self.safe_string(order, 'status') is None:
extendOrder['ccxtStatus'] = 'open'
orderWithStatus.append(self.extend(order, extendOrder))
return self.parse_orders(orderWithStatus, market, since, limit)
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently closed orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
orders = await self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['closed'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
async def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all canceled orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
orders = await self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['canceled'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
async def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all closed and canceled orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
orders = await self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders
closedOrders = self.filter_by_array(orders, 'status', ['canceled', 'closed', 'rejected'], False)
return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)
async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all orders
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open orders structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.subAccountAddress]: sub account user address
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchOrders', params)
await self.load_markets()
market = self.safe_market(symbol)
request: dict = {
'type': 'historicalOrders',
'user': userAddress,
}
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "coin": "ETH",
# "limitPx": "2000.0",
# "oid": 3991946565,
# "origSz": "0.1",
# "side": "B",
# "sz": "0.1",
# "timestamp": 1704346468838
# }
# ]
#
return self.parse_orders(response, market, since, limit)
async def fetch_order(self, id: str, symbol: Str = None, params={}):
"""
fetches information on an order made by the user
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#query-order-status-by-oid-or-cloid
:param str id: order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.subAccountAddress]: sub account user address
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchOrder', params)
await self.load_markets()
market = self.safe_market(symbol)
clientOrderId = self.safe_string(params, 'clientOrderId')
request: dict = {
'type': 'orderStatus',
# 'oid': id if isClientOrderId else self.parse_to_numeric(id),
'user': userAddress,
}
if clientOrderId is not None:
params = self.omit(params, 'clientOrderId')
request['oid'] = clientOrderId
else:
isClientOrderId = len(id) >= 34
request['oid'] = id if isClientOrderId else self.parse_to_numeric(id)
response = await self.publicPostInfo(self.extend(request, params))
#
# {
# "order": {
# "order": {
# "children": [],
# "cloid": null,
# "coin": "ETH",
# "isPositionTpsl": False,
# "isTrigger": False,
# "limitPx": "2000.0",
# "oid": "3991946565",
# "orderType": "Limit",
# "origSz": "0.1",
# "reduceOnly": False,
# "side": "B",
# "sz": "0.1",
# "tif": "Gtc",
# "timestamp": "1704346468838",
# "triggerCondition": "N/A",
# "triggerPx": "0.0"
# },
# "status": "open",
# "statusTimestamp": "1704346468838"
# },
# "status": "order"
# }
#
data = self.safe_dict(response, 'order')
return self.parse_order(data, market)
def parse_order(self, order: dict, market: Market = None) -> Order:
#
# createOrdersWs error
#
# {error: 'Insufficient margin to place order. asset=159'}
#
# fetchOpenOrders
#
# {
# "coin": "ETH",
# "limitPx": "2000.0",
# "oid": 3991946565,
# "origSz": "0.1",
# "side": "B",
# "sz": "0.1",
# "timestamp": 1704346468838
# }
# fetchClosedorders
# {
# "cloid": null,
# "closedPnl": "0.0",
# "coin": "SOL",
# "crossed": True,
# "dir": "Open Long",
# "fee": "0.003879",
# "hash": "0x4a2647998682b7f07bc5040ab531e1011400f9a51bfa0346a0b41ebe510e8875",
# "liquidationMarkPx": null,
# "oid": "6463280784",
# "px": "110.83",
# "side": "B",
# "startPosition": "1.64",
# "sz": "0.1",
# "tid": "232174667018988",
# "time": "1709142268394"
# }
#
# fetchOrder
#
# {
# "order": {
# "children": [],
# "cloid": null,
# "coin": "ETH",
# "isPositionTpsl": False,
# "isTrigger": False,
# "limitPx": "2000.0",
# "oid": "3991946565",
# "orderType": "Limit",
# "origSz": "0.1",
# "reduceOnly": False,
# "side": "B",
# "sz": "0.1",
# "tif": "Gtc",
# "timestamp": "1704346468838",
# "triggerCondition": "N/A",
# "triggerPx": "0.0"
# },
# "status": "open",
# "statusTimestamp": "1704346468838"
# }
#
# createOrder
#
# {
# "resting": {
# "oid": 5063830287
# }
# }
#
# {
# "filled":{
# "totalSz":"0.1",
# "avgPx":"100.84",
# "oid":6195281425
# }
# }
# frontendOrder
# {
# "children": [],
# "cloid": null,
# "coin": "BLUR",
# "isPositionTpsl": False,
# "isTrigger": True,
# "limitPx": "0.5",
# "oid": 8670487141,
# "orderType": "Stop Limit",
# "origSz": "20.0",
# "reduceOnly": False,
# "side": "B",
# "sz": "20.0",
# "tif": null,
# "timestamp": 1715523663687,
# "triggerCondition": "Price above 0.6",
# "triggerPx": "0.6"
# }
#
error = self.safe_string(order, 'error')
if error is not None:
return self.safe_order({
'info': order,
'status': 'rejected',
})
entry = self.safe_dict_n(order, ['order', 'resting', 'filled'])
if entry is None:
entry = order
coin = self.safe_string(entry, 'coin')
marketId = None
if coin is not None:
marketId = self.coin_to_market_id(coin)
if self.safe_string(entry, 'id') is None:
market = self.safe_market(marketId, None)
else:
market = self.safe_market(marketId, market)
symbol = market['symbol']
timestamp = self.safe_integer(entry, 'timestamp')
status = self.safe_string_2(order, 'status', 'ccxtStatus')
order = self.omit(order, ['ccxtStatus'])
side = self.safe_string(entry, 'side')
if side is not None:
side = 'sell' if (side == 'A') else 'buy'
totalAmount = self.safe_string_2(entry, 'origSz', 'totalSz')
remaining = self.safe_string(entry, 'sz')
tif = self.safe_string_upper(entry, 'tif')
postOnly = None
if tif is not None:
postOnly = (tif == 'ALO')
return self.safe_order({
'info': order,
'id': self.safe_string(entry, 'oid'),
'clientOrderId': self.safe_string(entry, 'cloid'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': None,
'lastUpdateTimestamp': self.safe_integer(order, 'statusTimestamp'),
'symbol': symbol,
'type': self.parse_order_type(self.safe_string_lower(entry, 'orderType')),
'timeInForce': tif,
'postOnly': postOnly,
'reduceOnly': self.safe_bool(entry, 'reduceOnly'),
'side': side,
'price': self.safe_string(entry, 'limitPx'),
'triggerPrice': self.safe_number(entry, 'triggerPx') if self.safe_bool(entry, 'isTrigger') else None,
'amount': totalAmount,
'cost': None,
'average': self.safe_string(entry, 'avgPx'),
'filled': Precise.string_sub(totalAmount, remaining),
'remaining': remaining,
'status': self.parse_order_status(status),
'fee': None,
'trades': None,
}, market)
def parse_order_status(self, status: Str):
if status is None:
return None
statuses: dict = {
'triggered': 'open',
'filled': 'closed',
'open': 'open',
'canceled': 'canceled',
'rejected': 'rejected',
'marginCanceled': 'canceled',
}
if status.endswith('Rejected'):
return 'rejected'
if status.endswith('Canceled'):
return 'canceled'
return self.safe_string(statuses, status, status)
def parse_order_type(self, status):
statuses: dict = {
'stop limit': 'limit',
'stop market': 'market',
}
return self.safe_string(statuses, status, status)
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all trades made by the user
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trades structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest trade
:param str [params.subAccountAddress]: sub account user address
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
userAddress = None
userAddress, params = self.handle_public_address('fetchMyTrades', params)
await self.load_markets()
market = self.safe_market(symbol)
request: dict = {
'user': userAddress,
}
if since is not None:
request['type'] = 'userFillsByTime'
request['startTime'] = since
else:
request['type'] = 'userFills'
until = self.safe_integer(params, 'until')
params = self.omit(params, 'until')
if until is not None:
request['endTime'] = until
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "closedPnl": "0.19343",
# "coin": "ETH",
# "crossed": True,
# "dir": "Close Long",
# "fee": "0.050062",
# "feeToken": "USDC",
# "hash": "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
# "liquidationMarkPx": null,
# "oid": 3929354691,
# "px": "2381.1",
# "side": "A",
# "startPosition": "0.0841",
# "sz": "0.0841",
# "tid": 128423918764978,
# "time": 1704262888911
# }
# ]
#
return self.parse_trades(response, market, since, limit)
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
#
# {
# "closedPnl": "0.19343",
# "coin": "ETH",
# "crossed": True,
# "dir": "Close Long",
# "fee": "0.050062",
# "hash": "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
# "liquidationMarkPx": null,
# "oid": 3929354691,
# "px": "2381.1",
# "side": "A",
# "startPosition": "0.0841",
# "sz": "0.0841",
# "tid": 128423918764978,
# "time": 1704262888911
# }
#
timestamp = self.safe_integer(trade, 'time')
price = self.safe_string(trade, 'px')
amount = self.safe_string(trade, 'sz')
coin = self.safe_string(trade, 'coin')
marketId = self.coin_to_market_id(coin)
market = self.safe_market(marketId, None)
symbol = market['symbol']
id = self.safe_string(trade, 'tid')
side = self.safe_string(trade, 'side')
if side is not None:
side = 'sell' if (side == 'A') else 'buy'
fee = self.safe_string(trade, 'fee')
takerOrMaker = None
crossed = self.safe_bool(trade, 'crossed')
if crossed is not None:
takerOrMaker = 'taker' if crossed else 'maker'
return self.safe_trade({
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': self.safe_string(trade, 'oid'),
'type': None,
'side': side,
'takerOrMaker': takerOrMaker,
'price': price,
'amount': amount,
'cost': None,
'fee': {
'cost': fee,
'currency': self.safe_string(trade, 'feeToken'),
'rate': None,
},
}, market)
async def fetch_position(self, symbol: str, params={}):
"""
fetch data on an open position
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
:param str symbol: unified market symbol of the market the position is held in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
positions = await self.fetch_positions([symbol], params)
return self.safe_dict(positions, 0, {})
async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
"""
fetch all open positions
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
:param str[] [symbols]: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.subAccountAddress]: sub account user address
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
await self.load_markets()
userAddress = None
userAddress, params = self.handle_public_address('fetchPositions', params)
symbols = self.market_symbols(symbols)
request: dict = {
'type': 'clearinghouseState',
'user': userAddress,
}
response = await self.publicPostInfo(self.extend(request, params))
#
# {
# "assetPositions": [
# {
# "position": {
# "coin": "ETH",
# "cumFunding": {
# "allTime": "0.0",
# "sinceChange": "0.0",
# "sinceOpen": "0.0"
# },
# "entryPx": "2213.9",
# "leverage": {
# "rawUsd": "-475.23904",
# "type": "isolated",
# "value": "20"
# },
# "liquidationPx": "2125.00856238",
# "marginUsed": "24.88097",
# "maxLeverage": "50",
# "positionValue": "500.12001",
# "returnOnEquity": "0.0",
# "szi": "0.2259",
# "unrealizedPnl": "0.0"
# },
# "type": "oneWay"
# }
# ],
# "crossMaintenanceMarginUsed": "0.0",
# "crossMarginSummary": {
# "accountValue": "100.0",
# "totalMarginUsed": "0.0",
# "totalNtlPos": "0.0",
# "totalRawUsd": "100.0"
# },
# "marginSummary": {
# "accountValue": "100.0",
# "totalMarginUsed": "0.0",
# "totalNtlPos": "0.0",
# "totalRawUsd": "100.0"
# },
# "time": "1704261007014",
# "withdrawable": "100.0"
# }
#
data = self.safe_list(response, 'assetPositions', [])
result = []
for i in range(0, len(data)):
result.append(self.parse_position(data[i], None))
return self.filter_by_array_positions(result, 'symbol', symbols, False)
def parse_position(self, position: dict, market: Market = None):
#
# {
# "position": {
# "coin": "ETH",
# "cumFunding": {
# "allTime": "0.0",
# "sinceChange": "0.0",
# "sinceOpen": "0.0"
# },
# "entryPx": "2213.9",
# "leverage": {
# "rawUsd": "-475.23904",
# "type": "isolated",
# "value": "20"
# },
# "liquidationPx": "2125.00856238",
# "marginUsed": "24.88097",
# "maxLeverage": "50",
# "positionValue": "500.12001",
# "returnOnEquity": "0.0",
# "szi": "0.2259",
# "unrealizedPnl": "0.0"
# },
# "type": "oneWay"
# }
#
entry = self.safe_dict(position, 'position', {})
coin = self.safe_string(entry, 'coin')
marketId = self.coin_to_market_id(coin)
market = self.safe_market(marketId, None)
symbol = market['symbol']
leverage = self.safe_dict(entry, 'leverage', {})
marginMode = self.safe_string(leverage, 'type')
isIsolated = (marginMode == 'isolated')
rawSize = self.safe_string(entry, 'szi')
size = rawSize
side = None
if size is not None:
side = 'long' if Precise.string_gt(rawSize, '0') else 'short'
size = Precise.string_abs(size)
rawUnrealizedPnl = self.safe_string(entry, 'unrealizedPnl')
absRawUnrealizedPnl = Precise.string_abs(rawUnrealizedPnl)
marginUsed = self.safe_string(entry, 'marginUsed')
initialMargin = None
if isIsolated:
initialMargin = Precise.string_sub(marginUsed, rawUnrealizedPnl)
else:
initialMargin = marginUsed
percentage = Precise.string_mul(Precise.string_div(absRawUnrealizedPnl, marginUsed), '100')
return self.safe_position({
'info': position,
'id': None,
'symbol': symbol,
'timestamp': None,
'datetime': None,
'isolated': isIsolated,
'hedged': None,
'side': side,
'contracts': self.parse_number(size),
'contractSize': None,
'entryPrice': self.safe_number(entry, 'entryPx'),
'markPrice': None,
'notional': self.safe_number(entry, 'positionValue'),
'leverage': self.safe_number(leverage, 'value'),
'collateral': self.parse_number(marginUsed),
'initialMargin': self.parse_number(initialMargin),
'maintenanceMargin': None,
'initialMarginPercentage': None,
'maintenanceMarginPercentage': None,
'unrealizedPnl': self.parse_number(rawUnrealizedPnl),
'liquidationPrice': self.safe_number(entry, 'liquidationPx'),
'marginMode': marginMode,
'percentage': self.parse_number(percentage),
})
async def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
"""
set margin mode(symbol)
:param str marginMode: margin mode must be either [isolated, cross]
:param str symbol: unified market symbol of the market the position is held in, default is None
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.leverage]: the rate of leverage, is required if setting trade mode(symbol)
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
leverage = self.safe_integer(params, 'leverage')
if leverage is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a leverage parameter')
asset = self.parse_to_int(market['baseId'])
isCross = (marginMode == 'cross')
nonce = self.milliseconds()
params = self.omit(params, ['leverage'])
updateAction: dict = {
'type': 'updateLeverage',
'asset': asset,
'isCross': isCross,
'leverage': leverage,
}
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'setMarginMode', 'vaultAddress', 'subAccountAddress')
if vaultAddress is not None:
if vaultAddress.startswith('0x'):
vaultAddress = vaultAddress.replace('0x', '')
signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
request: dict = {
'action': updateAction,
'nonce': nonce,
'signature': signature,
# 'vaultAddress': vaultAddress,
}
if vaultAddress is not None:
request['vaultAddress'] = vaultAddress
response = await self.privatePostExchange(request)
#
# {
# 'response': {
# 'type': 'default'
# },
# 'status': 'ok'
# }
#
return response
async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
"""
set the level of leverage for a market
:param float leverage: the rate of leverage
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.marginMode]: margin mode must be either [isolated, cross], default is cross
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
marginMode = self.safe_string(params, 'marginMode', 'cross')
isCross = (marginMode == 'cross')
asset = self.parse_to_int(market['baseId'])
nonce = self.milliseconds()
params = self.omit(params, 'marginMode')
updateAction: dict = {
'type': 'updateLeverage',
'asset': asset,
'isCross': isCross,
'leverage': leverage,
}
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'setLeverage', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
request: dict = {
'action': updateAction,
'nonce': nonce,
'signature': signature,
# 'vaultAddress': vaultAddress,
}
if vaultAddress is not None:
params = self.omit(params, 'vaultAddress')
request['vaultAddress'] = vaultAddress
response = await self.privatePostExchange(request)
#
# {
# 'response': {
# 'type': 'default'
# },
# 'status': 'ok'
# }
#
return response
async def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
add margin
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin
:param str symbol: unified market symbol
:param float amount: amount of margin to add
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
"""
return await self.modify_margin_helper(symbol, amount, 'add', params)
async def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin
remove margin from a position
:param str symbol: unified market symbol
:param float amount: the amount of margin to remove
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.vaultAddress]: the vault address
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
"""
return await self.modify_margin_helper(symbol, amount, 'reduce', params)
async def modify_margin_helper(self, symbol: str, amount, type, params={}) -> MarginModification:
await self.load_markets()
market = self.market(symbol)
asset = self.parse_to_int(market['baseId'])
sz = self.parse_to_int(Precise.string_mul(self.amount_to_precision(symbol, amount), '1000000'))
if type == 'reduce':
sz = -sz
nonce = self.milliseconds()
updateAction: dict = {
'type': 'updateIsolatedMargin',
'asset': asset,
'isBuy': True,
'ntli': sz,
}
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'modifyMargin', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
request: dict = {
'action': updateAction,
'nonce': nonce,
'signature': signature,
# 'vaultAddress': vaultAddress,
}
if vaultAddress is not None:
request['vaultAddress'] = vaultAddress
response = await self.privatePostExchange(request)
#
# {
# 'response': {
# 'type': 'default'
# },
# 'status': 'ok'
# }
#
return self.extend(self.parse_margin_modification(response, market), {
'code': self.safe_string(response, 'status'),
})
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
#
# {
# 'type': 'default'
# }
#
return {
'info': data,
'symbol': self.safe_symbol(None, market),
'type': None,
'marginMode': 'isolated',
'amount': None,
'total': None,
'code': self.safe_string(market, 'settle'),
'status': None,
'timestamp': None,
'datetime': None,
}
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
"""
transfer currency internally between wallets on the same account
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#l1-usdc-transfer
:param str code: unified currency code
:param float amount: amount to transfer
:param str fromAccount: account to transfer from *spot, swap*
:param str toAccount: account to transfer to *swap, spot or address*
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.vaultAddress]: the vault address for order
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
"""
self.check_required_credentials()
await self.load_markets()
isSandboxMode = self.safe_bool(self.options, 'sandboxMode')
nonce = self.milliseconds()
if self.in_array(fromAccount, ['spot', 'swap', 'perp']):
# handle swap <> spot account transfer
if not self.in_array(toAccount, ['spot', 'swap', 'perp']):
raise NotSupported(self.id + ' transfer() only support spot <> swap transfer')
strAmount = self.number_to_string(amount)
vaultAddress = self.safe_string_2(params, 'vaultAddress', 'subAccountAddress')
if vaultAddress is not None:
vaultAddress = self.format_vault_address(vaultAddress)
strAmount = strAmount + ' subaccount:' + vaultAddress
toPerp = (toAccount == 'perp') or (toAccount == 'swap')
transferPayload: dict = {
'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
'amount': strAmount,
'toPerp': toPerp,
'nonce': nonce,
}
transferSig = self.build_usd_class_send_sig(transferPayload)
transferRequest: dict = {
'action': {
'hyperliquidChain': transferPayload['hyperliquidChain'],
'signatureChainId': '0x66eee',
'type': 'usdClassTransfer',
'amount': strAmount,
'toPerp': toPerp,
'nonce': nonce,
},
'nonce': nonce,
'signature': transferSig,
}
transferResponse = await self.privatePostExchange(transferRequest)
return transferResponse
# transfer between main account and subaccount
isDeposit = False
subAccountAddress = None
if fromAccount == 'main':
subAccountAddress = toAccount
isDeposit = True
elif toAccount == 'main':
subAccountAddress = fromAccount
else:
raise NotSupported(self.id + ' transfer() only support main <> subaccount transfer')
self.check_address(subAccountAddress)
if code is None or code.upper() == 'USDC':
# Transfer USDC with subAccountTransfer
usd = self.parse_to_int(Precise.string_mul(self.number_to_string(amount), '1000000'))
action = {
'type': 'subAccountTransfer',
'subAccountUser': subAccountAddress,
'isDeposit': isDeposit,
'usd': usd,
}
sig = self.sign_l1_action(action, nonce)
request: dict = {
'action': action,
'nonce': nonce,
'signature': sig,
}
response = await self.privatePostExchange(request)
#
# {'response': {'type': 'default'}, 'status': 'ok'}
#
return self.parse_transfer(response)
else:
# Transfer non-USDC with subAccountSpotTransfer
symbol = self.symbol(code)
action = {
'type': 'subAccountSpotTransfer',
'subAccountUser': subAccountAddress,
'isDeposit': isDeposit,
'token': symbol,
'amount': self.number_to_string(amount),
}
sig = self.sign_l1_action(action, nonce)
request: dict = {
'action': action,
'nonce': nonce,
'signature': sig,
}
response = await self.privatePostExchange(request)
return self.parse_transfer(response)
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
#
# {'response': {'type': 'default'}, 'status': 'ok'}
#
return {
'info': transfer,
'id': None,
'timestamp': None,
'datetime': None,
'currency': None,
'amount': None,
'fromAccount': None,
'toAccount': None,
'status': 'ok',
}
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
"""
make a withdrawal(only support USDC)
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#initiate-a-withdrawal-request
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#deposit-or-withdraw-from-a-vault
: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.vaultAddress]: vault address withdraw from
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
self.check_required_credentials()
await self.load_markets()
self.check_address(address)
if code is not None:
code = code.upper()
if code != 'USDC':
raise NotSupported(self.id + ' withdraw() only support USDC')
vaultAddress = None
vaultAddress, params = self.handle_option_and_params(params, 'withdraw', 'vaultAddress')
vaultAddress = self.format_vault_address(vaultAddress)
params = self.omit(params, 'vaultAddress')
nonce = self.milliseconds()
action: dict = {}
sig = None
if vaultAddress is not None:
action = {
'type': 'vaultTransfer',
'vaultAddress': '0x' + vaultAddress,
'isDeposit': False,
'usd': amount,
}
sig = self.sign_l1_action(action, nonce)
else:
isSandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
payload: dict = {
'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
'destination': address,
'amount': str(amount),
'time': nonce,
}
sig = self.build_withdraw_sig(payload)
action = {
'hyperliquidChain': payload['hyperliquidChain'],
'signatureChainId': '0x66eee', # check self out
'destination': address,
'amount': str(amount),
'time': nonce,
'type': 'withdraw3',
}
request: dict = {
'action': action,
'nonce': nonce,
'signature': sig,
}
response = await self.privatePostExchange(request)
return self.parse_transaction(response)
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
#
# {status: 'ok', response: {type: 'default'}}
#
# fetchDeposits / fetchWithdrawals
# {
# "time":1724762307531,
# "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
# "delta":{
# "type":"accountClassTransfer",
# "usdc":"50.0",
# "toPerp":false
# }
# }
#
timestamp = self.safe_integer(transaction, 'time')
delta = self.safe_dict(transaction, 'delta', {})
fee = None
feeCost = self.safe_integer(delta, 'fee')
if feeCost is not None:
fee = {
'currency': 'USDC',
'cost': feeCost,
}
internal = None
type = self.safe_string(delta, 'type')
if type is not None:
internal = (type == 'internalTransfer')
return {
'info': transaction,
'id': None,
'txid': self.safe_string(transaction, 'hash'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'network': None,
'address': None,
'addressTo': self.safe_string(delta, 'destination'),
'addressFrom': self.safe_string(delta, 'user'),
'tag': None,
'tagTo': None,
'tagFrom': None,
'type': None,
'amount': self.safe_number(delta, 'usdc'),
'currency': None,
'status': self.safe_string(transaction, 'status'),
'updated': None,
'comment': None,
'internal': internal,
'fee': fee,
}
async def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
"""
fetch the trading fees for a market
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.user]: user address, will default to self.walletAddress if not provided
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
"""
await self.load_markets()
userAddress = None
userAddress, params = self.handle_public_address('fetchTradingFee', params)
market = self.market(symbol)
request: dict = {
'type': 'userFees',
'user': userAddress,
}
response = await self.publicPostInfo(self.extend(request, params))
#
# {
# "dailyUserVlm": [
# {
# "date": "2024-07-08",
# "userCross": "0.0",
# "userAdd": "0.0",
# "exchange": "90597185.23639999"
# }
# ],
# "feeSchedule": {
# "cross": "0.00035",
# "add": "0.0001",
# "tiers": {
# "vip": [
# {
# "ntlCutoff": "5000000.0",
# "cross": "0.0003",
# "add": "0.00005"
# }
# ],
# "mm": [
# {
# "makerFractionCutoff": "0.005",
# "add": "-0.00001"
# }
# ]
# },
# "referralDiscount": "0.04"
# },
# "userCrossRate": "0.00035",
# "userAddRate": "0.0001",
# "activeReferralDiscount": "0.0"
# }
#
data: dict = {
'userCrossRate': self.safe_string(response, 'userCrossRate'),
'userAddRate': self.safe_string(response, 'userAddRate'),
}
return self.parse_trading_fee(data, market)
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
#
# {
# "dailyUserVlm": [
# {
# "date": "2024-07-08",
# "userCross": "0.0",
# "userAdd": "0.0",
# "exchange": "90597185.23639999"
# }
# ],
# "feeSchedule": {
# "cross": "0.00035",
# "add": "0.0001",
# "tiers": {
# "vip": [
# {
# "ntlCutoff": "5000000.0",
# "cross": "0.0003",
# "add": "0.00005"
# }
# ],
# "mm": [
# {
# "makerFractionCutoff": "0.005",
# "add": "-0.00001"
# }
# ]
# },
# "referralDiscount": "0.04"
# },
# "userCrossRate": "0.00035",
# "userAddRate": "0.0001",
# "activeReferralDiscount": "0.0"
# }
#
symbol = self.safe_symbol(None, market)
return {
'info': fee,
'symbol': symbol,
'maker': self.safe_number(fee, 'userAddRate'),
'taker': self.safe_number(fee, 'userCrossRate'),
'percentage': None,
'tierBased': None,
}
async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
"""
fetch the history of changes, actions done by the user or operations that altered the balance of the user
:param str [code]: unified currency code
:param int [since]: timestamp in ms of the earliest ledger entry
:param int [limit]: max number of ledger entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: timestamp in ms of the latest ledger entry
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
"""
await self.load_markets()
userAddress = None
userAddress, params = self.handle_public_address('fetchLedger', params)
request: dict = {
'type': 'userNonFundingLedgerUpdates',
'user': userAddress,
}
if since is not None:
request['startTime'] = since
until = self.safe_integer(params, 'until')
if until is not None:
request['endTime'] = until
params = self.omit(params, ['until'])
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "time":1724762307531,
# "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
# "delta":{
# "type":"accountClassTransfer",
# "usdc":"50.0",
# "toPerp":false
# }
# }
# ]
#
return self.parse_ledger(response, None, since, limit)
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
#
# {
# "time":1724762307531,
# "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
# "delta":{
# "type":"accountClassTransfer",
# "usdc":"50.0",
# "toPerp":false
# }
# }
#
timestamp = self.safe_integer(item, 'time')
delta = self.safe_dict(item, 'delta', {})
fee = None
feeCost = self.safe_integer(delta, 'fee')
if feeCost is not None:
fee = {
'currency': 'USDC',
'cost': feeCost,
}
type = self.safe_string(delta, 'type')
amount = self.safe_string(delta, 'usdc')
return self.safe_ledger_entry({
'info': item,
'id': self.safe_string(item, 'hash'),
'direction': None,
'account': None,
'referenceAccount': self.safe_string(delta, 'user'),
'referenceId': self.safe_string(item, 'hash'),
'type': self.parse_ledger_entry_type(type),
'currency': None,
'amount': self.parse_number(amount),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'before': None,
'after': None,
'status': None,
'fee': fee,
}, currency)
def parse_ledger_entry_type(self, type):
ledgerType: dict = {
'internalTransfer': 'transfer',
'accountClassTransfer': 'transfer',
}
return self.safe_string(ledgerType, type, type)
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all deposits made to an account
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch deposits for
:param int [limit]: the maximum number of deposits structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: the latest time in ms to fetch withdrawals for
:param str [params.subAccountAddress]: sub account user address
:param str [params.vaultAddress]: vault address
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await self.load_markets()
userAddress = None
userAddress, params = self.handle_public_address('fetchDepositsWithdrawals', params)
request: dict = {
'type': 'userNonFundingLedgerUpdates',
'user': userAddress,
}
if since is not None:
request['startTime'] = since
until = self.safe_integer(params, 'until')
if until is not None:
if since is None:
raise ArgumentsRequired(self.id + ' fetchDeposits requires since while until is set')
request['endTime'] = until
params = self.omit(params, ['until'])
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "time":1724762307531,
# "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
# "delta":{
# "type":"accountClassTransfer",
# "usdc":"50.0",
# "toPerp":false
# }
# }
# ]
#
records = self.extract_type_from_delta(response)
vaultAddress = None
vaultAddress, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'vaultAddress')
vaultAddress = self.format_vault_address(vaultAddress)
deposits = []
if vaultAddress is not None:
for i in range(0, len(records)):
record = records[i]
if record['type'] == 'vaultDeposit':
delta = self.safe_dict(record, 'delta')
if delta['vault'] == '0x' + vaultAddress:
deposits.append(record)
else:
deposits = self.filter_by_array(records, 'type', ['deposit'], False)
return self.parse_transactions(deposits, None, since, limit)
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all withdrawals made from an account
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch withdrawals for
:param int [limit]: the maximum number of withdrawals structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: the latest time in ms to fetch withdrawals for
:param str [params.subAccountAddress]: sub account user address
:param str [params.vaultAddress]: vault address
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await self.load_markets()
userAddress = None
userAddress, params = self.handle_public_address('fetchDepositsWithdrawals', params)
request: dict = {
'type': 'userNonFundingLedgerUpdates',
'user': userAddress,
}
if since is not None:
request['startTime'] = since
until = self.safe_integer(params, 'until')
if until is not None:
request['endTime'] = until
params = self.omit(params, ['until'])
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "time":1724762307531,
# "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
# "delta":{
# "type":"accountClassTransfer",
# "usdc":"50.0",
# "toPerp":false
# }
# }
# ]
#
records = self.extract_type_from_delta(response)
vaultAddress = None
vaultAddress, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'vaultAddress')
vaultAddress = self.format_vault_address(vaultAddress)
withdrawals = []
if vaultAddress is not None:
for i in range(0, len(records)):
record = records[i]
if record['type'] == 'vaultWithdraw':
delta = self.safe_dict(record, 'delta')
if delta['vault'] == '0x' + vaultAddress:
withdrawals.append(record)
else:
withdrawals = self.filter_by_array(records, 'type', ['withdraw'], False)
return self.parse_transactions(withdrawals, None, since, limit)
async def fetch_open_interests(self, symbols: Strings = None, params={}):
"""
Retrieves the open interest for a list of symbols
:param str[] [symbols]: Unified CCXT market symbol
:param dict [params]: exchange specific parameters
:returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
swapMarkets = await self.fetch_swap_markets()
return self.parse_open_interests(swapMarkets, symbols)
async def fetch_open_interest(self, symbol: str, params={}):
"""
retrieves the open interest of a contract trading pair
:param str symbol: unified CCXT market symbol
:param dict [params]: exchange specific parameters
:returns dict: an `open interest structure <https://docs.ccxt.com/#/?id=open-interest-structure>`
"""
symbol = self.symbol(symbol)
await self.load_markets()
ois = await self.fetch_open_interests([symbol], params)
return ois[symbol]
def parse_open_interest(self, interest, market: Market = None):
#
# {
# szDecimals: '2',
# name: 'HYPE',
# maxLeverage: '3',
# funding: '0.00014735',
# openInterest: '14677900.74',
# prevDayPx: '26.145',
# dayNtlVlm: '299643445.12560016',
# premium: '0.00081613',
# oraclePx: '27.569',
# markPx: '27.63',
# midPx: '27.599',
# impactPxs: ['27.5915', '27.6319'],
# dayBaseVlm: '10790652.83',
# baseId: 159
# }
#
interest = self.safe_dict(interest, 'info', {})
coin = self.safe_string(interest, 'name')
marketId = None
if coin is not None:
marketId = self.coin_to_market_id(coin)
return self.safe_open_interest({
'symbol': self.safe_symbol(marketId),
'openInterestAmount': self.safe_number(interest, 'openInterest'),
'openInterestValue': None,
'timestamp': None,
'datetime': None,
'info': interest,
}, market)
async def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch the history of funding payments paid and received on self account
:param str [symbol]: unified market symbol
:param int [since]: the earliest time in ms to fetch funding history for
:param int [limit]: the maximum number of funding history structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.subAccountAddress]: sub account user address
:returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
"""
await self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
userAddress = None
userAddress, params = self.handle_public_address('fetchFundingHistory', params)
request: dict = {
'user': userAddress,
'type': 'userFunding',
}
if since is not None:
request['startTime'] = since
until = self.safe_integer(params, 'until')
params = self.omit(params, 'until')
if until is not None:
request['endTime'] = until
response = await self.publicPostInfo(self.extend(request, params))
#
# [
# {
# "time": 1734026400057,
# "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
# "delta": {
# "type": "funding",
# "coin": "SOL",
# "usdc": "75.635093",
# "szi": "-7375.9",
# "fundingRate": "0.00004381",
# "nSamples": null
# }
# }
# ]
#
return self.parse_incomes(response, market, since, limit)
def parse_income(self, income, market: Market = None):
#
# {
# "time": 1734026400057,
# "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
# "delta": {
# "type": "funding",
# "coin": "SOL",
# "usdc": "75.635093",
# "szi": "-7375.9",
# "fundingRate": "0.00004381",
# "nSamples": null
# }
# }
#
id = self.safe_string(income, 'hash')
timestamp = self.safe_integer(income, 'time')
delta = self.safe_dict(income, 'delta')
baseId = self.safe_string(delta, 'coin')
marketSymbol = baseId + '/USDC:USDC'
market = self.safe_market(marketSymbol)
symbol = market['symbol']
amount = self.safe_string(delta, 'usdc')
code = self.safe_currency_code('USDC')
rate = self.safe_number(delta, 'fundingRate')
return {
'info': income,
'symbol': symbol,
'code': code,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'id': id,
'amount': self.parse_number(amount),
'rate': rate,
}
async def reserve_request_weight(self, weight: Num, params={}) -> dict:
"""
Instead of trading to increase the address based rate limits, self action allows reserving additional actions for 0.0005 USDC per request. The cost is paid from the Perps balance.
:param number weight: the weight to reserve, 1 weight = 1 action, 0.0005 USDC per action
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a response object
"""
nonce = self.milliseconds()
request: dict = {
'nonce': nonce,
}
action: dict = {
'type': 'reserveRequestWeight',
'weight': weight,
}
signature = self.sign_l1_action(action, nonce)
request['action'] = action
request['signature'] = signature
response = await self.privatePostExchange(self.extend(request, params))
return response
def extract_type_from_delta(self, data=[]):
records = []
for i in range(0, len(data)):
record = data[i]
record['type'] = record['delta']['type']
records.append(record)
return records
def format_vault_address(self, address: Str = None):
if address is None:
return None
if address.startswith('0x'):
return address.replace('0x', '')
return address
def handle_public_address(self, methodName: str, params: dict):
userAux = None
userAux, params = self.handle_option_and_params_2(params, methodName, 'user', 'subAccountAddress')
user = userAux
user, params = self.handle_option_and_params(params, methodName, 'address', userAux)
if (user is not None) and (user != ''):
return [user, params]
if (self.walletAddress is not None) and (self.walletAddress != ''):
return [self.walletAddress, params]
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a user parameter inside \'params\' or the wallet address set')
def coin_to_market_id(self, coin: Str):
if coin.find('/') > -1 or coin.find('@') > -1:
return coin # spot
return self.safe_currency_code(coin) + '/USDC:USDC'
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
if not response:
return None # fallback to default error handler
# {"status":"err","response":"User or API Wallet 0xb8a6f8b26223de27c31938d56e470a5b832703a5 does not exist."}
#
# {
# status: 'ok',
# response: {type: 'order', data: {statuses: [{error: 'Insufficient margin to place order. asset=4'}]}}
# }
# {"status":"ok","response":{"type":"order","data":{"statuses":[{"error":"Insufficient margin to place order. asset=84"}]}}}
#
# {"status":"unknownOid"}
#
status = self.safe_string(response, 'status', '')
error = self.safe_string(response, 'error')
message = None
if status == 'err':
message = self.safe_string(response, 'response')
elif status == 'unknownOid':
raise OrderNotFound(self.id + ' ' + body) # {"status":"unknownOid"}
elif error is not None:
message = error
else:
responsePayload = self.safe_dict(response, 'response', {})
data = self.safe_dict(responsePayload, 'data', {})
statuses = self.safe_list(data, 'statuses', [])
for i in range(0, len(statuses)):
message = self.safe_string(statuses[i], 'error')
if message is not None:
break
feedback = self.id + ' ' + body
nonEmptyMessage = ((message is not None) and (message != ''))
if nonEmptyMessage:
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
if nonEmptyMessage:
raise ExchangeError(feedback) # unknown message
return None
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
url = self.implode_hostname(self.urls['api'][api]) + '/' + path
if method == 'POST':
headers = {
'Content-Type': 'application/json',
}
body = self.json(params)
return {'url': url, 'method': method, 'body': body, 'headers': headers}
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
if ('byType' in config) and ('type' in params):
type = params['type']
byType = config['byType']
if type in byType:
return byType[type]
return self.safe_value(config, 'cost', 1)
def parse_create_edit_order_args(self, id: Str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
market = self.market(symbol)
vaultAddress = None
vaultAddress, params = self.handle_option_and_params_2(params, 'createOrder', 'vaultAddress', 'subAccountAddress')
vaultAddress = self.format_vault_address(vaultAddress)
symbol = market['symbol']
order = {
'symbol': symbol,
'type': type,
'side': side,
'amount': amount,
'price': price,
'params': params,
}
globalParams = {}
if vaultAddress is not None:
globalParams['vaultAddress'] = vaultAddress
if id is not None:
order['id'] = id
return [order, globalParams]