# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code from ccxt.base.exchange import Exchange from ccxt.abstract.bydfi import ImplicitAPI import hashlib from ccxt.base.types import Any, Balances, Currency, Int, Leverage, MarginMode, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, Transaction, FundingRateHistory, TransferEntry from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import PermissionDenied from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import BadRequest from ccxt.base.errors import InsufficientFunds from ccxt.base.errors import NotSupported from ccxt.base.errors import RateLimitExceeded from ccxt.base.decimal_to_precision import TICK_SIZE from ccxt.base.precise import Precise class bydfi(Exchange, ImplicitAPI): def describe(self) -> Any: return self.deep_extend(super(bydfi, self).describe(), { 'id': 'bydfi', 'name': 'BYDFi', 'countries': ['SG'], # Singapore todo check 'rateLimit': 50, # 20 requests per second 'version': 'v1', 'certified': False, 'pro': True, 'has': { 'CORS': None, 'spot': False, 'margin': False, 'swap': True, 'future': False, 'option': False, 'addMargin': False, 'borrowCrossMargin': False, 'borrowIsolatedMargin': False, 'borrowMargin': False, 'cancelAllOrders': True, 'cancelOrder': False, 'cancelOrders': False, 'cancelOrdersWithClientOrderId': False, 'cancelOrderWithClientOrderId': False, 'closeAllPositions': False, 'closePosition': False, 'createDepositAddress': False, 'createLimitBuyOrder': False, 'createLimitOrder': True, 'createLimitSellOrder': False, 'createMarketBuyOrder': False, 'createMarketBuyOrderWithCost': False, 'createMarketOrder': True, 'createMarketOrderWithCost': False, 'createMarketSellOrder': False, 'createMarketSellOrderWithCost': False, 'createOrder': True, 'createOrders': True, 'createOrderWithTakeProfitAndStopLoss': False, 'createPostOnlyOrder': True, 'createReduceOnlyOrder': True, 'createStopLimitOrder': True, 'createStopLossOrder': True, 'createStopMarketOrder': False, 'createStopOrder': False, 'createTakeProfitOrder': True, 'createTrailingAmountOrder': False, 'createTrailingPercentOrder': True, 'createTriggerOrder': False, 'deposit': False, 'editOrder': True, 'editOrders': True, 'editOrderWithClientOrderId': True, 'fetchAccounts': False, 'fetchBalance': True, 'fetchBidsAsks': False, 'fetchBorrowInterest': False, 'fetchBorrowRate': False, 'fetchBorrowRateHistories': False, 'fetchBorrowRateHistory': False, 'fetchBorrowRates': False, 'fetchBorrowRatesPerSymbol': False, 'fetchCanceledAndClosedOrders': True, 'fetchCanceledOrders': False, 'fetchClosedOrder': False, 'fetchClosedOrders': False, 'fetchConvertCurrencies': False, 'fetchConvertQuote': False, 'fetchConvertTrade': False, 'fetchConvertTradeHistory': False, 'fetchCrossBorrowRate': False, 'fetchCrossBorrowRates': False, 'fetchCurrencies': False, 'fetchDeposit': False, 'fetchDepositAddress': False, 'fetchDepositAddresses': False, 'fetchDepositAddressesByNetwork': False, 'fetchDeposits': True, 'fetchDepositsWithdrawals': False, 'fetchDepositWithdrawFee': False, 'fetchDepositWithdrawFees': False, 'fetchFundingHistory': False, 'fetchFundingInterval': False, 'fetchFundingIntervals': False, 'fetchFundingRate': True, 'fetchFundingRateHistory': True, 'fetchFundingRates': False, 'fetchGreeks': False, 'fetchIndexOHLCV': False, 'fetchIsolatedBorrowRate': False, 'fetchIsolatedBorrowRates': False, 'fetchIsolatedPositions': False, 'fetchL2OrderBook': True, 'fetchL3OrderBook': False, 'fetchLastPrices': False, 'fetchLedger': False, 'fetchLedgerEntry': False, 'fetchLeverage': True, 'fetchLeverages': False, 'fetchLeverageTiers': False, 'fetchLiquidations': False, 'fetchLongShortRatio': False, 'fetchLongShortRatioHistory': False, 'fetchMarginAdjustmentHistory': False, 'fetchMarginMode': True, 'fetchMarginModes': False, 'fetchMarketLeverageTiers': False, 'fetchMarkets': True, 'fetchMarkOHLCV': False, 'fetchMarkPrices': False, 'fetchMyLiquidations': False, 'fetchMySettlementHistory': False, 'fetchMyTrades': True, 'fetchOHLCV': True, 'fetchOpenInterest': False, 'fetchOpenInterestHistory': False, 'fetchOpenInterests': False, 'fetchOpenOrder': False, 'fetchOpenOrders': True, 'fetchOption': False, 'fetchOptionChain': False, 'fetchOrder': False, 'fetchOrderBook': True, 'fetchOrderBooks': False, 'fetchOrders': False, 'fetchOrdersByStatus': False, 'fetchOrderTrades': False, 'fetchOrderWithClientOrderId': False, 'fetchPosition': False, 'fetchPositionHistory': True, 'fetchPositionMode': True, 'fetchPositions': True, 'fetchPositionsForSymbol': True, 'fetchPositionsHistory': True, 'fetchPositionsRisk': False, 'fetchPremiumIndexOHLCV': False, 'fetchSettlementHistory': False, 'fetchStatus': False, 'fetchTicker': True, 'fetchTickers': True, 'fetchTime': False, 'fetchTrades': True, 'fetchTradingFee': False, 'fetchTradingFees': False, 'fetchTradingLimits': False, 'fetchTransactionFee': False, 'fetchTransactionFees': False, 'fetchTransactions': False, 'fetchTransfer': False, 'fetchTransfers': True, 'fetchUnderlyingAssets': False, 'fetchVolatilityHistory': False, 'fetchWithdrawAddresses': False, 'fetchWithdrawal': False, 'fetchWithdrawals': True, 'fetchWithdrawalWhitelist': False, 'reduceMargin': False, 'repayCrossMargin': False, 'repayIsolatedMargin': False, 'setLeverage': True, 'setMargin': False, 'setMarginMode': True, 'setPositionMode': True, 'signIn': False, 'transfer': True, 'watchMyLiquidationsForSymbols': False, 'withdraw': False, 'ws': True, }, 'urls': { 'logo': 'https://github.com/user-attachments/assets/bfffb73d-29bd-465d-b75b-98e210491769', 'api': { 'public': 'https://api.bydfi.com/api', 'private': 'https://api.bydfi.com/api', }, 'www': 'https://bydfi.com/', 'doc': 'https://developers.bydfi.com/en/', 'referral': 'https://partner.bydfi.com/j/DilWutCI', }, 'fees': { }, 'api': { 'public': { 'get': { 'v1/public/api_limits': 1, # https://developers.bydfi.com/en/public#inquiry-into-api-rate-limit-configuration 'v1/fapi/market/exchange_info': 1, 'v1/fapi/market/depth': 1, 'v1/fapi/market/trades': 1, 'v1/fapi/market/klines': 1, 'v1/fapi/market/ticker/24hr': 1, 'v1/fapi/market/ticker/price': 1, # https://developers.bydfi.com/en/futures/market#latest-price 'v1/fapi/market/mark_price': 1, # https://developers.bydfi.com/en/futures/market#mark-price 'v1/fapi/market/funding_rate': 1, 'v1/fapi/market/funding_rate_history': 1, 'v1/fapi/market/risk_limit': 1, # https://developers.bydfi.com/en/futures/market#risk-limit }, }, 'private': { 'get': { 'v1/account/assets': 1, 'v1/account/transfer_records': 1, 'v1/spot/deposit_records': 1, 'v1/spot/withdraw_records': 1, 'v1/fapi/trade/open_order': 1, 'v1/fapi/trade/plan_order': 1, 'v1/fapi/trade/leverage': 1, 'v1/fapi/trade/history_order': 1, 'v1/fapi/trade/history_trade': 1, 'v1/fapi/trade/position_history': 1, 'v1/fapi/trade/positions': 1, 'v1/fapi/account/balance': 1, 'v1/fapi/user_data/assets_margin': 1, 'v1/fapi/user_data/position_side/dual': 1, 'v1/agent/teams': 1, # https://developers.bydfi.com/en/agent/#query-kol-subordinate-team-information 'v1/agent/agent_links': 1, # https://developers.bydfi.com/en/agent/#query-kol-invitation-code-list 'v1/agent/regular_overview': 1, # https://developers.bydfi.com/en/agent/#query-kol-direct-client-data-list 'v1/agent/agent_sub_overview': 1, # https://developers.bydfi.com/en/agent/#query-kol-subordinate-affiliate-list 'v1/agent/partener_user_deposit': 1, # https://developers.bydfi.com/en/agent/#check-the-recharge-amount-of-kol-within-one-year 'v1/agent/partener_users_data': 1, # https://developers.bydfi.com/en/agent/#query-kol-subordinate-deposit-and-trading-data 'v1/agent/affiliate_uids': 1, # https://developers.bydfi.com/en/agent/#get-affiliate-uids 'v1/agent/affiliate_commission': 1, # https://developers.bydfi.com/en/agent/#get-affiliate-commission 'v1/agent/internal_withdrawal_status': 1, # https://developers.bydfi.com/en/agent/#get-internal-withdrawal-status }, 'post': { 'v1/account/transfer': 1, 'v1/fapi/trade/place_order': 1, 'v1/fapi/trade/batch_place_order': 1, 'v1/fapi/trade/edit_order': 1, 'v1/fapi/trade/batch_edit_order': 1, 'v1/fapi/trade/cancel_all_order': 1, 'v1/fapi/trade/leverage': 1, 'v1/fapi/trade/batch_leverage_margin': 1, # https://developers.bydfi.com/en/futures/trade#modify-leverage-and-margin-type-with-one-click 'v1/fapi/user_data/margin_type': 1, 'v1/fapi/user_data/position_side/dual': 1, 'v1/agent/internal_withdrawal': 1, # https://developers.bydfi.com/en/agent/#internal-withdrawal }, }, }, 'features': { 'spot': None, 'swap': { 'linear': { 'sandbox': False, 'createOrder': { 'marginMode': False, 'triggerPrice': False, 'triggerPriceType': { 'mark': True, 'last': True, 'index': False, }, 'stopLossPrice': True, 'takeProfitPrice': True, 'attachedStopLossTakeProfit': None, # not supported 'timeInForce': { 'IOC': True, 'FOK': True, 'PO': True, 'GTD': False, }, 'hedged': True, 'selfTradePrevention': False, 'trailing': True, 'iceberg': False, 'leverage': False, 'marketBuyRequiresPrice': False, 'marketBuyByCost': False, }, 'createOrders': { 'max': 5, }, 'fetchMyTrades': { 'marginMode': False, 'daysBack': 182, # 6 months 'limit': 500, 'untilDays': 7, 'symbolRequired': False, }, 'fetchOrder': None, 'fetchOpenOrder': { 'marginMode': False, 'trigger': True, 'trailing': False, 'symbolRequired': True, }, 'fetchOpenOrders': { 'marginMode': False, 'limit': 500, 'trigger': True, 'trailing': False, 'symbolRequired': True, }, 'fetchOrders': None, 'fetchCanceledAndClosedOrders': { 'marginMode': False, 'limit': 500, 'daysBack': 182, # 6 months 'untilDays': 7, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchClosedOrders': None, 'fetchOHLCV': { 'limit': 500, }, }, 'inverse': None, }, 'future': { 'linear': None, 'inverse': None, }, }, 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '12h': '12h', '1d': '1d', }, 'precisionMode': TICK_SIZE, 'exceptions': { 'exact': { '101001': AuthenticationError, # {"code":101001,"message":"Apikey doesn't exist!"} '101103': AuthenticationError, # {"code":101103,"message":"Invalid API-key, IP, or permissions for action."} '102001': BadRequest, # {"code":102001,"message":"Unsupported transfer type"} '102002': PermissionDenied, # {"code":102002,"message":"The current account does not support transfer of self currency"} '401': AuthenticationError, # 401 Unauthorized – Invalid API Key '500': ExchangeError, # 500 Internal Error '501': ExchangeError, # 501 System Busy '506': ExchangeError, # 506 Unknown Request Origin '510': RateLimitExceeded, # 510 Requests Too Frequent '511': AuthenticationError, # 511 Access to the Interface is Forbidden '513': BadRequest, # 513 Invalid Request '514': BadRequest, # 514 Duplicate Request '600': BadRequest, # 600 Parameter Error 'Position does not exist': BadRequest, # {"code":100036,"message":"Position does not exist"} 'Requires transaction permissions': PermissionDenied, # {"code":101107,"message":"Requires transaction permissions"} 'Service error': ExchangeError, # {msg: 'Service error', code: '-1'} 'transfer failed': InsufficientFunds, # {"code":500,"message":"transfer failed","success":false} }, 'broad': { 'is missing': ArgumentsRequired, # {"code":600,"message":"The parameter 'startTime' is missing"} }, }, 'commonCurrencies': { }, 'options': { 'networks': { 'ERC20': 'ETH', # todo add more networks }, 'timeInForce': { 'GTC': 'GTC', # Good Till Cancelled 'FOK': 'FOK', # Fill Or Kill 'IOC': 'IOC', # Immediate Or Cancel 'PO': 'POST_ONLY', # Post Only }, 'accountsByType': { 'spot': 'SPOT', 'swap': 'UMFUTURE', 'funding': 'FUNDING', 'inverse': 'CMFUTURE', }, 'accountsById': { 'SPOT': 'spot', 'UMFUTURE': 'swap', 'FUNDING': 'funding', 'CMFUTURE': 'inverse', }, }, }) def fetch_markets(self, params={}) -> List[Market]: """ retrieves data on all markets for bydfi https://developers.bydfi.com/en/futures/market#fetching-trading-rules-and-pairs :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ response = self.publicGetV1FapiMarketExchangeInfo(params) # # { # "code": "200", # "message": "success", # "data": [ # { # "symbol": "CLANKER-USDT", # "baseAsset": "CLANKER", # "marginAsset": "USDT", # "quoteAsset": "USDT", # "contractFactor": "0.01", # "limitMaxQty": "50000", # "limitMinQty": "1", # "marketMaxQty": "10000", # "marketMinQty": "1", # "pricePrecision": "8", # "basePrecision": "8", # "feeRateTaker": "0.0006", # "feeRateMaker": "0.0002", # "liqFeeRate": "0.0006", # "openBuyLimitRateMax": "0.05", # "openSellLimitRateMax": "100", # "openBuyLimitRateMin": "0.98", # "openSellLimitRateMin": "0.05", # "priceOrderPrecision": "2", # "baseShowPrecision": "2", # "maxLeverageLevel": "20", # "volumePrecision": "2", # "maxLimitOrderNum": "200", # "maxPlanOrderNum": "10", # "reverse": False, # "onboardTime": "1763373600000", # "status": "NORMAL" # }, # ... # ], # "success": True # } data = self.safe_list(response, 'data', []) return self.parse_markets(data) def parse_market(self, market: dict) -> Market: # # { # "symbol": "CLANKER-USDT", # "baseAsset": "CLANKER", # "marginAsset": "USDT", # "quoteAsset": "USDT", # "contractFactor": "0.01", # "limitMaxQty": "50000", # "limitMinQty": "1", # "marketMaxQty": "10000", # "marketMinQty": "1", # "pricePrecision": "8", # "basePrecision": "8", # "feeRateTaker": "0.0006", # "feeRateMaker": "0.0002", # "liqFeeRate": "0.0006", # "openBuyLimitRateMax": "0.05", # "openSellLimitRateMax": "100", # "openBuyLimitRateMin": "0.98", # "openSellLimitRateMin": "0.05", # "priceOrderPrecision": "2", # "baseShowPrecision": "2", # "maxLeverageLevel": "20", # "volumePrecision": "2", # "maxLimitOrderNum": "200", # "maxPlanOrderNum": "10", # "reverse": False, # "onboardTime": "1763373600000", # "status": "NORMAL" # } # id = self.safe_string(market, 'symbol') baseId = self.safe_string(market, 'baseAsset') quoteId = self.safe_string(market, 'quoteAsset') settleId = self.safe_string(market, 'marginAsset') base = self.safe_currency_code(baseId) quote = self.safe_currency_code(quoteId) settle = self.safe_currency_code(settleId) symbol = base + '/' + quote + ':' + settle inverse = self.safe_bool(market, 'reverse') limitMaxQty = self.safe_string(market, 'limitMaxQty') marketMaxQty = self.safe_string(market, 'marketMaxQty') maxAmountString = Precise.string_max(limitMaxQty, marketMaxQty) marketMinQty = self.safe_string(market, 'marketMinQty') limitMinQty = self.safe_string(market, 'limitMinQty') minAmountString = Precise.string_min(marketMinQty, limitMinQty) contractSize = self.safe_string(market, 'contractFactor') pricePrecision = self.parse_precision(self.safe_string(market, 'priceOrderPrecision')) rawAmountPrecision = self.parse_precision(self.safe_string(market, 'volumePrecision')) amountPrecision = Precise.string_div(rawAmountPrecision, contractSize) basePrecision = self.parse_precision(self.safe_string(market, 'basePrecision')) taker = self.safe_number(market, 'feeRateTaker') maker = self.safe_number(market, 'feeRateMaker') maxLeverage = self.safe_number(market, 'maxLeverageLevel') status = self.safe_string(market, 'status') return self.safe_market_structure({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': 'swap', 'spot': False, 'margin': None, 'swap': True, 'future': False, 'option': False, 'active': status == 'NORMAL', 'contract': True, 'linear': not inverse, 'inverse': inverse, 'taker': taker, 'maker': maker, 'contractSize': self.parse_number(contractSize), 'expiry': None, 'expiryDatetime': None, 'strike': None, 'optionType': None, 'precision': { 'amount': self.parse_number(amountPrecision), 'price': self.parse_number(pricePrecision), 'base': self.parse_number(basePrecision), }, 'limits': { 'leverage': { 'min': None, 'max': maxLeverage, }, 'amount': { 'min': self.parse_number(minAmountString), 'max': self.parse_number(maxAmountString), }, 'price': { 'min': None, 'max': None, }, 'cost': { 'min': None, 'max': None, }, }, 'created': self.parse8601(self.safe_string(market, 'createdAt')), 'info': market, }) 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://developers.bydfi.com/en/futures/market#depth-information :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, could be 5, 10, 20, 50, 100, 500 or 1000(default 500) :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.loc]: crypto location, default: us :returns dict: A dictionary of `order book structures ` indexed by market symbols """ self.load_markets() market = self.market(symbol) request = { 'symbol': market['id'], } if limit is not None: request['limit'] = self.get_closest_limit(limit) response = self.publicGetV1FapiMarketDepth(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "lastUpdateId": "221780076", # "symbol": "ETH-USDT", # "asks": [ # { # "price": "2958.21", # "amount": "39478" # }, # ... # ], # "bids": [ # { # "price": "2958.19", # "amount": "174498" # }, # ... # ], # "e": "221780076" # }, # "success": True # } # data = self.safe_dict(response, 'data', {}) timestamp = self.milliseconds() orderBook = self.parse_order_book(data, market['symbol'], timestamp, 'bids', 'asks', 'price', 'amount') orderBook['nonce'] = self.safe_integer(data, 'lastUpdateId') return orderBook def get_closest_limit(self, limit: Int) -> Int: limits = [5, 10, 20, 50, 100, 500, 1000] result = 1000 for i in range(0, len(limits)): if limit <= limits[i]: result = limits[i] break return result 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://developers.bydfi.com/en/futures/market#recent-trades :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch(default 500, max 1000) :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.fromId]: retrieve from which trade ID to start. Default to retrieve the most recent trade records :returns Trade[]: a list of `trade structures ` """ self.load_markets() market = self.market(symbol) request = { 'symbol': market['id'], } if limit is not None: request['limit'] = min(limit, 1000) response = self.publicGetV1FapiMarketTrades(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "id": "7407825178362667008", # "symbol": "ETH-USDT", # "price": "2970.49", # "quantity": "63", # "side": "SELL", # "time": 1766163153218 # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_trades(data, market, since, limit) def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ fetch all trades made by the user https://developers.bydfi.com/en/futures/trade#historical-trades-query :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]: the latest time in ms to fetch trades for :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet :param str [params.orderType]: order type('LIMIT', 'MARKET', 'LIQ', 'LIMIT_CLOSE', 'MARKET_CLOSE', 'STOP', 'TAKE_PROFIT', 'STOP_MARKET', 'TAKE_PROFIT_MARKET' or 'TRAILING_STOP_MARKET') :returns Trade[]: a list of `trade structures ` """ self.load_markets() paginate = self.safe_bool(params, 'paginate', False) if paginate: maxLimit = 500 params = self.omit(params, 'paginate') params = self.extend(params, {'paginationDirection': 'backward'}) paginatedResponse = self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params, maxLimit, True) return self.sort_by(paginatedResponse, 'timestamp') contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchMyTrades', 'contractType', contractType) request: dict = { 'contractType': contractType, } market = None if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] params = self.handle_since_and_until('fetchMyTrades', since, params) if limit is not None: request['limit'] = limit response = self.privateGetV1FapiTradeHistoryTrade(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "orderId": "7408919189505597440", # "wallet": "W001", # "symbol": "ETH-USDC", # "time": "1766423985842", # "dealPrice": "3032.45", # "dealVolume": "1", # "fee": "0", # "side": "BUY", # "type": "2", # "liqPrice": null, # "basePrecision": "8", # "baseShowPrecision": "2", # "tradePnl": "0", # "marginType": "CROSS", # "leverageLevel": 1 # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_trades(data, market, since, limit) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # fetchTrades # { # "id": "7407825178362667008", # "symbol": "ETH-USDT", # "price": "2970.49", # "quantity": "63", # "side": "SELL", # "time": 1766163153218 # } # # fetchMyTrades # { # "orderId": "7408919189505597440", # "wallet": "W001", # "symbol": "ETH-USDC", # "time": "1766423985842", # "dealPrice": "3032.45", # "dealVolume": "1", # "fee": "0", # "side": "BUY", # "type": "2", # "liqPrice": null, # "basePrecision": "8", # "baseShowPrecision": "2", # "tradePnl": "0", # "marginType": "CROSS", # "leverageLevel": 1 # } # marketId = self.safe_string(trade, 'symbol') market = self.safe_market(marketId, market) timestamp = self.safe_integer(trade, 'time') fee = None rawType = self.safe_string(trade, 'type') feeCost = self.safe_string(trade, 'fee') if feeCost is not None: fee = { 'cost': feeCost, 'currency': market['settle'], } orderId = self.safe_string(trade, 'orderId') side: Str = None # fetchMyTrades always returns side BUY if orderId is None: # from fetchTrades side = self.safe_string_lower(trade, 'side') return self.safe_trade({ 'info': trade, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': market['symbol'], 'id': self.safe_string(trade, 'id'), 'order': orderId, 'type': self.parse_trade_type(rawType), 'side': side, 'takerOrMaker': None, 'price': self.safe_string_2(trade, 'price', 'dealPrice'), 'amount': self.safe_string_2(trade, 'quantity', 'dealVolume'), 'cost': None, 'fee': fee, }, market) def parse_trade_type(self, type: Str) -> Str: types = { '1': 'limit', '2': 'market', '3': 'liquidation', } return self.safe_string(types, type, type) def fetch_ohlcv(self, symbol: str, timeframe='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://developers.bydfi.com/en/futures/market#candlestick-data :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(max 500) :param dict [params]: extra parameters specific to the bitteam 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 """ self.load_markets() maxLimit = 500 # docs says max 1500, but in practice only 500 works paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate') if paginate: return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, maxLimit) market = self.market(symbol) interval = self.safe_string(self.timeframes, timeframe, timeframe) request = { 'symbol': market['id'], 'interval': interval, } startTime = since numberOfCandles = limit if limit else maxLimit until = None until, params = self.handle_option_and_params(params, 'fetchOHLCV', 'until') now = self.milliseconds() duration = self.parse_timeframe(timeframe) * 1000 timeDelta = duration * numberOfCandles if startTime is None and until is None: startTime = now - timeDelta until = now elif until is None: until = startTime + timeDelta if until > now: until = now elif startTime is None: startTime = until - timeDelta request['startTime'] = startTime request['endTime'] = until if limit is not None: request['limit'] = limit response = self.publicGetV1FapiMarketKlines(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "s": "ETH-USDT", # "t": "1766166000000", # "c": "2964.990000000000000000", # "o": "2967.830000000000000000", # "h": "2967.830000000000000000", # "l": "2964.130000000000000000", # "v": "20358.000000000000000000" # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) result = self.parse_ohlcvs(data, market, timeframe, since, limit) return result def parse_ohlcv(self, ohlcv, market: Market = None) -> list: # # { # "s": "ETH-USDT", # "t": "1766166000000", # "c": "2964.990000000000000000", # "o": "2967.830000000000000000", # "h": "2967.830000000000000000", # "l": "2964.130000000000000000", # "v": "20358.000000000000000000" # } # 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'), ] def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ https://developers.bydfi.com/en/futures/market#24hr-price-change-statistics fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `ticker structures ` """ self.load_markets() response = self.publicGetV1FapiMarketTicker24hr(params) # # { # "code": 200, # "message": "success", # "data": [ # { # "symbol": "BTC-USDT", # "open": "86452.9", # "high": "89371.2", # "low": "84418.5", # "last": "87050.3", # "vol": "12938783", # "time": 1766169423872 # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_tickers(data, symbols) def fetch_ticker(self, symbol: str, params={}) -> Ticker: """ fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://developers.bydfi.com/en/futures/market#24hr-price-change-statistics :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } response = self.publicGetV1FapiMarketTicker24hr(self.extend(request, params)) data = self.safe_list(response, 'data', []) ticker = self.safe_dict(data, 0, {}) return self.parse_ticker(ticker, market) def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker: # # fetchTicker/fetchTickers # { # "symbol": "BTC-USDT", # "open": "86452.9", # "high": "89371.2", # "low": "84418.5", # "last": "87050.3", # "vol": "12938783", # "time": 1766169423872 # } # marketId = self.safe_string_2(ticker, 'symbol', 's') market = self.safe_market(marketId, market) timestamp = self.safe_integer_2(ticker, 'time', 'E') last = self.safe_string_2(ticker, 'last', 'c') return self.safe_ticker({ 'symbol': self.safe_symbol(marketId, market), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string_2(ticker, 'high', 'h'), 'low': self.safe_string_2(ticker, 'low', 'l'), 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': None, 'open': self.safe_string_2(ticker, 'open', 'o'), 'close': last, 'last': last, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': self.safe_string_2(ticker, 'vol', 'v'), 'quoteVolume': None, 'markPrice': None, 'indexPrice': None, 'info': ticker, }, market) def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate: """ fetch the current funding rate https://developers.bydfi.com/en/futures/market#recent-funding-rate :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } response = self.publicGetV1FapiMarketFundingRate(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "symbol": "BTC-USDT", # "lastFundingRate": "0.0001", # "nextFundingTime": "1766188800000", # "time": "1766170665007" # }, # "success": True # } # data = self.safe_dict(response, 'data') return self.parse_funding_rate(data, market) def parse_funding_rate(self, contract, market: Market = None) -> FundingRate: # # { # "symbol": "BTC-USDT", # "lastFundingRate": "0.0001", # "nextFundingTime": "1766188800000", # "time": "1766170665007" # } # marketId = self.safe_string(contract, 'symbol') symbol = self.safe_symbol(marketId, market) timestamp = self.safe_integer(contract, 'time') nextFundingTimestamp = self.safe_integer(contract, 'nextFundingTime') return { 'info': contract, 'symbol': symbol, 'markPrice': None, 'indexPrice': None, 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fundingRate': self.safe_number(contract, 'lastFundingRate'), 'fundingTimestamp': None, 'fundingDatetime': None, 'nextFundingRate': None, 'nextFundingTimestamp': nextFundingTimestamp, 'nextFundingDatetime': self.iso8601(nextFundingTimestamp), 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': None, } def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[FundingRateHistory]: """ fetches historical funding rate prices https://developers.bydfi.com/en/futures/market#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 ` to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: timestamp in ms of the latest funding rate to fetch :returns dict[]: a list of `funding rate structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } if since is not None: request['startTime'] = since if limit is not None: request['limit'] = limit until = None until, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'until') if until is not None: request['endTime'] = until response = self.publicGetV1FapiMarketFundingRateHistory(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "symbol": "ETH-USDT", # "fundingRate": "0.00000025", # "fundingTime": "1765584000000", # "markPrice": "3083.2" # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_funding_rate_histories(data, market, since, limit) def parse_funding_rate_history(self, contract, market: Market = None): # # { # "symbol": "ETH-USDT", # "fundingRate": "0.00000025", # "fundingTime": "1765584000000", # "markPrice": "3083.2" # } # marketId = self.safe_string(contract, 'symbol') timestamp = self.safe_integer(contract, 'fundingTime') return { 'info': contract, 'symbol': self.safe_symbol(marketId, market), 'fundingRate': self.safe_number(contract, 'fundingRate'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), } def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order: """ create a trade order https://developers.bydfi.com/en/futures/trade#placing-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 fullfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :param bool [params.hedged]: True for hedged mode, False for one way mode, default is False :param str [params.clientOrderId]: Custom order ID, must be unique for open orders :param str [params.timeInForce]: 'GTC'(Good Till Cancelled), 'FOK'(Fill Or Kill), 'IOC'(Immediate Or Cancel), 'PO'(Post Only) :param bool [params.postOnly]: True or False, whether the order is post-only :param bool [params.reduceOnly]: True or False, True or False whether the order is reduce-only :param float [params.stopLossPrice]: The price a stop loss order is triggered at :param float [params.takeProfitPrice]: The price a take profit order is triggered at :param float [params.trailingTriggerPrice]: the price to activate a trailing order, default uses the price argument or market price if price is not provided :param float [params.trailingPercent]: the percent to trail away from the current market price :param str [params.triggerPriceType]: 'MARK_PRICE' or 'CONTRACT_PRICE', default is 'CONTRACT_PRICE', the price type used to trigger stop orders :param bool [params.closePosition]: True or False, whether to close all positions after triggering, only supported in STOP_MARKET and TAKE_PROFIT_MARKET; not used with quantity :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) orderRequest = self.create_order_request(symbol, type, side, amount, price, params) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'createOrder', 'wallet', wallet) orderRequest = self.extend(orderRequest, {'wallet': wallet}) response = self.privatePostV1FapiTradePlaceOrder(orderRequest) # # { # "code": 200, # "message": "success", # "data": { # "wallet": "W001", # "symbol": "ETH-USDT", # "orderId": "7408875768086683648", # "clientOrderId": "7408875768086683648", # "price": "1000", # "origQty": "10", # "avgPrice": null, # "executedQty": "0", # "orderType": "LIMIT", # "side": "BUY", # "status": "NEW", # "stopPrice": null, # "activatePrice": null, # "timeInForce": null, # "workingType": "CONTRACT_PRICE", # "positionSide": "BOTH", # "priceProtect": False, # "reduceOnly": False, # "closePosition": False, # "createTime": "1766413633367", # "updateTime": "1766413633367" # }, # "success": True # } # data = self.safe_dict(response, 'data', {}) return self.parse_order(data, market) def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): market = self.market(symbol) request: dict = { 'symbol': market['id'], 'side': side.upper(), # 'positionSide': STRING Position direction, not required in single position mode, default and can only be BOTH; required in dual position mode, and can only choose LONG or SHORT # 'type': STRING Order type LIMIT / MARKET / STOP / TAKE_PROFIT / STOP_MARKET / TAKE_PROFIT_MARKET / TRAILING_STOP_MARKET # 'reduceOnly': BOOL True, False; defaults to False in non-dual mode; not accepted in dual mode; not supported when using closePosition. # 'quantity': DECIMAL Order quantity, not supported with closePosition. # 'price': DECIMAL Order price # 'clientOrderId': STRING User-defined order number, must not be repeated in pending orders. If blank, the system will assign automatically # 'stopPrice': DECIMAL Trigger price, only required for STOP, STOP_MARKET, TAKE_PROFIT, TAKE_PROFIT_MARKET # 'closePosition': BOOL True, False; all positions closed after triggering, only supported in STOP_MARKET and TAKE_PROFIT_MARKET; not used with quantity; has a self-closing effect, not used with reduceOnly # 'activationPrice': DECIMAL Trailing stop activation price, required for TRAILING_STOP_MARKET, default to current market price upon order(supports different workingType) # 'callbackRate': DECIMAL Trailing stop callback rate, can range from [0.1, 5], where 1 represents 1%, only required for TRAILING_STOP_MARKET # 'timeInForce': STRING Validity method GTC / FOK / POST_ONLY / IOC / TRAILING_STOP # 'workingType': STRING stopPrice trigger type: MARK_PRICE(marking price), CONTRACT_PRICE(latest contract price). Default CONTRACT_PRICE } stopLossPrice = self.safe_string(params, 'stopLossPrice') isStopLossOrder = (stopLossPrice is not None) takeProfitPrice = self.safe_string(params, 'takeProfitPrice') isTakeProfitOrder = (takeProfitPrice is not None) trailingPercent = self.safe_string(params, 'trailingPercent') isTailingStopOrder = (trailingPercent is not None) stopPrice = None if isStopLossOrder or isTakeProfitOrder: stopPrice = stopLossPrice if isStopLossOrder else takeProfitPrice params = self.omit(params, ['stopLossPrice', 'takeProfitPrice']) request['stopPrice'] = self.price_to_precision(symbol, stopPrice) elif isTailingStopOrder: params = self.omit(params, ['trailingPercent']) request['callbackRate'] = trailingPercent trailingTriggerPrice = self.number_to_string(price) trailingTriggerPrice, params = self.handle_param_string(params, 'trailingTriggerPrice', trailingTriggerPrice) if trailingTriggerPrice is not None: request['activationPrice'] = self.price_to_precision(symbol, trailingTriggerPrice) params = self.omit(params, ['trailingTriggerPrice']) type = type.upper() isMarketOrder = ((type == 'MARKET') or (type == 'STOP_MARKET') or (type == 'TAKE_PROFIT_MARKET') or (type == 'TRAILING_STOP_MARKET')) if isMarketOrder: if type == 'MARKET': if isStopLossOrder: type = 'STOP_MARKET' elif isTakeProfitOrder: type = 'TAKE_PROFIT_MARKET' elif isTailingStopOrder: type = 'TRAILING_STOP_MARKET' else: if price is None: raise ArgumentsRequired(self.id + ' createOrder() requires a price argument for a ' + type + ' order') request['price'] = self.price_to_precision(symbol, price) if isStopLossOrder: type = 'STOP' elif isTakeProfitOrder: type = 'TAKE_PROFIT' request['type'] = type hedged = False hedged, params = self.handle_option_and_params(params, 'createOrder', 'hedged', hedged) reduceOnly = self.safe_bool(params, 'reduceOnly', False) if hedged: params = self.omit(params, 'reduceOnly') if side == 'buy': request['positionSide'] = 'SHORT' if reduceOnly else 'LONG' elif side == 'sell': request['positionSide'] = 'LONG' if reduceOnly else 'SHORT' closePosition = self.safe_bool(params, 'closePosition', False) if not closePosition: params = self.omit(params, 'closePosition') request['quantity'] = self.amount_to_precision(symbol, amount) elif (type != 'STOP_MARKET') and (type != 'TAKE_PROFIT_MARKET'): raise NotSupported(self.id + ' createOrder() closePosition is only supported for stopLoss and takeProfit market orders') timeInForce = self.handle_time_in_force(params) postOnly = False postOnly, params = self.handle_post_only(isMarketOrder, timeInForce == 'POST_ONLY', params) if postOnly: timeInForce = 'POST_ONLY' if timeInForce is not None: request['timeInForce'] = timeInForce params = self.omit(params, 'timeInForce') if isStopLossOrder or isTakeProfitOrder or isTailingStopOrder: workingType = 'CONTRACT_PRICE' workingType, params = self.handle_option_and_params(params, 'createOrder', 'triggerPriceType', workingType) request['workingType'] = self.encode_working_type(workingType) return self.extend(request, params) def encode_working_type(self, workingType: Str) -> Str: types = { 'markPrice': 'MARK_PRICE', 'mark': 'MARK_PRICE', 'contractPrice': 'CONTRACT_PRICE', 'contract': 'CONTRACT_PRICE', 'last': 'CONTRACT_PRICE', } return self.safe_string(types, workingType, workingType) def create_orders(self, orders: List[OrderRequest], params={}): """ create a list of trade orders https://developers.bydfi.com/en/futures/trade#batch-order-placement :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: an `order structure ` """ self.load_markets() length = len(orders) if length > 5: raise BadRequest(self.id + ' createOrders() accepts a maximum of 5 orders') ordersRequests = [] for i in range(0, len(orders)): rawOrder = orders[i] symbol = self.safe_string(rawOrder, 'symbol') type = self.safe_string(rawOrder, 'type') side = self.safe_string(rawOrder, 'side') amount = self.safe_number(rawOrder, 'amount') price = self.safe_number(rawOrder, 'price') orderParams = self.safe_dict(rawOrder, 'params', {}) orderRequest = self.create_order_request(symbol, type, side, amount, price, orderParams) ordersRequests.append(orderRequest) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'createOrder', 'wallet', wallet) request: dict = { 'wallet': wallet, 'orders': ordersRequests, } response = self.privatePostV1FapiTradeBatchPlaceOrder(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_orders(data) def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order: """ edit a trade order https://developers.bydfi.com/en/futures/trade#order-modification :param str id: order id(mandatory if params.clientOrderId is not provided) :param str [symbol]: unified symbol of the market to create an order in :param str [type]: not used by bydfi editOrder :param str [side]: 'buy' or 'sell' :param float [amount]: how much of the currency you want to trade in units of the base currency :param float [price]: the price for the order, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.clientOrderId]: a unique identifier for the order(could be alternative to id) :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: an `order structure ` """ self.load_markets() request = self.create_edit_order_request(id, symbol, 'limit', side, amount, price, params) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'editOrder', 'wallet', wallet) request['wallet'] = wallet response = self.privatePostV1FapiTradeEditOrder(request) data = self.safe_dict(response, 'data', {}) return self.parse_order(data) def edit_orders(self, orders: List[OrderRequest], params={}) -> List[Order]: """ edit a list of trade orders https://developers.bydfi.com/en/futures/trade#batch-order-modification :param Array orders: list of orders to edit, each object should contain the parameters required by editOrder, namely id, symbol, amount, price and params :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: an `order structure ` """ self.load_markets() length = len(orders) if length > 5: raise BadRequest(self.id + ' editOrders() accepts a maximum of 5 orders') ordersRequests = [] for i in range(0, len(orders)): rawOrder = orders[i] id = self.safe_string(rawOrder, 'id') symbol = self.safe_string(rawOrder, 'symbol') side = self.safe_string(rawOrder, 'side') amount = self.safe_number(rawOrder, 'amount') price = self.safe_number(rawOrder, 'price') orderParams = self.safe_dict(rawOrder, 'params', {}) orderRequest = self.create_edit_order_request(id, symbol, 'limit', side, amount, price, orderParams) ordersRequests.append(orderRequest) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'editOrder', 'wallet', wallet) request: dict = { 'wallet': wallet, 'editOrders': ordersRequests, } response = self.privatePostV1FapiTradeBatchEditOrder(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_orders(data) def create_edit_order_request(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}): clientOrderId = self.safe_string(params, 'clientOrderId') request: dict = {} if (id is None) and (clientOrderId is None): raise ArgumentsRequired(self.id + ' editOrder() requires an id argument or a clientOrderId parameter') elif id is not None: request['orderId'] = id market = self.market(symbol) request['symbol'] = market['id'] if side is not None: request['side'] = side.upper() if amount is not None: request['quantity'] = self.amount_to_precision(symbol, amount) if price is not None: request['price'] = self.price_to_precision(symbol, price) return self.extend(request, params) def cancel_all_orders(self, symbol: Str = None, params={}) -> List[Order]: """ cancel all open orders in a market https://developers.bydfi.com/en/futures/trade#complete-order-cancellation :param str symbol: unified market symbol of the market to cancel orders in :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict[]: a list of `order structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument') self.load_markets() market = self.market(symbol) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'cancelAllOrders', 'wallet', wallet) request: dict = { 'symbol': market['id'], 'wallet': wallet, } response = self.privatePostV1FapiTradeCancelAllOrder(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "wallet": "W001", # "symbol": "ETH-USDT", # "orderId": "7408875768086683648", # "clientOrderId": "7408875768086683648", # "price": "1000", # "origQty": "10", # "avgPrice": "0", # "executedQty": "0", # "orderType": "LIMIT", # "side": "BUY", # "status": "CANCELED", # "stopPrice": null, # "activatePrice": null, # "timeInForce": null, # "workingType": "CONTRACT_PRICE", # "positionSide": "BOTH", # "priceProtect": False, # "reduceOnly": False, # "closePosition": False, # "createTime": "1766413633367", # "updateTime": "1766413633370" # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_orders(data, market) def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all unfilled currently open orders https://developers.bydfi.com/en/futures/trade#pending-order-query https://developers.bydfi.com/en/futures/trade#planned-order-query :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.trigger]: True or False, whether to fetch conditional orders only :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns Order[]: a list of `order structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument') self.load_markets() market = self.market(symbol) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'wallet', wallet) request: dict = { 'symbol': market['id'], 'wallet': wallet, } response = None trigger = False trigger, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'trigger', trigger) if not trigger: # # { # "code": 200, # "message": "success", # "data": [ # { # "wallet": "W001", # "symbol": "ETH-USDC", # "orderId": "7408896083240091648", # "clientOrderId": "7408896083240091648", # "price": "999", # "origQty": "1", # "avgPrice": "0", # "executedQty": "0", # "orderType": "LIMIT", # "side": "BUY", # "status": "NEW", # "stopPrice": null, # "activatePrice": null, # "timeInForce": null, # "workingType": "CONTRACT_PRICE", # "positionSide": "BOTH", # "priceProtect": False, # "reduceOnly": False, # "closePosition": False, # "createTime": "1766418476877", # "updateTime": "1766418476880" # } # ], # "success": True # } # response = self.privateGetV1FapiTradeOpenOrder(self.extend(request, params)) else: response = self.privateGetV1FapiTradePlanOrder(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def fetch_open_order(self, id: str, symbol: Str = None, params={}): """ fetch an open order by the id https://developers.bydfi.com/en/futures/trade#pending-order-query https://developers.bydfi.com/en/futures/trade#planned-order-query :param str id: order id(mandatory if params.clientOrderId is not provided) :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param bool [params.trigger]: True or False, whether to fetch conditional orders only :param str [params.clientOrderId]: a unique identifier for the order(could be alternative to id) :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: an `order structure ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchOpenOrder() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } clientOrderId = self.safe_string(params, 'clientOrderId') if (id is None) and (clientOrderId is None): raise ArgumentsRequired(self.id + ' fetchOpenOrder() requires an id argument or a clientOrderId parameter') elif id is not None: request['orderId'] = id wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchOpenOrder', 'wallet', wallet) request['wallet'] = wallet response = None trigger = False trigger, params = self.handle_option_and_params(params, 'fetchOpenOrder', 'trigger', trigger) if not trigger: response = self.privateGetV1FapiTradeOpenOrder(self.extend(request, params)) else: response = self.privateGetV1FapiTradePlanOrder(self.extend(request, params)) data = self.safe_list(response, 'data', []) order = self.safe_dict(data, 0, {}) return self.parse_order(order, market) def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetches information on multiple canceled and closed orders made by the user https://developers.bydfi.com/en/futures/trade#historical-orders-query :param str symbol: unified market symbol of the closed orders :param int [since]: timestamp in ms of the earliest order :param int [limit]: the max number of closed orders to return :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: timestamp in ms of the latest order :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet :param str [params.orderType]: order type('LIMIT', 'MARKET', 'LIQ', 'LIMIT_CLOSE', 'MARKET_CLOSE', 'STOP', 'TAKE_PROFIT', 'STOP_MARKET', 'TAKE_PROFIT_MARKET' or 'TRAILING_STOP_MARKET') :returns dict[]: a list of `order structures ` """ self.load_markets() paginate = self.safe_bool(params, 'paginate', False) if paginate: maxLimit = 500 params = self.omit(params, 'paginate') params = self.extend(params, {'paginationDirection': 'backward'}) paginatedResponse = self.fetch_paginated_call_dynamic('fetchCanceledAndClosedOrders', symbol, since, limit, params, maxLimit, True) return self.sort_by(paginatedResponse, 'timestamp') contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchCanceledAndClosedOrders', 'contractType', contractType) request: dict = { 'contractType': contractType, } market = None if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] params = self.handle_since_and_until('fetchCanceledAndClosedOrders', since, params) if limit is not None: request['limit'] = limit response = self.privateGetV1FapiTradeHistoryOrder(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "orderId": "7408919189505597440", # "orderType": "MARKET", # "symbol": "ETH-USDC", # "origQty": "1", # "side": "BUY", # "positionSide": "BOTH", # "positionAvgPrice": null, # "positionVolume": null, # "positionType": null, # "reduceOnly": False, # "closePosition": False, # "action": null, # "price": "3032.45", # "avgPrice": "3032.45", # "brkPrice": null, # "dealVolume": null, # "status": "2", # "wallet": "W001", # "alias": null, # "contractId": null, # "mtime": "1766423985842", # "ctime": "1766423985840", # "fixedPrice": null, # "direction": null, # "triggerPrice": null, # "priceType": null, # "basePrecision": "8", # "baseShowPrecision": "2", # "strategyType": null, # "leverageLevel": 1, # "marginType": "CROSS", # "remark": null, # "callbackRate": null, # "activationPrice": null # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def handle_since_and_until(self, methodName: str, since: Int = None, params={}) -> dict: until = None until, params = self.handle_option_and_params_2(params, methodName, 'until', 'endTime') now = self.milliseconds() sevenDays = 7 * 24 * 60 * 60 * 1000 # the maximum range is 7 days startTime = since if startTime is None: if until is None: # both since and until are None startTime = now - sevenDays until = now else: # since is None but until is defined startTime = until - sevenDays elif until is None: # until is None but since is defined delta = now - startTime if delta > sevenDays: until = startTime + sevenDays else: until = now request: dict = { 'startTime': startTime, 'endTime': until, } return self.extend(request, params) def parse_order(self, order: dict, market: Market = None) -> Order: # # createOrder, fetchOpenOrders, fetchOpenOrder # { # "wallet": "W001", # "symbol": "ETH-USDT", # "orderId": "7408875768086683648", # "clientOrderId": "7408875768086683648", # "price": "1000", # "origQty": "10", # "avgPrice": "0", # "executedQty": "0", # "orderType": "LIMIT", # "side": "BUY", # "status": "CANCELED", # "stopPrice": null, # "activatePrice": null, # "timeInForce": null, # "workingType": "CONTRACT_PRICE", # "positionSide": "BOTH", # "priceProtect": False, # "reduceOnly": False, # "closePosition": False, # "createTime": "1766413633367", # "updateTime": "1766413633370" # } # # fetchCanceledAndClosedOrders # { # "orderId": "7408919189505597440", # "orderType": "MARKET", # "symbol": "ETH-USDC", # "origQty": "1", # "side": "BUY", # "positionSide": "BOTH", # "positionAvgPrice": null, # "positionVolume": null, # "positionType": null, # "reduceOnly": False, # "closePosition": False, # "action": null, # "price": "3032.45", # "avgPrice": "3032.45", # "brkPrice": null, # "dealVolume": null, # "status": "2", # "wallet": "W001", # "alias": null, # "contractId": null, # "mtime": "1766423985842", # "ctime": "1766423985840", # "fixedPrice": null, # "direction": null, # "triggerPrice": null, # "priceType": null, # "basePrecision": "8", # "baseShowPrecision": "2", # "strategyType": null, # "leverageLevel": 1, # "marginType": "CROSS", # "remark": null, # "callbackRate": null, # "activationPrice": null # } # marketId = self.safe_string(order, 'symbol') market = self.safe_market(marketId, market) timestamp = self.safe_integer_2(order, 'createTime', 'ctime') rawType = self.safe_string(order, 'orderType') stopPrice = self.safe_string_n(order, ['stopPrice', 'activatePrice', 'triggerPrice']) isStopLossOrder = (rawType == 'STOP') or (rawType == 'STOP_MARKET') or (rawType == 'TRAILING_STOP_MARKET') isTakeProfitOrder = (rawType == 'TAKE_PROFIT') or (rawType == 'TAKE_PROFIT_MARKET') rawTimeInForce = self.safe_string(order, 'timeInForce') timeInForce = self.parse_order_time_in_force(rawTimeInForce) postOnly = None if timeInForce == 'PO': postOnly = True rawStatus = self.safe_string(order, 'status') fee = {} quoteFee = self.safe_number(order, 'quoteFee') if quoteFee is not None: fee['cost'] = quoteFee fee['currency'] = market['quote'] return self.safe_order({ 'info': order, 'id': self.safe_string(order, 'orderId'), 'clientOrderId': self.safe_string(order, 'clientOrderId'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'lastUpdateTimestamp': self.safe_integer_2(order, 'updateTime', 'mtime'), 'status': self.parse_order_status(rawStatus), 'symbol': market['symbol'], 'type': self.parse_order_type(rawType), 'timeInForce': timeInForce, 'postOnly': postOnly, 'reduceOnly': self.safe_bool(order, 'reduceOnly'), 'side': self.safe_string_lower(order, 'side'), 'price': self.safe_string(order, 'price'), 'triggerPrice': stopPrice, 'stopLossPrice': stopPrice if isStopLossOrder else None, 'takeProfitPrice': stopPrice if isTakeProfitOrder else None, 'amount': self.safe_string(order, 'origQty'), 'filled': self.safe_string(order, 'executedQty'), 'remaining': None, 'cost': None, 'trades': None, 'fee': fee, 'average': self.omit_zero(self.safe_string(order, 'avgPrice')), }, market) def parse_order_type(self, type: Str) -> Str: types = { 'LIMIT': 'limit', 'MARKET': 'market', 'STOP': 'limit', 'STOP_MARKET': 'market', 'TAKE_PROFIT': 'limit', 'TAKE_PROFIT_MARKET': 'market', 'TRAILING_STOP_MARKET': 'market', } return self.safe_string(types, type, type) def parse_order_time_in_force(self, timeInForce: Str) -> Str: timeInForces = { 'GTC': 'GTC', 'FOK': 'FOK', 'IOC': 'IOC', 'POST_ONLY': 'PO', 'TRAILING_STOP': 'IOC', } return self.safe_string(timeInForces, timeInForce, timeInForce) def parse_order_status(self, status: Str) -> Str: statuses = { 'NEW': 'open', 'PARTIALLY_FILLED': 'open', 'FILLED': 'closed', 'EXPIRED': 'canceled', 'PART_FILLED_CANCELLED': 'canceled', 'CANCELED': 'canceled', '2': 'closed', '4': 'canceled', } return self.safe_string(statuses, status, status) def set_leverage(self, leverage: int, symbol: Str = None, params={}): """ set the level of leverage for a market https://developers.bydfi.com/en/futures/trade#set-leverage-for-single-trading-pair :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.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: response from the exchange """ if symbol is None: raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument') self.load_markets() market = self.market(symbol) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'setLeverage', 'wallet', wallet) request: dict = { 'symbol': market['id'], 'leverage': leverage, 'wallet': wallet, } response = self.privatePostV1FapiTradeLeverage(self.extend(request, params)) data = self.safe_dict(response, 'data', {}) return data def fetch_leverage(self, symbol: str, params={}) -> Leverage: """ fetch the set leverage for a market https://developers.bydfi.com/en/futures/trade#get-leverage-for-single-trading-pair :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: a `leverage structure ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchLeverage() requires a symbol argument') self.load_markets() market = self.market(symbol) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchLeverage', 'wallet', wallet) request: dict = { 'symbol': market['id'], 'wallet': wallet, } response = self.privateGetV1FapiTradeLeverage(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "symbol": "ETH-USDC", # "leverage": 1, # "maxNotionalValue": "100000000" # }, # "success": True # } # data = self.safe_dict(response, 'data', {}) return self.parse_leverage(data, market) def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage: marketId = self.safe_string(leverage, 'symbol') return { 'info': leverage, 'symbol': self.safe_symbol(marketId, market), 'marginMode': None, 'longLeverage': self.safe_integer(leverage, 'leverage'), 'shortLeverage': self.safe_integer(leverage, 'leverage'), } def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open positions https://developers.bydfi.com/en/futures/trade#positions-query :param str[] [symbols]: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.settleCoin]: the settlement currency(USDT or USDC or USD) :returns dict[]: a list of `position structure ` """ self.load_markets() contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchPositions', 'contractType', contractType) request: dict = { 'contractType': contractType, } response = self.privateGetV1FapiTradePositions(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "symbol": "ETH-USDC", # "side": "BUY", # "volume": "0.001", # "avgPrice": "3032.45", # "liqPrice": "0", # "markPrice": "3032.37", # "unPnl": "-0.00008", # "positionMargin": "0", # "settleCoin": "USDC", # "im": "3.03245", # "mm": "0.007581125" # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_positions(data, symbols) def fetch_positions_for_symbol(self, symbol: str, params={}) -> List[Position]: """ fetch open positions for a single market https://developers.bydfi.com/en/futures/trade#positions-query fetch all open positions for specific symbol :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :returns dict[]: a list of `position structure ` """ self.load_markets() market = self.market(symbol) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchPositions', 'contractType', contractType) request: dict = { 'contractType': contractType, 'symbol': market['id'], } response = self.privateGetV1FapiTradePositions(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_positions(data, [market['symbol']]) def parse_position(self, position: dict, market: Market = None): # # fetchPositions, fetchPositionsForSymbol # { # "symbol": "ETH-USDC", # "side": "BUY", # "volume": "0.001", # "avgPrice": "3032.45", # "liqPrice": "0", # "markPrice": "3032.37", # "unPnl": "-0.00008", # "positionMargin": "0", # "settleCoin": "USDC", # "im": "3.03245", # "mm": "0.007581125" # } # # fetchPositionsHistory # { # "id": "16788366", # "wallet": "W001", # "currency": "USDC", # "symbol": "ETH-USDC", # "side": "BUY", # "positionSide": "BOTH", # "leverage": 1, # "avgOpenPositionPrice": "3032.45", # "openPositionVolume": "1", # "openCount": 1, # "highPrice": "3032.45", # "lowPrice": "2953.67", # "avgClosePositionPrice": "2953.67", # "closePositionVolume": "1", # "closePositionCost": "2.95367", # "closeCount": 1, # "positionProfits": "-0.07878", # "lossBonus": "0", # "capitalFeeTotal": "-0.00026361", # "capitalFeeOutCash": "-0.00026361", # "capitalFeeInCash": "0", # "capitalFeeBonus": "0", # "openFeeTotal": "-0.00181947", # "openFeeBonus": "0", # "closeFeeTotal": "-0.00177221", # "closeFeeBonus": "0", # "liqLoss": "0", # "liqClosed": False, # "sequence": "53685341336", # "updateTime": "1766494929423", # "createTime": "1766423985842" # } # marketId = self.safe_string(position, 'symbol') market = self.safe_market(marketId, market) buyOrSell = self.safe_string(position, 'side') rawPositionSide = self.safe_string_lower(position, 'positionSide') positionSide = self.parse_position_side(buyOrSell) hedged = None isFetchPositionsHistory = False if rawPositionSide is not None: isFetchPositionsHistory = True if rawPositionSide != 'both': positionSide = rawPositionSide hedged = True else: hedged = False contractSize = self.safe_string(market, 'contractSize') contracts = self.safe_string_2(position, 'volume', 'openPositionVolume') if not isFetchPositionsHistory: # in fetchPositions, the 'volume' is in base currency units, need to convert to contracts contracts = Precise.string_div(contracts, contractSize) timestamp = self.safe_integer(position, 'createTime') return self.safe_position({ 'info': position, 'id': self.safe_string(position, 'id'), 'symbol': market['symbol'], 'entryPrice': self.parse_number(self.safe_string_2(position, 'avgOpenPositionPrice', 'avgPrice')), 'markPrice': self.parse_number(self.safe_string(position, 'markPrice')), 'lastPrice': self.parse_number(self.safe_string(position, 'avgClosePositionPrice')), 'notional': self.parse_number(self.safe_string(position, 'closePositionCost')), 'collateral': None, 'unrealizedPnl': self.parse_number(self.safe_string(position, 'unPnl')), 'realizedPnl': self.parse_number(self.safe_string(position, 'positionProfits')), 'side': positionSide, 'contracts': self.parse_number(contracts), 'contractSize': self.parse_number(contractSize), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastUpdateTimestamp': self.safe_integer(position, 'updateTime'), 'hedged': hedged, 'maintenanceMargin': self.parse_number(self.safe_string(position, 'mm')), 'maintenanceMarginPercentage': None, 'initialMargin': self.parse_number(self.safe_string(position, 'im')), 'initialMarginPercentage': None, 'leverage': self.parse_number(self.safe_string(position, 'leverage')), 'liquidationPrice': self.parse_number(self.safe_string(position, 'liqPrice')), 'marginRatio': None, 'marginMode': None, 'percentage': None, }) def parse_position_side(self, side: Str) -> Str: sides = { 'BUY': 'long', 'SELL': 'short', } return self.safe_string(sides, side, side) def fetch_position_history(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ fetches historical positions https://developers.bydfi.com/en/futures/trade#query-historical-position-profit-and-loss-records :param str symbol: a unified market symbol :param int [since]: timestamp in ms of the earliest position to fetch , params["until"] - since <= 7 days :param int [limit]: the maximum amount of records to fetch(default 500, max 500) :param dict params: extra parameters specific to the exchange api endpoint :param int [params.until]: timestamp in ms of the latest position to fetch , params["until"] - since <= 7 days :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict[]: a list of `position structures ` """ self.load_markets() market = self.market(symbol) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchPositionsHistory', 'contractType', contractType) request: dict = { 'symbol': market['id'], 'contractType': contractType, } params = self.handle_since_and_until('fetchPositionsHistory', since, params) if limit is not None: request['limit'] = limit response = self.privateGetV1FapiTradePositionHistory(self.extend(request, params)) # # data = self.safe_list(response, 'data', []) positions = self.parse_positions(data) return self.filter_by_since_limit(positions, since, limit) def fetch_positions_history(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ fetches historical positions https://developers.bydfi.com/en/futures/trade#query-historical-position-profit-and-loss-records :param str[] symbols: a list of unified market symbols :param int [since]: timestamp in ms of the earliest position to fetch , params["until"] - since <= 7 days :param int [limit]: the maximum amount of records to fetch(default 500, max 500) :param dict params: extra parameters specific to the exchange api endpoint :param int [params.until]: timestamp in ms of the latest position to fetch , params["until"] - since <= 7 days :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict[]: a list of `position structures ` """ self.load_markets() contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchPositionsHistory', 'contractType', contractType) request: dict = { 'contractType': contractType, } params = self.handle_since_and_until('fetchPositionsHistory', since, params) if limit is not None: request['limit'] = limit response = self.privateGetV1FapiTradePositionHistory(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "id": "16788366", # "wallet": "W001", # "currency": "USDC", # "symbol": "ETH-USDC", # "side": "BUY", # "positionSide": "BOTH", # "leverage": 1, # "avgOpenPositionPrice": "3032.45", # "openPositionVolume": "1", # "openCount": 1, # "highPrice": "3032.45", # "lowPrice": "2953.67", # "avgClosePositionPrice": "2953.67", # "closePositionVolume": "1", # "closePositionCost": "2.95367", # "closeCount": 1, # "positionProfits": "-0.07878", # "lossBonus": "0", # "capitalFeeTotal": "-0.00026361", # "capitalFeeOutCash": "-0.00026361", # "capitalFeeInCash": "0", # "capitalFeeBonus": "0", # "openFeeTotal": "-0.00181947", # "openFeeBonus": "0", # "closeFeeTotal": "-0.00177221", # "closeFeeBonus": "0", # "liqLoss": "0", # "liqClosed": False, # "sequence": "53685341336", # "updateTime": "1766494929423", # "createTime": "1766423985842" # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) positions = self.parse_positions(data, symbols) return self.filter_by_since_limit(positions, since, limit) def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode: """ fetches the margin mode of a trading pair https://developers.bydfi.com/en/futures/user#margin-mode-query :param str symbol: unified symbol of the market to fetch the margin mode for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: a `margin mode structure ` """ self.load_markets() market = self.market(symbol) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchMarginMode', 'contractType', contractType) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchMarginMode', 'wallet', wallet) request: dict = { 'contractType': contractType, 'symbol': market['id'], 'wallet': wallet, } response = self.privateGetV1FapiUserDataAssetsMargin(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "wallet": "W001", # "symbol": "ETH-USDC", # "marginType": "CROSS" # }, # "success": True # } # data = self.safe_dict(response, 'data', {}) return self.parse_margin_mode(data, market) def parse_margin_mode(self, marginMode: dict, market: Market = None) -> MarginMode: marketId = self.safe_string(marginMode, 'symbol') return { 'info': marginMode, 'symbol': self.safe_symbol(marketId, market), 'marginMode': self.safe_string_lower(marginMode, 'marginType'), } def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}): """ set margin mode to 'cross' or 'isolated' https://developers.bydfi.com/en/futures/user#change-margin-type-cross-margin :param str marginMode: 'cross' or 'isolated' :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :returns dict: response from the exchange """ if symbol is None: raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument') marginMode = marginMode.lower() if marginMode != 'isolated' and marginMode != 'cross': raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross') self.load_markets() market = self.market(symbol) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchMarginMode', 'contractType', contractType) wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchMarginMode', 'wallet', wallet) request: dict = { 'contractType': contractType, 'symbol': market['id'], 'marginType': marginMode.upper(), 'wallet': wallet, } return self.privatePostV1FapiUserDataMarginType(self.extend(request, params)) def set_position_mode(self, hedged: bool, symbol: Str = None, params={}): """ set hedged to True or False for a market, hedged for bydfi is set identically for all markets with same settle currency https://developers.bydfi.com/en/futures/user#change-position-mode-dual :param bool hedged: set to True to use dualSidePosition :param str [symbol]: not used by bydfi setPositionMode() :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :param str [params.settleCoin]: The settlement currency - USDT or USDC or USD(default is USDT) :returns dict: response from the exchange """ if symbol is not None: raise NotSupported(self.id + ' setPositionMode() does not support a symbol argument. The position mode is set identically for all markets with same settle currency') self.load_markets() positionType = 'HEDGE' if hedged else 'ONEWAY' wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'setPositionMode', 'wallet', wallet) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'setPositionMode', 'contractType', contractType) settleCoin = 'USDT' settleCoin, params = self.handle_option_and_params(params, 'setPositionMode', 'settleCoin', settleCoin) request: dict = { 'contractType': contractType, 'wallet': wallet, 'positionType': positionType, 'settleCoin': settleCoin, } # # { # "code": 200, # "message": "success", # "success": True # } # return self.privatePostV1FapiUserDataPositionSideDual(self.extend(request, params)) def fetch_position_mode(self, symbol: Str = None, params={}): """ fetchs the position mode, hedged or one way, hedged for bydfi is set identically for all markets with same settle currency https://developers.bydfi.com/en/futures/user#get-position-mode :param str [symbol]: unified symbol of the market to fetch the order book for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.contractType]: FUTURE or DELIVERY, default is FUTURE :param str [params.wallet]: The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :param str [params.settleCoin]: The settlement currency - USDT or USDC or USD(default is USDT or settle currency of the market if market is provided) :returns dict: an object detailing whether the market is in hedged or one-way mode """ self.load_markets() wallet = 'W001' wallet, params = self.handle_option_and_params(params, 'fetchPositionMode', 'wallet', wallet) contractType = 'FUTURE' contractType, params = self.handle_option_and_params(params, 'fetchPositionMode', 'contractType', contractType) settleCoin = 'USDT' if symbol is None: settleCoin, params = self.handle_option_and_params(params, 'fetchPositionMode', 'settleCoin', settleCoin) else: market = self.market(symbol) settleCoin = market['settleId'] request: dict = { 'contractType': contractType, 'settleCoin': settleCoin, 'wallet': wallet, } response = self.privateGetV1FapiUserDataPositionSideDual(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": { # "wallet": "W001", # "contractType": "FUTURE", # "settleCoin": "USDT", # "positionType": "HEDGE", # "unitModel": 2, # "pricingModel": "FLAG", # "priceProtection": "CLOSE", # "totalWallet": 2 # }, # "success": True # } # data = self.safe_dict(response, 'data', {}) hedged = self.safe_string(data, 'positionType') == 'HEDGE' return { 'info': response, 'hedged': hedged, } def fetch_balance(self, params={}) -> Balances: """ query for balance and get the amount of funds available for trading or funds locked in orders https://developers.bydfi.com/en/account#asset-inquiry https://developers.bydfi.com/en/futures/user#asset-query :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: the type of account to fetch the balance for, either 'SPOT' or 'UMFUTURE' or 'CMFUTURE' or 'COPY' or 'GRID' or 'FUNDING'(default is 'SPOT') :param str [params.wallet]: *swap only* The unique code of a sub-wallet. W001 is the default wallet and the main wallet code of the contract :param str [params.asset]: currency id for the balance to fetch :returns dict: a `balance structure ` """ self.load_markets() type = None type, params = self.handle_market_type_and_params('fetchBalance', None, params) wallet = None wallet, params = self.handle_option_and_params(params, 'fetchBalance', 'wallet') request: dict = {} response = None if wallet is None: options = self.safe_dict(self.options, 'accountsByType', {}) parsedAccountType = self.safe_string_upper(options, type, type) request['walletType'] = parsedAccountType # # { # "code": 200, # "message": "success", # "data": [ # { # "walletType": "spot", # "asset": "USDC", # "total": "100", # "available": "100", # "frozen": "0" # } # ], # "success": True # } # response = self.privateGetV1AccountAssets(self.extend(request, params)) else: request['wallet'] = wallet # # { # "code": 200, # "message": "success", # "data": [ # { # "wallet": "W001", # "asset": "USDT", # "balance": "0", # "frozen": "0", # "positionMargin": "0", # "availableBalance": "0", # "canWithdrawAmount": "0", # "bonusAmount": "0" # }, # { # "wallet": "W001", # "asset": "USDC", # "balance": "99.99505828", # "frozen": "4.0024", # "positionMargin": "2.95342", # "availableBalance": "92.96020828", # "canWithdrawAmount": "92.96020828", # "bonusAmount": "0" # } # ], # "success": True # } response = self.privateGetV1FapiAccountBalance(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_balance(data) def parse_balance(self, response) -> Balances: timestamp = self.milliseconds() result: dict = { 'info': response, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), } for i in range(0, len(response)): balance = response[i] symbol = self.safe_string(balance, 'asset') code = self.safe_currency_code(symbol) account = self.account() account['total'] = self.safe_string_2(balance, 'total', 'balance') account['free'] = self.safe_string_2(balance, 'available', 'availableBalance') result[code] = account return self.safe_balance(result) def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry: """ transfer currency internally between wallets on the same account https://developers.bydfi.com/en/account#asset-transfer-between-accounts :param str code: unified currency code :param float amount: amount to transfer :param str fromAccount: 'spot', 'funding', or 'swap' :param str toAccount: 'spot', 'funding', or 'swap' :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `transfer structure ` """ self.load_markets() currency = self.currency(code) accountsByType = self.safe_dict(self.options, 'accountsByType', {}) fromId = self.safe_string(accountsByType, fromAccount, fromAccount) toId = self.safe_string(accountsByType, toAccount, toAccount) request: dict = { 'asset': currency['id'], 'amount': self.currency_to_precision(code, amount), 'fromType': fromId, 'toType': toId, } response = self.privatePostV1AccountTransfer(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "success": True # } # transfer = self.parse_transfer(response, currency) transferOptions = self.safe_dict(self.options, 'transfer', {}) fillResponseFromRequest = self.safe_bool(transferOptions, 'fillResponseFromRequest', True) if fillResponseFromRequest: timestamp = self.milliseconds() transfer['timestamp'] = timestamp transfer['datetime'] = self.iso8601(timestamp) transfer['currency'] = code transfer['fromAccount'] = fromAccount transfer['toAccount'] = toAccount transfer['amount'] = amount return transfer def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]: """ fetch a history of internal transfers made on an account https://developers.bydfi.com/en/account#query-wallet-transfer-records :param str code: unified currency code of the currency transferred :param int [since]: the earliest time in ms to fetch transfers for :param int [limit]: the maximum number of transfers structures to retrieve(default 10) :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch entries for :returns dict[]: a list of `transfer structures ` """ if code is None: raise ArgumentsRequired(self.id + ' fetchTransfers() requires a code argument') self.load_markets() currency = self.currency(code) paginate = self.safe_bool(params, 'paginate', False) if paginate: maxLimit = 50 params = self.omit(params, 'paginate') params = self.extend(params, {'paginationDirection': 'backward'}) paginatedResponse = self.fetch_paginated_call_dynamic('fetchTransfers', currency['code'], since, limit, params, maxLimit, True) return self.sort_by(paginatedResponse, 'timestamp') request: dict = { 'asset': currency['id'], } until = None until, params = self.handle_option_and_params_2(params, 'fetchTransfers', 'until', 'endTime') if until is None: until = self.milliseconds() # exchange requires endTime if since is None: since = 1 # exchange requires startTime but allows any value request['startTime'] = since request['endTime'] = until if limit is not None: request['rows'] = limit response = self.privateGetV1AccountTransferRecords(self.extend(request, params)) # # { # "code": 200, # "message": "success", # "data": [ # { # "orderId": "1209991065294581760", # "txId": "6km5fRK83Gwdp43HA479DW1Colh2pKyS", # "sourceWallet": "SPOT", # "targetWallet": "SWAP", # "asset": "USDC", # "amount": "100", # "status": "SUCCESS", # "timestamp": 1766413950000 # } # ], # "success": True # } # data = self.safe_list(response, 'data', []) return self.parse_transfers(data, currency, since, limit) def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry: # # transfer # { # "code": 200, # "message": "success", # "success": True # } # # fetchTransfers # { # "orderId": "1209991065294581760", # "txId": "6km5fRK83Gwdp43HA479DW1Colh2pKyS", # "sourceWallet": "SPOT", # "targetWallet": "SWAP", # "asset": "USDC", # "amount": "100", # "status": "SUCCESS", # "timestamp": 1766413950000 # } # status = self.safe_string_upper_2(transfer, 'message', 'status') accountsById = self.safe_dict(self.options, 'accountsById', {}) fromId = self.safe_string_upper(transfer, 'sourceWallet') toId = self.safe_string_upper(transfer, 'targetWallet') fromAccount = self.safe_string(accountsById, fromId, fromId) toAccount = self.safe_string(accountsById, toId, toId) timestamp = self.safe_integer(transfer, 'timestamp') currencyId = self.safe_string(transfer, 'asset') return { 'info': transfer, 'id': self.safe_string(transfer, 'txId'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'currency': self.safe_currency_code(currencyId, currency), 'amount': self.safe_number(transfer, 'amount'), 'fromAccount': fromAccount, 'toAccount': toAccount, 'status': self.parase_transfer_status(status), } def parase_transfer_status(self, status: Str) -> Str: statuses = { 'SUCCESS': 'ok', 'WAIT': 'pending', 'FAILED': 'failed', } return self.safe_string(statuses, status, status) def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all deposits made to an account https://developers.bydfi.com/en/spot/account#query-deposit-records :param str code: unified currency code(mandatory) :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 :returns dict[]: a list of `transaction structures ` """ return self.fetch_transactions_helper('deposit', code, since, limit, params) def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all withdrawals made from an account https://developers.bydfi.com/en/spot/account#query-withdrawal-records :param str code: unified currency code(mandatory) :param int [since]: the earliest time in ms to fetch withdrawals for :param int [limit]: the maximum number of withdrawal structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `transaction structures ` """ return self.fetch_transactions_helper('withdrawal', code, since, limit, params) def fetch_transactions_helper(self, type, code, since, limit, params): methodName = 'fetchDeposits' if (type == 'deposit') else 'fetchWithdrawals' if code is None: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a code argument') self.load_markets() currency = self.currency(code) paginate = self.safe_bool(params, 'paginate', False) if paginate: maxLimit = 50 params = self.omit(params, 'paginate') params = self.extend(params, {'paginationDirection': 'backward'}) paginatedResponse = self.fetch_paginated_call_dynamic(methodName, currency['code'], since, limit, params, maxLimit, True) return self.sort_by(paginatedResponse, 'timestamp') request: dict = { 'asset': currency['id'], } until = None until, params = self.handle_option_and_params_2(params, 'fetchTransfers', 'until', 'endTime') now = self.milliseconds() sevenDays = 7 * 24 * 60 * 60 * 1000 # the maximum range is 7 days startTime = since if startTime is None: if until is None: # both since and until are None startTime = now - sevenDays until = now else: # since is None but until is defined startTime = until - sevenDays elif until is None: # until is None but since is defined delta = now - startTime if delta > sevenDays: until = startTime + sevenDays else: until = now request['startTime'] = startTime request['endTime'] = until if limit is not None: request['limit'] = limit response = None if type == 'deposit': # # { # "code": 200, # "message": "success", # "data": [ # { # "orderId": "1208864446987255809", # "asset": "USDC", # "amount": "200", # "status": "SUCCESS", # "txId": "0xd059a82a55ffc737722bd23c1ef3db2884ce8525b72ff0b3c038b430ce0c8ca5", # "network": "ETH", # "address": "0x8346b46f6aa9843c09f79f1c170a37aca83c8fcd", # "addressTag": null, # "finishTime": 1766145475000, # "createTime": 1766145344000 # } # ], # "success": True # } # response = self.privateGetV1SpotDepositRecords(self.extend(request, params)) else: # # todo check after withdrawal # response = self.privateGetV1SpotWithdrawRecords(self.extend(request, params)) data = self.safe_list(response, 'data', []) transactionParams: dict = { 'type': type, } params = self.extend(params, transactionParams) return self.parse_transactions(data, currency, since, limit, params) def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction: # # fetchDeposits # { # "orderId": "1208864446987255809", # "asset": "USDC", # "amount": "200", # "status": "SUCCESS", # "txId": "0xd059a82a55ffc737722bd23c1ef3db2884ce8525b72ff0b3c038b430ce0c8ca5", # "network": "ETH", # "address": "0x8346b46f6aa9843c09f79f1c170a37aca83c8fcd", # "addressTag": null, # "finishTime": 1766145475000, # "createTime": 1766145344000 # } # currencyId = self.safe_string(transaction, 'asset') code = self.safe_currency_code(currencyId, currency) rawStatus = self.safe_string_lower(transaction, 'status') timestamp = self.safe_integer(transaction, 'createTime') fee = None feeCost = self.safe_number(transaction, 'fee') if feeCost is not None: fee = { 'cost': feeCost, 'currency': None, } return { 'info': transaction, 'id': self.safe_string(transaction, 'orderId'), 'txid': self.safe_string(transaction, 'txId'), 'type': None, 'currency': code, 'network': self.network_id_to_code(self.safe_string(transaction, 'network')), 'amount': self.safe_number(transaction, 'amount'), 'status': self.parse_transaction_status(rawStatus), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'address': self.safe_string(transaction, 'address'), 'addressFrom': None, 'addressTo': None, 'tag': self.safe_string(transaction, 'addressTag'), 'tagFrom': None, 'tagTo': None, 'updated': self.safe_integer(transaction, 'finishTime'), 'comment': None, 'fee': fee, 'internal': False, } def parse_transaction_status(self, status: Str) -> Str: statuses = { 'success': 'ok', 'wait': 'pending', 'failed': 'failed', } return self.safe_string(statuses, status, status) def sign(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None): url = self.urls['api'][api] endpoint = '/' + path query = '' sortedParams = self.keysort(params) if method == 'GET': query = self.urlencode(sortedParams) if len(query) != 0: endpoint += '?' + query if api == 'private': self.check_required_credentials() timestamp = str(self.milliseconds()) if method == 'GET': payload = self.apiKey + timestamp + query signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'hex') headers = { 'X-API-KEY': self.apiKey, 'X-API-TIMESTAMP': timestamp, 'X-API-SIGNATURE': signature, } else: body = self.json(sortedParams) payload = self.apiKey + timestamp + body signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'hex') headers = { 'Content-Type': 'application/json', 'X-API-KEY': self.apiKey, 'X-API-TIMESTAMP': timestamp, 'X-API-SIGNATURE': signature, } url += endpoint 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 # # { # "code": 101107, # "message": "Requires transaction permissions" # } # code = self.safe_string(response, 'code') message = self.safe_string(response, 'message') if code != '200': feedback = self.id + ' ' + body self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) raise ExchangeError(feedback) # unknown message return None