2074 lines
84 KiB
Python
2074 lines
84 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.hibachi import ImplicitAPI
|
|
import asyncio
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, FundingRate, Trade, TradingFees, Transaction
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class hibachi(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(hibachi, self).describe(), {
|
|
'id': 'hibachi',
|
|
'name': 'Hibachi',
|
|
'countries': ['US'],
|
|
'rateLimit': 100,
|
|
'userAgent': self.userAgents['chrome'],
|
|
'certified': False,
|
|
'pro': False,
|
|
'dex': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': False,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'cancelWithdraw': False,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createConvertTrade': False,
|
|
'createDepositAddress': False,
|
|
'createMarketBuyOrderWithCost': False,
|
|
'createMarketOrder': False,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createOrders': True,
|
|
'createOrderWithTakeProfitAndStopLoss': False,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopLimitOrder': False,
|
|
'createStopLossOrder': False,
|
|
'createStopMarketOrder': False,
|
|
'createStopOrder': False,
|
|
'createTakeProfitOrder': False,
|
|
'createTrailingAmountOrder': False,
|
|
'createTrailingPercentOrder': False,
|
|
'createTriggerOrder': False,
|
|
'editOrder': True,
|
|
'editOrders': True,
|
|
'fetchAccounts': False,
|
|
'fetchBalance': True,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrder': False,
|
|
'fetchClosedOrders': False,
|
|
'fetchConvertCurrencies': False,
|
|
'fetchConvertQuote': False,
|
|
'fetchCurrencies': False,
|
|
'fetchDepositAddress': True,
|
|
'fetchDeposits': True,
|
|
'fetchDepositsWithdrawals': False,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingInterval': False,
|
|
'fetchFundingIntervals': False,
|
|
'fetchFundingRate': True,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchLedger': True,
|
|
'fetchLeverage': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarkets': True,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenOrder': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': False,
|
|
'fetchOrderTrades': False,
|
|
'fetchPosition': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchStatus': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': False,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': True,
|
|
'fetchTradingLimits': False,
|
|
'fetchTransactions': 'emulated',
|
|
'fetchTransfers': False,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'timeframes': {
|
|
'1m': '1min',
|
|
'5m': '5min',
|
|
'15m': '15min',
|
|
'1h': '1h',
|
|
'4h': '4h',
|
|
'1d': '1d',
|
|
'1w': '1w',
|
|
},
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/7301bbb1-4f27-4167-8a55-75f74b14e973',
|
|
'api': {
|
|
'public': 'https://data-api.hibachi.xyz',
|
|
'private': 'https://api.hibachi.xyz',
|
|
},
|
|
'www': 'https://www.hibachi.xyz/',
|
|
'referral': {
|
|
'url': 'hibachi.xyz/r/ZBL2YFWIHU',
|
|
},
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'market/exchange-info': 1,
|
|
'market/data/trades': 1,
|
|
'market/data/prices': 1,
|
|
'market/data/stats': 1,
|
|
'market/data/klines': 1,
|
|
'market/data/orderbook': 1,
|
|
'market/data/open-interest': 1,
|
|
'market/data/funding-rates': 1,
|
|
'exchange/utc-timestamp': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'capital/deposit-info': 1,
|
|
'capital/history': 1,
|
|
'trade/account/trading_history': 1,
|
|
'trade/account/info': 1,
|
|
'trade/order': 1,
|
|
'trade/account/trades': 1,
|
|
'trade/orders': 1,
|
|
},
|
|
'put': {
|
|
'trade/order': 1,
|
|
},
|
|
'delete': {
|
|
'trade/order': 1,
|
|
'trade/orders': 1,
|
|
},
|
|
'post': {
|
|
'trade/order': 1,
|
|
'trade/orders': 1,
|
|
'capital/withdraw': 1,
|
|
},
|
|
},
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': False,
|
|
'accountId': True,
|
|
'privateKey': True,
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'tierBased': False,
|
|
'percentage': True,
|
|
'maker': self.parse_number('0.00015'),
|
|
'taker': self.parse_number('0.00045'),
|
|
},
|
|
},
|
|
'currencies': self.hardcoded_currencies(),
|
|
'options': {
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': None,
|
|
'stopLossPrice': False,
|
|
'takeProfitPrice': False,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': False,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'daysBack': None,
|
|
'untilDays': None,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': None,
|
|
'fetchOHLCV': {
|
|
'limit': None,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'commonCurrencies': {},
|
|
'exceptions': {
|
|
'exact': {
|
|
'2': BadRequest, # {"errorCode":2,"message":"Invalid signature: Failed to verify signature"}
|
|
'3': OrderNotFound, # {"errorCode":3,"message":"Not found: order ID 33","status":"failed"}
|
|
'4': BadRequest, # {"errorCode":4,"message":"Missing accountId","status":"failed"}
|
|
},
|
|
'broad': {
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
})
|
|
|
|
def get_account_id(self):
|
|
self.check_required_credentials()
|
|
id = self.parse_to_int(self.accountId)
|
|
return id
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
marketId = self.safe_string(market, 'symbol')
|
|
numericId = self.safe_number(market, 'id')
|
|
marketType = 'swap'
|
|
baseId = self.safe_string(market, 'underlyingSymbol')
|
|
quoteId = self.safe_string(market, 'settlementSymbol')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
settleId: Str = self.safe_string(market, 'settlementSymbol')
|
|
settle: Str = self.safe_currency_code(settleId)
|
|
symbol = base + '/' + quote + ':' + settle
|
|
created = self.safe_integer_product(market, 'marketCreationTimestamp', 1000)
|
|
return {
|
|
'id': marketId,
|
|
'numericId': numericId,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': marketType,
|
|
'spot': False,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'active': self.safe_string(market, 'status') == 'LIVE',
|
|
'contract': True,
|
|
'linear': True,
|
|
'inverse': False,
|
|
'contractSize': self.parse_number('1'),
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'underlyingDecimals'))),
|
|
'price': self.parse_number(self.safe_list(market, 'orderbookGranularities')[0]) / 10000.0,
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(market, 'minNotional'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': created,
|
|
'info': market,
|
|
}
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for hibachi
|
|
|
|
https://api-doc.hibachi.xyz/#183981da-8df5-40a0-a155-da15015dd536
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.publicGetMarketExchangeInfo(params)
|
|
# {
|
|
# "displayName": "ETH/USDT Perps",
|
|
# "id": 1,
|
|
# "maintenanceFactorForPositions": "0.030000",
|
|
# "marketCloseTimestamp": null,
|
|
# "marketOpenTimestamp": null,
|
|
# "minNotional": "1",
|
|
# "minOrderSize": "0.000000001",
|
|
# "orderbookGranularities": [
|
|
# "0.01",
|
|
# "0.1",
|
|
# "1",
|
|
# "10"
|
|
# ],
|
|
# "riskFactorForOrders": "0.066667",
|
|
# "riskFactorForPositions": "0.030000",
|
|
# "settlementDecimals": 6,
|
|
# "settlementSymbol": "USDT",
|
|
# "status": "LIVE",
|
|
# "stepSize": "0.000000001",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "tickSize": "0.000001",
|
|
# "underlyingDecimals": 9,
|
|
# "underlyingSymbol": "ETH"
|
|
# },
|
|
rows = self.safe_list(response, 'futureContracts')
|
|
return self.parse_markets(rows)
|
|
|
|
def hardcoded_currencies(self) -> Currencies:
|
|
# Hibachi only supports USDT on Arbitrum at self time
|
|
# We don't have an API endpoint to expose self information yet
|
|
result: dict = {}
|
|
networks: dict = {}
|
|
networkId = 'ARBITRUM'
|
|
networks[networkId] = {
|
|
'id': networkId,
|
|
'network': networkId,
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'deposit': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'info': {},
|
|
}
|
|
code = self.safe_currency_code('USDT')
|
|
result[code] = self.safe_currency_structure({
|
|
'id': 'USDT',
|
|
'name': 'USDT',
|
|
'type': 'fiat',
|
|
'code': code,
|
|
'precision': self.parse_number('0.000001'),
|
|
'active': True,
|
|
'fee': None,
|
|
'networks': networks,
|
|
'deposit': True,
|
|
'withdraw': True,
|
|
'limits': {
|
|
'deposit': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'info': {},
|
|
})
|
|
return result
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
result: dict = {
|
|
'info': response,
|
|
}
|
|
# Hibachi only supports USDT on Arbitrum at self time
|
|
code = self.safe_currency_code('USDT')
|
|
account = self.account()
|
|
account['total'] = self.safe_string(response, 'balance')
|
|
account['free'] = self.safe_string(response, 'maximalWithdraw')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
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://api-doc.hibachi.xyz/#69aafedb-8274-4e21-bbaf-91dace8b8f31
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetTradeAccountInfo(self.extend(request, params))
|
|
#
|
|
# {
|
|
# assets: [{quantity: '3.000000', symbol: 'USDT'}],
|
|
# balance: '3.000000',
|
|
# maximalWithdraw: '3.000000',
|
|
# numFreeTransfersRemaining: '100',
|
|
# positions: [],
|
|
# totalOrderNotional: '0.000000',
|
|
# totalPositionNotional: '0.000000',
|
|
# totalUnrealizedFundingPnl: '0.000000',
|
|
# totalUnrealizedPnl: '0.000000',
|
|
# totalUnrealizedTradingPnl: '0.000000',
|
|
# tradeMakerFeeRate: '0.00000000',
|
|
# tradeTakerFeeRate: '0.00020000'
|
|
# }
|
|
#
|
|
return self.parse_balance(response)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
prices = self.safe_dict(ticker, 'prices')
|
|
stats = self.safe_dict(ticker, 'stats')
|
|
bid = self.safe_number(prices, 'bidPrice')
|
|
ask = self.safe_number(prices, 'askPrice')
|
|
last = self.safe_number(prices, 'tradePrice')
|
|
high = self.safe_number(stats, 'high24h')
|
|
low = self.safe_number(stats, 'low24h')
|
|
volume = self.safe_number(stats, 'volume24h')
|
|
return self.safe_ticker({
|
|
'symbol': self.safe_symbol(None, market),
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'bid': bid,
|
|
'ask': ask,
|
|
'last': last,
|
|
'high': high,
|
|
'low': low,
|
|
'bidVolume': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': None,
|
|
'quoteVolume': volume,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
# public fetchTrades:
|
|
# {
|
|
# "price": "3512.431902",
|
|
# "quantity": "1.414780098",
|
|
# "takerSide": "Buy",
|
|
# "timestamp": 1712692147
|
|
# }
|
|
#
|
|
# private fetchMyTrades:
|
|
# {
|
|
# "askAccountId": 221,
|
|
# "askOrderId": 589168494921909200,
|
|
# "bidAccountId": 132,
|
|
# "bidOrderId": 589168494829895700,
|
|
# "fee": "0.000477",
|
|
# "id": 199511136,
|
|
# "orderType": "MARKET",
|
|
# "price": "119257.90000",
|
|
# "quantity": "0.0000200000",
|
|
# "realizedPnl": "-0.000352",
|
|
# "side": "Sell",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "timestamp": 1752543391
|
|
# }
|
|
marketId = self.safe_string(trade, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
id = self.safe_string(trade, 'id')
|
|
price = self.safe_string(trade, 'price')
|
|
amount = self.safe_string(trade, 'quantity')
|
|
timestamp = self.safe_integer_product(trade, 'timestamp', 1000)
|
|
cost = Precise.string_mul(price, amount)
|
|
side = None
|
|
fee = None
|
|
orderType = None
|
|
orderId = None
|
|
takerOrMaker = None
|
|
if id is None:
|
|
# public trades
|
|
side = self.safe_string_lower(trade, 'takerSide')
|
|
takerOrMaker = 'taker'
|
|
else:
|
|
# private trades
|
|
side = self.safe_string_lower(trade, 'side')
|
|
fee = {'cost': self.safe_string(trade, 'fee'), 'currency': 'USDT'}
|
|
orderType = self.safe_string_lower(trade, 'orderType')
|
|
if side == 'buy':
|
|
orderId = self.safe_string(trade, 'bidOrderId')
|
|
else:
|
|
orderId = self.safe_string(trade, 'askOrderId')
|
|
return self.safe_trade({
|
|
'id': id,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'side': side,
|
|
'price': price,
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'order': orderId,
|
|
'takerOrMaker': takerOrMaker,
|
|
'type': orderType,
|
|
'fee': fee,
|
|
'info': trade,
|
|
}, market)
|
|
|
|
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://api-doc.hibachi.xyz/#86a53bc1-d3bb-4b93-8a11-7034d4698caa
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch(maximum value is 100)
|
|
:param dict [params]: extra parameters specific to the hibachi api endpoint
|
|
:returns dict[]: a list of recent [trade structures]
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = await self.publicGetMarketDataTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "trades": [
|
|
# {
|
|
# "price": "111091.38352",
|
|
# "quantity": "0.0090090093",
|
|
# "takerSide": "Buy",
|
|
# "timestamp": 1752095479
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'trades', [])
|
|
return self.parse_trades(trades, market)
|
|
|
|
async def fetch_ticker(self, symbol: Str, params={}) -> Ticker:
|
|
"""
|
|
|
|
https://api-doc.hibachi.xyz/#4abb30c4-e5c7-4b0f-9ade-790111dbfa47
|
|
|
|
fetches a price ticker and the related information for the past 24h
|
|
:param str symbol: unified symbol of the market
|
|
:param dict [params]: extra parameters specific to the hibachi api endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
rawPromises = [
|
|
self.publicGetMarketDataPrices(self.extend(request, params)),
|
|
self.publicGetMarketDataStats(self.extend(request, params)),
|
|
]
|
|
promises = await asyncio.gather(*rawPromises)
|
|
pricesResponse = promises[0]
|
|
# {
|
|
# "askPrice": "3514.650296",
|
|
# "bidPrice": "3513.596112",
|
|
# "fundingRateEstimation": {
|
|
# "estimatedFundingRate": "0.000001",
|
|
# "nextFundingTimestamp": 1712707200
|
|
# },
|
|
# "markPrice": "3514.288858",
|
|
# "spotPrice": "3514.715000",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "tradePrice": "2372.746570"
|
|
# }
|
|
statsResponse = promises[1]
|
|
# {
|
|
# "high24h": "3819.507827",
|
|
# "low24h": "3754.474162",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "volume24h": "23554.858590416"
|
|
# }
|
|
ticker = {
|
|
'prices': pricesResponse,
|
|
'stats': statsResponse,
|
|
}
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
def parse_order_status(self, status: str) -> str:
|
|
statuses: dict = {
|
|
'PENDING': 'open',
|
|
'CHILD_PENDING': 'open',
|
|
'SCHEDULED_TWAP': 'open',
|
|
'PLACED': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'FILLED': 'closed',
|
|
'CANCELLED': 'canceled',
|
|
'REJECTED': 'rejected',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
status = self.safe_string(order, 'status')
|
|
type = self.safe_string_lower(order, 'orderType')
|
|
price = self.safe_string(order, 'price')
|
|
rawSide = self.safe_string(order, 'side')
|
|
side = None
|
|
if rawSide == 'BID':
|
|
side = 'buy'
|
|
elif rawSide == 'ASK':
|
|
side = 'sell'
|
|
amount = self.safe_string(order, 'totalQuantity')
|
|
remaining = self.safe_string(order, 'availableQuantity')
|
|
totalQuantity = self.safe_string(order, 'totalQuantity')
|
|
availableQuantity = self.safe_string(order, 'availableQuantity')
|
|
filled = None
|
|
if totalQuantity is not None and availableQuantity is not None:
|
|
filled = Precise.string_sub(totalQuantity, availableQuantity)
|
|
timeInForce = 'GTC'
|
|
orderFlags = self.safe_value(order, 'orderFlags')
|
|
postOnly = False
|
|
reduceOnly = False
|
|
if orderFlags == 'POST_ONLY':
|
|
timeInForce = 'PO'
|
|
postOnly = True
|
|
elif orderFlags == 'IOC':
|
|
timeInForce = 'IOC'
|
|
elif orderFlags == 'REDUCE_ONLY':
|
|
reduceOnly = True
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': self.safe_string(order, 'orderId'),
|
|
'clientOrderId': None,
|
|
'datetime': None,
|
|
'timestamp': None,
|
|
'lastTradeTimestamp': None,
|
|
'lastUpdateTimestamp': None,
|
|
'status': self.parse_order_status(status),
|
|
'symbol': market['symbol'],
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'side': side,
|
|
'price': price,
|
|
'average': None,
|
|
'amount': amount,
|
|
'filled': filled,
|
|
'remaining': remaining,
|
|
'cost': None,
|
|
'trades': None,
|
|
'fee': None,
|
|
'reduceOnly': reduceOnly,
|
|
'postOnly': postOnly,
|
|
'triggerPrice': self.safe_number(order, 'triggerPrice'),
|
|
}, market)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://api-doc.hibachi.xyz/#096a8854-b918-4de8-8731-b2a28d26b96d
|
|
|
|
:param str id: the 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
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'orderId': id,
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetTradeOrder(self.extend(request, params))
|
|
return self.parse_order(response, market)
|
|
|
|
async def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
fetch the trading fee
|
|
@param params extra parameters
|
|
:returns dict: a map of market symbols to `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetTradeAccountInfo(self.extend(request, params))
|
|
# {
|
|
# "tradeMakerFeeRate": "0.00000000",
|
|
# "tradeTakerFeeRate": "0.00020000"
|
|
# },
|
|
makerFeeRate = self.safe_number(response, 'tradeMakerFeeRate')
|
|
takerFeeRate = self.safe_number(response, 'tradeTakerFeeRate')
|
|
result: dict = {}
|
|
for i in range(0, len(self.symbols)):
|
|
symbol = self.symbols[i]
|
|
result[symbol] = {
|
|
'info': response,
|
|
'symbol': symbol,
|
|
'maker': makerFeeRate,
|
|
'taker': takerFeeRate,
|
|
'percentage': True,
|
|
}
|
|
return result
|
|
|
|
def order_message(self, market, nonce: float, feeRate: float, type: OrderType, side: OrderSide, amount: float, price: Num = None):
|
|
sideInternal = 0
|
|
if side == 'sell':
|
|
sideInternal = 0
|
|
elif side == 'buy':
|
|
sideInternal = 1
|
|
# Converting them to internal representation:
|
|
# - Quantity: Internal = External * (10^underlyingDecimals)
|
|
# - Price: Internal = External * (2^32) * (10^(settlementDecimals-underlyingDecimals))
|
|
# - FeeRate: Internal = External * (10^8)
|
|
amountStr = self.amount_to_precision(self.safe_string(market, 'symbol'), amount)
|
|
feeRateStr = self.number_to_string(feeRate)
|
|
info = self.safe_dict(market, 'info')
|
|
underlying = '1e' + self.safe_string(info, 'underlyingDecimals')
|
|
settlement = '1e' + self.safe_string(info, 'settlementDecimals')
|
|
one = '1'
|
|
feeRateFactor = '100000000' # 10^8
|
|
priceFactor = '4294967296' # 2^32
|
|
quantityInternal = Precise.string_div(Precise.string_mul(amountStr, underlying), one, 0)
|
|
feeRateInternal = Precise.string_div(Precise.string_mul(feeRateStr, feeRateFactor), one, 0)
|
|
# Encoding
|
|
nonce16 = self.int_to_base16(nonce)
|
|
noncePadded = nonce16.rjust(16, '0')
|
|
encodedNonce = self.base16_to_binary(noncePadded)
|
|
numericId = self.int_to_base16(self.safe_integer(market, 'numericId'))
|
|
numericIdPadded = numericId.rjust(8, '0')
|
|
encodedMarketId = self.base16_to_binary(numericIdPadded)
|
|
quantity16 = self.int_to_base16(self.parse_to_int(quantityInternal))
|
|
quantityPadded = quantity16.rjust(16, '0')
|
|
encodedQuantity = self.base16_to_binary(quantityPadded)
|
|
sideInternal16 = self.int_to_base16(sideInternal)
|
|
sidePadded = sideInternal16.rjust(8, '0')
|
|
encodedSide = self.base16_to_binary(sidePadded)
|
|
feeRateInternal16 = self.int_to_base16(self.parse_to_int(feeRateInternal))
|
|
feeRatePadded = feeRateInternal16.rjust(16, '0')
|
|
encodedFeeRate = self.base16_to_binary(feeRatePadded)
|
|
encodedPrice = self.binary_concat()
|
|
if type == 'limit':
|
|
priceStr = self.price_to_precision(self.safe_string(market, 'symbol'), price)
|
|
priceInternal = Precise.string_div(Precise.string_div(Precise.string_mul(Precise.string_mul(priceStr, priceFactor), settlement), underlying), one, 0)
|
|
price16 = self.int_to_base16(self.parse_to_int(priceInternal))
|
|
pricePadded = price16.rjust(16, '0')
|
|
encodedPrice = self.base16_to_binary(pricePadded)
|
|
message = self.binary_concat(encodedNonce, encodedMarketId, encodedQuantity, encodedSide, encodedPrice, encodedFeeRate)
|
|
return message
|
|
|
|
def create_order_request(self, nonce: float, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
market = self.market(symbol)
|
|
feeRate = max(self.safe_number(market, 'taker', self.safe_number(self.options, 'defaultTakerFee', 0.00045)), self.safe_number(market, 'maker', self.safe_number(self.options, 'defaultMakerFee', 0.00015)))
|
|
sideInternal = ''
|
|
if side == 'sell':
|
|
sideInternal = 'ASK'
|
|
elif side == 'buy':
|
|
sideInternal = 'BID'
|
|
priceInternal = ''
|
|
if price:
|
|
priceInternal = self.price_to_precision(symbol, price)
|
|
message = self.order_message(market, nonce, feeRate, type, side, amount, price)
|
|
signature = self.sign_message(message, self.privateKey)
|
|
request = {
|
|
'symbol': self.safe_string(market, 'id'),
|
|
'nonce': nonce,
|
|
'side': sideInternal,
|
|
'orderType': type.upper(),
|
|
'quantity': self.amount_to_precision(symbol, amount),
|
|
'price': priceInternal,
|
|
'signature': signature,
|
|
'maxFeesPercent': self.number_to_string(feeRate),
|
|
}
|
|
postOnly = self.is_post_only(type.upper() == 'MARKET', None, params)
|
|
reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only')
|
|
timeInForce = self.safe_string_lower(params, 'timeInForce')
|
|
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
|
if postOnly:
|
|
request['orderFlags'] = 'POST_ONLY'
|
|
elif timeInForce == 'ioc':
|
|
request['orderFlags'] = 'IOC'
|
|
elif reduceOnly:
|
|
request['orderFlags'] = 'REDUCE_ONLY'
|
|
if triggerPrice is not None:
|
|
request['triggerPrice'] = triggerPrice
|
|
params = self.omit(params, ['reduceOnly', 'reduce_only', 'postOnly', 'timeInForce', 'stopPrice', 'triggerPrice'])
|
|
return self.extend(request, params)
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://api-doc.hibachi.xyz/#00f6d5ad-5275-41cb-a1a8-19ed5d142124
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
nonce = self.nonce()
|
|
request = self.create_order_request(nonce, symbol, type, side, amount, price, params)
|
|
request['accountId'] = self.get_account_id()
|
|
response = await self.privatePostTradeOrder(request)
|
|
#
|
|
# {
|
|
# "orderId": "578721673790138368"
|
|
# }
|
|
#
|
|
return self.safe_order({
|
|
'id': self.safe_string(response, 'orderId'),
|
|
'status': 'pending',
|
|
})
|
|
|
|
async def create_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
|
|
"""
|
|
*contract only* create a list of trade orders
|
|
|
|
https://api-doc.hibachi.xyz/#c2840b9b-f02c-44ed-937d-dc2819f135b4
|
|
|
|
: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()
|
|
nonce = self.nonce()
|
|
requestOrders = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
symbol = self.safe_string(rawOrder, 'symbol')
|
|
type = self.safe_string(rawOrder, 'type')
|
|
side = self.safe_string(rawOrder, 'side')
|
|
amount = self.safe_value(rawOrder, 'amount')
|
|
price = self.safe_value(rawOrder, 'price')
|
|
orderParams = self.safe_dict(rawOrder, 'params', {})
|
|
orderRequest = self.create_order_request(nonce + i, symbol, type, side, amount, price, orderParams)
|
|
orderRequest['action'] = 'place'
|
|
requestOrders.append(orderRequest)
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
'orders': requestOrders,
|
|
}
|
|
response = await self.privatePostTradeOrders(self.extend(request, params))
|
|
#
|
|
# {"orders": [{nonce: '1754349993908', orderId: '589642085255349248'}]}
|
|
#
|
|
ret = []
|
|
responseOrders = self.safe_list(response, 'orders')
|
|
for i in range(0, len(responseOrders)):
|
|
responseOrder = responseOrders[i]
|
|
ret.append(self.safe_order({
|
|
'info': responseOrder,
|
|
'id': self.safe_string(responseOrder, 'orderId'),
|
|
'status': 'pending',
|
|
}))
|
|
return ret
|
|
|
|
def edit_order_request(self, nonce: float, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
|
market = self.market(symbol)
|
|
feeRate = max(self.safe_number(market, 'taker'), self.safe_number(market, 'maker'))
|
|
message = self.order_message(market, nonce, feeRate, type, side, amount, price)
|
|
signature = self.sign_message(message, self.privateKey)
|
|
request = {
|
|
'orderId': id,
|
|
'nonce': nonce,
|
|
'updatedQuantity': self.amount_to_precision(symbol, amount),
|
|
'updatedPrice': self.price_to_precision(symbol, price),
|
|
'maxFeesPercent': self.number_to_string(feeRate),
|
|
'signature': signature,
|
|
}
|
|
return self.extend(request, params)
|
|
|
|
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
|
"""
|
|
edit a limit order that is not matched
|
|
|
|
https://api-doc.hibachi.xyz/#94d2cdaf-1c71-440f-a981-da1112824810
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: must be 'limit'
|
|
:param str side: 'buy' or 'sell', should stay the same with original side
|
|
: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
|
|
: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()
|
|
nonce = self.nonce()
|
|
request = self.edit_order_request(nonce, id, symbol, type, side, amount, price, params)
|
|
request['accountId'] = self.get_account_id()
|
|
await self.privatePutTradeOrder(request)
|
|
# At self time the response body is empty. A 200 response means the update request is accepted and sent to process
|
|
#
|
|
# {}
|
|
#
|
|
return self.safe_order({
|
|
'id': id,
|
|
'status': 'pending',
|
|
})
|
|
|
|
async def edit_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
|
|
"""
|
|
edit a list of trade orders
|
|
|
|
https://api-doc.hibachi.xyz/#c2840b9b-f02c-44ed-937d-dc2819f135b4
|
|
|
|
:param Array orders: list of orders to edit, each object should contain the parameters required by editOrder, namely id, 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()
|
|
nonce = self.nonce()
|
|
requestOrders = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
id = self.safe_string(rawOrder, 'id')
|
|
symbol = self.safe_string(rawOrder, 'symbol')
|
|
type = self.safe_string(rawOrder, 'type')
|
|
side = self.safe_string(rawOrder, 'side')
|
|
amount = self.safe_value(rawOrder, 'amount')
|
|
price = self.safe_value(rawOrder, 'price')
|
|
orderParams = self.safe_dict(rawOrder, 'params', {})
|
|
orderRequest = self.edit_order_request(nonce + i, id, symbol, type, side, amount, price, orderParams)
|
|
orderRequest['action'] = 'modify'
|
|
requestOrders.append(orderRequest)
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
'orders': requestOrders,
|
|
}
|
|
response = await self.privatePostTradeOrders(self.extend(request, params))
|
|
#
|
|
# {"orders": [{"orderId": "589636801329628160"}]}
|
|
#
|
|
ret = []
|
|
responseOrders = self.safe_list(response, 'orders')
|
|
for i in range(0, len(responseOrders)):
|
|
responseOrder = responseOrders[i]
|
|
ret.append(self.safe_order({
|
|
'info': responseOrder,
|
|
'id': self.safe_string(responseOrder, 'orderId'),
|
|
'status': 'pending',
|
|
}))
|
|
return ret
|
|
|
|
def cancel_order_request(self, id: str):
|
|
bigid = self.convert_to_big_int(id)
|
|
idbase16 = self.int_to_base16(bigid)
|
|
idPadded = idbase16.rjust(16, '0')
|
|
message = self.base16_to_binary(idPadded)
|
|
signature = self.sign_message(message, self.privateKey)
|
|
return {
|
|
'orderId': id,
|
|
'signature': signature,
|
|
}
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://api-doc.hibachi.xyz/#e99c4f48-e610-4b7c-b7f6-1b4bb7af0271
|
|
|
|
cancels an open order
|
|
:param str id: order id
|
|
:param str symbol: is unused
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = self.cancel_order_request(id)
|
|
request['accountId'] = self.get_account_id()
|
|
response = await self.privateDeleteTradeOrder(self.extend(request, params))
|
|
# At self time the response body is empty. A 200 response means the cancel request is accepted and sent to cancel
|
|
#
|
|
# {}
|
|
#
|
|
return self.safe_order({
|
|
'info': response,
|
|
'id': id,
|
|
'status': 'canceled',
|
|
})
|
|
|
|
async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://api-doc.hibachi.xyz/#c2840b9b-f02c-44ed-937d-dc2819f135b4
|
|
|
|
:param str[] ids: order ids
|
|
:param str [symbol]: unified market symbol, unused
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
orders = []
|
|
for i in range(0, len(ids)):
|
|
orderRequest = self.cancel_order_request(ids[i])
|
|
orderRequest['action'] = 'cancel'
|
|
orders.append(orderRequest)
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
'orders': orders,
|
|
}
|
|
response = await self.privatePostTradeOrders(self.extend(request, params))
|
|
#
|
|
# {"orders": [{"orderId": "589636801329628160"}]}
|
|
#
|
|
ret = []
|
|
responseOrders = self.safe_list(response, 'orders')
|
|
for i in range(0, len(responseOrders)):
|
|
responseOrder = responseOrders[i]
|
|
ret.append(self.safe_order({
|
|
'info': responseOrder,
|
|
'id': self.safe_string(responseOrder, 'orderId'),
|
|
'status': 'canceled',
|
|
}))
|
|
return ret
|
|
|
|
async def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://api-doc.hibachi.xyz/#8ed24695-016e-49b2-a72d-7511ca921fee
|
|
|
|
cancel all open orders in a market
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
nonce = self.nonce()
|
|
nonce16 = self.int_to_base16(nonce)
|
|
noncePadded = nonce16.rjust(16, '0')
|
|
message = self.base16_to_binary(noncePadded)
|
|
signature = self.sign_message(message, self.privateKey)
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
'nonce': nonce,
|
|
'signature': signature,
|
|
}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['contractId'] = self.safe_integer(market, 'numericId')
|
|
response = await self.privateDeleteTradeOrders(self.extend(request, params))
|
|
# At self time the response body is empty. A 200 response means the cancel request is accepted and sent to process
|
|
#
|
|
# {}
|
|
#
|
|
return [
|
|
self.safe_order({
|
|
'info': response,
|
|
}),
|
|
]
|
|
|
|
def encode_withdraw_message(self, amount: float, maxFees: float, address: str):
|
|
# Converting them to internal representation:
|
|
# - Quantity: Internal = External * (10^6)
|
|
# - maxFees: Internal = External * (10^6)
|
|
# We only have USDT currency time
|
|
USDTAssetId = 1
|
|
USDTFactor = '1000000'
|
|
amountStr = self.number_to_string(amount)
|
|
maxFeesStr = self.number_to_string(maxFees)
|
|
one = '1'
|
|
quantityInternal = Precise.string_div(Precise.string_mul(amountStr, USDTFactor), one, 0)
|
|
maxFeesInternal = Precise.string_div(Precise.string_mul(maxFeesStr, USDTFactor), one, 0)
|
|
# Encoding
|
|
usdtAsset16 = self.int_to_base16(USDTAssetId)
|
|
usdtAssetPadded = usdtAsset16.rjust(8, '0')
|
|
encodedAssetId = self.base16_to_binary(usdtAssetPadded)
|
|
quantity16 = self.int_to_base16(self.parse_to_int(quantityInternal))
|
|
quantityPadded = quantity16.rjust(16, '0')
|
|
encodedQuantity = self.base16_to_binary(quantityPadded)
|
|
maxFees16 = self.int_to_base16(self.parse_to_int(maxFeesInternal))
|
|
maxFeesPadded = maxFees16.rjust(16, '0')
|
|
encodedMaxFees = self.base16_to_binary(maxFeesPadded)
|
|
encodedAddress = self.base16_to_binary(address)
|
|
message = self.binary_concat(encodedAssetId, encodedQuantity, encodedMaxFees, encodedAddress)
|
|
return message
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://api-doc.hibachi.xyz/#6421625d-3e45-45fa-be9b-d2a0e780c090
|
|
|
|
:param str code: unified currency code, only support USDT
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
withdrawAddress = address[-40:]
|
|
# Get the withdraw fees
|
|
exchangeInfo = await self.publicGetMarketExchangeInfo(params)
|
|
# {
|
|
# "feeConfig": {
|
|
# "depositFees": "0.004518",
|
|
# "tradeMakerFeeRate": "0.00000000",
|
|
# "tradeTakerFeeRate": "0.00020000",
|
|
# "transferFeeRate": "0.00010000",
|
|
# "withdrawalFees": "0.012050"
|
|
# },
|
|
# }
|
|
feeConfig = self.safe_dict(exchangeInfo, 'feeConfig')
|
|
maxFees = self.safe_number(feeConfig, 'withdrawalFees')
|
|
# Generate the signature
|
|
message = self.encode_withdraw_message(amount, maxFees, withdrawAddress)
|
|
signature = self.sign_message(message, self.privateKey)
|
|
request = {
|
|
'accountId': self.get_account_id(),
|
|
'coin': 'USDT',
|
|
'network': 'ARBITRUM',
|
|
'withdrawAddress': withdrawAddress,
|
|
'selfWithdrawal': False,
|
|
'quantity': self.number_to_string(amount),
|
|
'maxFees': self.number_to_string(maxFees),
|
|
'signature': signature,
|
|
}
|
|
await self.privatePostCapitalWithdraw(self.extend(request, params))
|
|
# At self time the response body is empty. A 200 response means the withdraw request is accepted and sent to process
|
|
#
|
|
# {}
|
|
#
|
|
return {
|
|
'info': None,
|
|
'id': None,
|
|
'txid': None,
|
|
'timestamp': self.milliseconds(),
|
|
'datetime': None,
|
|
'address': None,
|
|
'addressFrom': None,
|
|
'addressTo': withdrawAddress,
|
|
'tag': None,
|
|
'tagFrom': None,
|
|
'tagTo': None,
|
|
'type': 'withdrawal',
|
|
'amount': amount,
|
|
'currency': code,
|
|
'status': 'pending',
|
|
'fee': {'currency': 'USDT', 'cost': maxFees},
|
|
'network': 'ARBITRUM',
|
|
'updated': None,
|
|
'comment': None,
|
|
'internal': None,
|
|
}
|
|
|
|
def nonce(self):
|
|
return self.milliseconds()
|
|
|
|
def sign_message(self, message, privateKey):
|
|
if len(privateKey) == 44:
|
|
# For Exchange Managed account, the key length is 44 and we use HMAC to sign the message
|
|
return self.hmac(message, self.encode(privateKey), hashlib.sha256, 'hex')
|
|
else:
|
|
# For Trustless account, the key length is 66 including '0x' and we use ECDSA to sign the message
|
|
hash = self.hash(message, 'sha256', 'hex')
|
|
signature = self.ecdsa(hash[-64:], privateKey[-64:], 'secp256k1', None)
|
|
r = signature['r']
|
|
s = signature['s']
|
|
v = self.int_to_base16(signature['v'])
|
|
return r.rjust(64, '0') + s.rjust(64, '0') + v.rjust(2, '0')
|
|
|
|
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches the state of the open orders on the orderbook
|
|
|
|
https://api-doc.hibachi.xyz/#4abb30c4-e5c7-4b0f-9ade-790111dbfa47
|
|
|
|
:param str symbol: unified symbol of the market
|
|
:param int [limit]: currently unused
|
|
:param dict [params]: extra parameters to be passed -- see documentation link above
|
|
:returns dict: A dictionary containg `orderbook information <https://docs.ccxt.com/#/?id=order-book-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market: Market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = await self.publicGetMarketDataOrderbook(self.extend(request, params))
|
|
formattedResponse = {}
|
|
formattedResponse['ask'] = self.safe_list(self.safe_dict(response, 'ask'), 'levels')
|
|
formattedResponse['bid'] = self.safe_list(self.safe_dict(response, 'bid'), 'levels')
|
|
# {
|
|
# "ask": {
|
|
# "endPrice": "3512.63",
|
|
# "levels": [
|
|
# {
|
|
# "price": "3511.93",
|
|
# "quantity": "0.284772482"
|
|
# },
|
|
# {
|
|
# "price": "3512.28",
|
|
# "quantity": "0.569544964"
|
|
# },
|
|
# {
|
|
# "price": "3512.63",
|
|
# "quantity": "0.854317446"
|
|
# }
|
|
# ],
|
|
# "startPrice": "3511.93"
|
|
# },
|
|
# "bid": {
|
|
# "endPrice": "3510.87",
|
|
# "levels": [
|
|
# {
|
|
# "price": "3515.39",
|
|
# "quantity": "2.345153070"
|
|
# },
|
|
# {
|
|
# "price": "3511.22",
|
|
# "quantity": "0.284772482"
|
|
# },
|
|
# {
|
|
# "price": "3510.87",
|
|
# "quantity": "0.569544964"
|
|
# }
|
|
# ],
|
|
# "startPrice": "3515.39"
|
|
# }
|
|
# }
|
|
return self.parse_order_book(formattedResponse, symbol, self.milliseconds(), 'bid', 'ask', 'price', 'quantity')
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
|
|
https://api-doc.hibachi.xyz/#0adbf143-189f-40e0-afdc-88af4cba3c79
|
|
|
|
fetch all trades made by the user
|
|
: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
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request = {'accountId': self.get_account_id()}
|
|
response = await self.privateGetTradeAccountTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "trades": [
|
|
# {
|
|
# "askAccountId": 221,
|
|
# "askOrderId": 589168494921909200,
|
|
# "bidAccountId": 132,
|
|
# "bidOrderId": 589168494829895700,
|
|
# "fee": "0.000477",
|
|
# "id": 199511136,
|
|
# "orderType": "MARKET",
|
|
# "price": "119257.90000",
|
|
# "quantity": "0.0000200000",
|
|
# "realizedPnl": "-0.000352",
|
|
# "side": "Sell",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "timestamp": 1752543391
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'trades')
|
|
return self.parse_trades(trades, market, since, limit, params)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# [
|
|
# {
|
|
# "close": "3704.751036",
|
|
# "high": "3716.530378",
|
|
# "interval": "1h",
|
|
# "low": "3699.627883",
|
|
# "open": "3716.406894",
|
|
# "timestamp": 1712628000,
|
|
# "volumeNotional": "1637355.846362"
|
|
# }
|
|
# ]
|
|
#
|
|
return [
|
|
self.safe_integer_product(ohlcv, 'timestamp', 1000),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'volumeNotional'),
|
|
]
|
|
|
|
async def fetch_open_orders(self, symbol: str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches all current open orders
|
|
|
|
https://api-doc.hibachi.xyz/#3243f8a0-086c-44c5-ab8a-71bbb7bab403
|
|
|
|
:param str [symbol]: unified market symbol to filter by
|
|
:param int [since]: milisecond timestamp of the earliest order
|
|
:param int [limit]: the maximum number of open orders to return
|
|
:param dict [params]: extra parameters
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetTradeOrders(self.extend(request, params))
|
|
# [
|
|
# {
|
|
# "accountId": 12452,
|
|
# "availableQuantity": "0.0000230769",
|
|
# "contractId": 2,
|
|
# "creationTime": 1752684501,
|
|
# "orderId": "589205486123876352",
|
|
# "orderType": "LIMIT",
|
|
# "price": "130000.00000",
|
|
# "side": "ASK",
|
|
# "status": "PLACED",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "totalQuantity": "0.0000230769"
|
|
# },
|
|
# {
|
|
# "accountId": 12452,
|
|
# "availableQuantity": "1.234000000",
|
|
# "contractId": 1,
|
|
# "creationTime": 1752240682,
|
|
# "orderId": "589089141754429441",
|
|
# "orderType": "LIMIT",
|
|
# "price": "1.234000",
|
|
# "side": "BID",
|
|
# "status": "PLACED",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "totalQuantity": "1.234000000"
|
|
# }
|
|
# ]
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
|
|
https://api-doc.hibachi.xyz/#4f0eacec-c61e-4d51-afb3-23c51c2c6bac
|
|
|
|
fetches historical candlestick data containing the close, high, low, open prices, interval and the volumeNotional
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms 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)
|
|
timeframe = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'interval': timeframe,
|
|
}
|
|
if since is not None:
|
|
request['fromMs'] = since
|
|
until: Int = None
|
|
until, params = self.handle_option_and_params(params, 'fetchOHLCV', 'until')
|
|
if until is not None:
|
|
request['toMs'] = until
|
|
response = await self.publicGetMarketDataKlines(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "close": "3704.751036",
|
|
# "high": "3716.530378",
|
|
# "interval": "1h",
|
|
# "low": "3699.627883",
|
|
# "open": "3716.406894",
|
|
# "timestamp": 1712628000,
|
|
# "volumeNotional": "1637355.846362"
|
|
# }
|
|
# ]
|
|
#
|
|
klines = self.safe_list(response, 'klines', [])
|
|
return self.parse_ohlcvs(klines, market, timeframe, since, limit)
|
|
|
|
async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://api-doc.hibachi.xyz/#69aafedb-8274-4e21-bbaf-91dace8b8f31
|
|
|
|
:param str[] [symbols]: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetTradeAccountInfo(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "assets": [
|
|
# {
|
|
# "quantity": "14.130626",
|
|
# "symbol": "USDT"
|
|
# }
|
|
# ],
|
|
# "balance": "14.186087",
|
|
# "maximalWithdraw": "4.152340",
|
|
# "numFreeTransfersRemaining": 96,
|
|
# "positions": [
|
|
# {
|
|
# "direction": "Short",
|
|
# "entryNotional": "10.302213",
|
|
# "notionalValue": "10.225008",
|
|
# "quantity": "0.004310550",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "unrealizedFundingPnl": "0.000000",
|
|
# "unrealizedTradingPnl": "0.077204"
|
|
# },
|
|
# {
|
|
# "direction": "Short",
|
|
# "entryNotional": "2.000016",
|
|
# "notionalValue": "1.999390",
|
|
# "quantity": "0.0000328410",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "unrealizedFundingPnl": "0.000000",
|
|
# "unrealizedTradingPnl": "0.000625"
|
|
# },
|
|
# {
|
|
# "direction": "Short",
|
|
# "entryNotional": "2.000015",
|
|
# "notionalValue": "2.022384",
|
|
# "quantity": "0.01470600",
|
|
# "symbol": "SOL/USDT-P",
|
|
# "unrealizedFundingPnl": "0.000000",
|
|
# "unrealizedTradingPnl": "-0.022369"
|
|
# }
|
|
# ],
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'positions', [])
|
|
return self.parse_positions(data, symbols)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# {
|
|
# "direction": "Short",
|
|
# "entryNotional": "10.302213",
|
|
# "notionalValue": "10.225008",
|
|
# "quantity": "0.004310550",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "unrealizedFundingPnl": "0.000000",
|
|
# "unrealizedTradingPnl": "0.077204"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
side = self.safe_string_lower(position, 'direction')
|
|
quantity = self.safe_string(position, 'quantity')
|
|
unrealizedFunding = self.safe_string(position, 'unrealizedFundingPnl', '0')
|
|
unrealizedTrading = self.safe_string(position, 'unrealizedTradingPnl', '0')
|
|
unrealizedPnl = Precise.string_add(unrealizedFunding, unrealizedTrading)
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': None,
|
|
'symbol': symbol,
|
|
'entryPrice': self.safe_string(position, 'average_entry_price'),
|
|
'markPrice': None,
|
|
'notional': self.safe_string(position, 'notionalValue'),
|
|
'collateral': None,
|
|
'unrealizedPnl': unrealizedPnl,
|
|
'side': side,
|
|
'contracts': self.parse_number(quantity),
|
|
'contractSize': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'hedged': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'leverage': None,
|
|
'liquidationPrice': None,
|
|
'marginRatio': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
})
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
endpoint = '/' + self.implode_params(path, params)
|
|
url = self.urls['api'][api] + endpoint
|
|
headers = {'Hibachi-Client': 'HibachiCCXT/unversioned'}
|
|
if method == 'GET':
|
|
request = self.omit(params, self.extract_params(path))
|
|
query = self.urlencode(request)
|
|
if len(query) != 0:
|
|
url += '?' + query
|
|
if method == 'POST' or method == 'PUT' or method == 'DELETE':
|
|
headers['Content-Type'] = 'application/json'
|
|
body = self.json(params)
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
headers['Authorization'] = self.apiKey
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
if 'status' in response:
|
|
#
|
|
# {"errorCode":4,"message":"Invalid input: Invalid quantity: 0","status":"failed"}
|
|
#
|
|
status = self.safe_string(response, 'status')
|
|
if status == 'failed':
|
|
code = self.safe_string(response, 'errorCode')
|
|
feedback = self.id + ' ' + body
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
|
|
message = self.safe_string(response, 'message')
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|
|
|
|
def parse_transaction_type(self, type):
|
|
types: dict = {
|
|
'deposit': 'transaction',
|
|
'withdrawal': 'transaction',
|
|
'transfer-in': 'transfer',
|
|
'transfer-out': 'transfer',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def parse_transaction_status(self, status):
|
|
statuses: dict = {
|
|
'pending': 'pending',
|
|
'claimable': 'pending',
|
|
'completed': 'ok',
|
|
'failed': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
|
transactionType = self.safe_string(item, 'transactionType')
|
|
timestamp = None
|
|
type = None
|
|
direction = None
|
|
amount = None
|
|
fee = None
|
|
referenceId = None
|
|
referenceAccount = None
|
|
status = None
|
|
if transactionType is None:
|
|
# response from TradeAccountTradingHistory
|
|
timestamp = self.safe_integer_product(item, 'timestamp', 1000)
|
|
type = 'trade'
|
|
amountStr = self.safe_string(item, 'realizedPnl')
|
|
if Precise.string_lt(amountStr, '0'):
|
|
direction = 'out'
|
|
amountStr = Precise.string_neg(amountStr)
|
|
else:
|
|
direction = 'in'
|
|
amount = self.parse_number(amountStr)
|
|
fee = {'currency': 'USDT', 'cost': self.safe_number(item, 'fee')}
|
|
status = 'ok'
|
|
else:
|
|
# response from CapitalHistory
|
|
timestamp = self.safe_integer_product(item, 'timestampSec', 1000)
|
|
amount = self.safe_number(item, 'quantity')
|
|
direction = 'in' if (transactionType == 'deposit' or transactionType == 'transfer-in') else 'out'
|
|
type = self.parse_transaction_type(transactionType)
|
|
status = self.parse_transaction_status(self.safe_string(item, 'status'))
|
|
if transactionType == 'transfer-in':
|
|
referenceAccount = self.safe_string(item, 'srcAccountId')
|
|
elif transactionType == 'transfer-out':
|
|
referenceAccount = self.safe_string(item, 'receivingAccountId')
|
|
referenceId = self.safe_string(item, 'transactionHash')
|
|
return self.safe_ledger_entry({
|
|
'id': self.safe_string(item, 'id'),
|
|
'currency': self.currency('USDT'),
|
|
'account': self.number_to_string(self.accountId),
|
|
'referenceAccount': referenceAccount,
|
|
'referenceId': referenceId,
|
|
'status': status,
|
|
'amount': amount,
|
|
'before': None,
|
|
'after': None,
|
|
'fee': fee,
|
|
'direction': direction,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'type': type,
|
|
'info': item,
|
|
}, currency)
|
|
|
|
async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
|
"""
|
|
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
|
|
|
https://api-doc.hibachi.xyz/#35125e3f-d154-4bfd-8276-a48bb1c62020
|
|
|
|
:param str [code]: unified currency code, default is None
|
|
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
|
:param int [limit]: max number of ledger entries to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency('USDT')
|
|
request = {'accountId': self.get_account_id()}
|
|
rawPromises = [
|
|
self.privateGetCapitalHistory(self.extend(request, params)),
|
|
self.privateGetTradeAccountTradingHistory(self.extend(request, params)),
|
|
]
|
|
promises = await asyncio.gather(*rawPromises)
|
|
responseCapitalHistory = promises[0]
|
|
#
|
|
# {
|
|
# "transactions": [
|
|
# {
|
|
# "assetId": 1,
|
|
# "blockNumber": 358396669,
|
|
# "chain": "Arbitrum",
|
|
# "etaTsSec": null,
|
|
# "id": 358396669,
|
|
# "quantity": "0.999500",
|
|
# "status": "pending",
|
|
# "timestampSec": 1752692872,
|
|
# "token": "USDT",
|
|
# "transactionHash": "0x408e48881e0ba77d8638e3fe57bc06bdec513ddaa8b672e0aefa7e22e2f18b5e",
|
|
# "transactionType": "deposit"
|
|
# },
|
|
# {
|
|
# "assetId": 1,
|
|
# "etaTsSec": null,
|
|
# "id": 13116,
|
|
# "instantWithdrawalChain": null,
|
|
# "instantWithdrawalToken": null,
|
|
# "isInstantWithdrawal": False,
|
|
# "quantity": "0.040000",
|
|
# "status": "completed",
|
|
# "timestampSec": 1752542708,
|
|
# "transactionHash": "0xe89cf90b2408d1a273dc9427654145def102d9449e5e2cfc10690ccffc3d7e28",
|
|
# "transactionType": "withdrawal",
|
|
# "withdrawalAddress": "0x23625d5fc6a6e32638d908eb4c3a3415e5121f76"
|
|
# },
|
|
# {
|
|
# "assetId": 1,
|
|
# "id": 167,
|
|
# "quantity": "10.000000",
|
|
# "srcAccountId": 175,
|
|
# "srcAddress": "0xc2f77ce029438a3fdfe68ddee25991a9fb985a86",
|
|
# "status": "completed",
|
|
# "timestampSec": 1732224729,
|
|
# "transactionType": "transfer-in"
|
|
# },
|
|
# {
|
|
# "assetId": 1,
|
|
# "id": 170,
|
|
# "quantity": "10.000000",
|
|
# "receivingAccountId": 175,
|
|
# "receivingAddress": "0xc2f77ce029438a3fdfe68ddee25991a9fb985a86",
|
|
# "status": "completed",
|
|
# "timestampSec": 1732225631,
|
|
# "transactionType": "transfer-out"
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
rowsCapitalHistory = self.safe_list(responseCapitalHistory, 'transactions')
|
|
responseTradingHistory = promises[1]
|
|
#
|
|
# {
|
|
# "tradingHistory": [
|
|
# {
|
|
# "eventType": "MARKET",
|
|
# "fee": "0.000008",
|
|
# "priceOrFundingRate": "119687.82481",
|
|
# "quantity": "0.0000003727",
|
|
# "realizedPnl": "0.004634",
|
|
# "side": "Sell",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "timestamp": 1752522571
|
|
# },
|
|
# {
|
|
# "eventType": "FundingEvent",
|
|
# "fee": "0",
|
|
# "priceOrFundingRate": "0.000203",
|
|
# "quantity": "0.0000003727",
|
|
# "realizedPnl": "-0.000009067899008751979",
|
|
# "side": "Long",
|
|
# "symbol": "BTC/USDT-P",
|
|
# "timestamp": 1752508800
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
rowsTradingHistory = self.safe_list(responseTradingHistory, 'tradingHistory')
|
|
rows = self.array_concat(rowsCapitalHistory, rowsTradingHistory)
|
|
return self.parse_ledger(rows, currency, since, limit, params)
|
|
|
|
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch deposit address for given currency and chain. currently, we have a single EVM address across multiple EVM chains. Note: This method is currently only supported for trustless accounts
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters for API
|
|
:param str [params.publicKey]: your public key, you can get it from UI after creating API key
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
request = {
|
|
'publicKey': self.safe_string(params, 'publicKey'),
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetCapitalDepositInfo(self.extend(request, params))
|
|
# {
|
|
# "depositAddressEvm": "0x0b95d90b9345dadf1460bd38b9f4bb0d2f4ed788"
|
|
# }
|
|
return {
|
|
'info': response,
|
|
'currency': 'USDT',
|
|
'network': 'ARBITRUM',
|
|
'address': self.safe_string(response, 'depositAddressEvm'),
|
|
'tag': None,
|
|
}
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
timestamp = self.safe_integer_product(transaction, 'timestampSec', 1000)
|
|
address = self.safe_string(transaction, 'withdrawalAddress')
|
|
transactionType = self.safe_string(transaction, 'transactionType')
|
|
if transactionType != 'deposit' and transactionType != 'withdrawal':
|
|
transactionType = self.parse_transaction_type(transactionType)
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'id'),
|
|
'txid': self.safe_string(transaction, 'transactionHash'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': 'ARBITRUM', # Currently the exchange only exists on Arbitrum,
|
|
'address': address,
|
|
'addressTo': address,
|
|
'addressFrom': None,
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'tagFrom': None,
|
|
'type': transactionType,
|
|
'amount': self.safe_number(transaction, 'quantity'),
|
|
'currency': 'USDT',
|
|
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
|
'updated': None,
|
|
'internal': None,
|
|
'comment': None,
|
|
'fee': None,
|
|
}
|
|
|
|
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch deposits made to account
|
|
|
|
https://api-doc.hibachi.xyz/#35125e3f-d154-4bfd-8276-a48bb1c62020
|
|
|
|
:param str [code]: unified currency code
|
|
:param int [since]: filter by earliest timestamp(ms)
|
|
:param int [limit]: maximum number of deposits to be returned
|
|
:param dict [params]: extra parameters to be passed to API
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
currency = self.safe_currency(code)
|
|
request = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetCapitalHistory(self.extend(request, params))
|
|
# {
|
|
# "transactions": [
|
|
# {
|
|
# "assetId": 1,
|
|
# "blockNumber": 0,
|
|
# "chain": null,
|
|
# "etaTsSec": 1752758789,
|
|
# "id": 42688,
|
|
# "quantity": "6.130000",
|
|
# "status": "completed",
|
|
# "timestampSec": 1752758788,
|
|
# "token": null,
|
|
# "transactionHash": "0x8dcd7bd1155b5624fb5e38a1365888f712ec633a57434340e05080c70b0e3bba",
|
|
# "transactionType": "deposit"
|
|
# },
|
|
# {
|
|
# "assetId": 1,
|
|
# "etaTsSec": null,
|
|
# "id": 12993,
|
|
# "instantWithdrawalChain": null,
|
|
# "instantWithdrawalToken": null,
|
|
# "isInstantWithdrawal": False,
|
|
# "quantity": "0.111930",
|
|
# "status": "completed",
|
|
# "timestampSec": 1752387891,
|
|
# "transactionHash": "0x32ab5fe5b90f6d753bab83523ebc8465eb9daef54580e13cb9ff031d400c5620",
|
|
# "transactionType": "withdrawal",
|
|
# "withdrawalAddress": "0x43f15ef2ef2ab5e61e987ee3d652a5872aea8a6c"
|
|
# },
|
|
# ]
|
|
# }
|
|
transactions = self.safe_list(response, 'transactions')
|
|
deposits = []
|
|
for i in range(0, len(transactions)):
|
|
transaction = transactions[i]
|
|
if self.safe_string(transaction, 'transactionType') == 'deposit':
|
|
deposits.append(transaction)
|
|
return self.parse_transactions(deposits, currency, since, limit, params)
|
|
|
|
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch withdrawals made from account
|
|
|
|
https://api-doc.hibachi.xyz/#35125e3f-d154-4bfd-8276-a48bb1c62020
|
|
|
|
:param str [code]: unified currency code
|
|
:param int [since]: filter by earliest timestamp(ms)
|
|
:param int [limit]: maximum number of deposits to be returned
|
|
:param dict [params]: extra parameters to be passed to API
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
currency = self.safe_currency(code)
|
|
request = {
|
|
'accountId': self.get_account_id(),
|
|
}
|
|
response = await self.privateGetCapitalHistory(self.extend(request, params))
|
|
# {
|
|
# "transactions": [
|
|
# {
|
|
# "assetId": 1,
|
|
# "blockNumber": 0,
|
|
# "chain": null,
|
|
# "etaTsSec": 1752758789,
|
|
# "id": 42688,
|
|
# "quantity": "6.130000",
|
|
# "status": "completed",
|
|
# "timestampSec": 1752758788,
|
|
# "token": null,
|
|
# "transactionHash": "0x8dcd7bd1155b5624fb5e38a1365888f712ec633a57434340e05080c70b0e3bba",
|
|
# "transactionType": "deposit"
|
|
# },
|
|
# {
|
|
# "assetId": 1,
|
|
# "etaTsSec": null,
|
|
# "id": 12993,
|
|
# "instantWithdrawalChain": null,
|
|
# "instantWithdrawalToken": null,
|
|
# "isInstantWithdrawal": False,
|
|
# "quantity": "0.111930",
|
|
# "status": "completed",
|
|
# "timestampSec": 1752387891,
|
|
# "transactionHash": "0x32ab5fe5b90f6d753bab83523ebc8465eb9daef54580e13cb9ff031d400c5620",
|
|
# "transactionType": "withdrawal",
|
|
# "withdrawalAddress": "0x43f15ef2ef2ab5e61e987ee3d652a5872aea8a6c"
|
|
# },
|
|
# ]
|
|
# }
|
|
transactions = self.safe_list(response, 'transactions')
|
|
withdrawals = []
|
|
for i in range(0, len(transactions)):
|
|
transaction = transactions[i]
|
|
if self.safe_string(transaction, 'transactionType') == 'withdrawal':
|
|
withdrawals.append(transaction)
|
|
return self.parse_transactions(withdrawals, currency, since, limit, params)
|
|
|
|
async def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
|
|
http://api-doc.hibachi.xyz/#b5c6a3bc-243d-4d35-b6d4-a74c92495434
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the exchange server
|
|
"""
|
|
response = await self.publicGetExchangeUtcTimestamp(params)
|
|
#
|
|
# {"timestampMs":1754077574040}
|
|
#
|
|
return self.safe_integer(response, 'timestampMs')
|
|
|
|
async def fetch_open_interest(self, symbol: str, params={}):
|
|
"""
|
|
retrieves the open interest of a contract trading pair
|
|
|
|
https://api-doc.hibachi.xyz/#bc34e8ae-e094-4802-8d56-3efe3a7bad49
|
|
|
|
:param str symbol: 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()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = await self.publicGetMarketDataOpenInterest(self.extend(request, params))
|
|
#
|
|
# {"totalQuantity" : "2.3299770166"}
|
|
#
|
|
timestamp = self.milliseconds()
|
|
return self.safe_open_interest({
|
|
'symbol': symbol,
|
|
'openInterestAmount': self.safe_string(response, 'totalQuantity'),
|
|
'openInterestValue': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': response,
|
|
}, market)
|
|
|
|
async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
|
"""
|
|
fetch the current funding rate
|
|
|
|
https://api-doc.hibachi.xyz/#bca696ca-b9b2-4072-8864-5d6b8c09807e
|
|
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = await self.publicGetMarketDataPrices(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "askPrice": "3514.650296",
|
|
# "bidPrice": "3513.596112",
|
|
# "fundingRateEstimation": {
|
|
# "estimatedFundingRate": "0.000001",
|
|
# "nextFundingTimestamp": 1712707200
|
|
# },
|
|
# "markPrice": "3514.288858",
|
|
# "spotPrice": "3514.715000",
|
|
# "symbol": "ETH/USDT-P",
|
|
# "tradePrice": "2372.746570"
|
|
# }
|
|
#
|
|
funding = self.safe_dict(response, 'fundingRateEstimation', {})
|
|
timestamp = self.milliseconds()
|
|
nextFundingTimestamp = self.safe_integer_product(funding, 'nextFundingTimestamp', 1000)
|
|
return {
|
|
'info': funding,
|
|
'symbol': market['symbol'],
|
|
'markPrice': None,
|
|
'indexPrice': None,
|
|
'interestRate': self.parse_number('0'),
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'fundingRate': self.safe_number(funding, 'estimatedFundingRate'),
|
|
'fundingTimestamp': nextFundingTimestamp,
|
|
'fundingDatetime': self.iso8601(nextFundingTimestamp),
|
|
'nextFundingRate': None,
|
|
'nextFundingTimestamp': None,
|
|
'nextFundingDatetime': None,
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'interval': '8h',
|
|
}
|
|
|
|
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://api-doc.hibachi.xyz/#4abb30c4-e5c7-4b0f-9ade-790111dbfa47
|
|
|
|
: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
|
|
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = await self.publicGetMarketDataFundingRates(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "data": [
|
|
# {
|
|
# "contractId": 2,
|
|
# "fundingTimestamp": 1753488000,
|
|
# "fundingRate": "0.000137",
|
|
# "indexPrice": "117623.65010"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data')
|
|
rates = []
|
|
for i in range(0, len(data)):
|
|
entry = data[i]
|
|
timestamp = self.safe_integer_product(entry, 'fundingTimestamp', 1000)
|
|
rates.append({
|
|
'info': entry,
|
|
'symbol': symbol,
|
|
'fundingRate': self.safe_number(entry, 'fundingRate'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
})
|
|
sorted = self.sort_by(rates, 'timestamp')
|
|
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|