9021 lines
435 KiB
Python
9021 lines
435 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
||
|
||
from ccxt.base.exchange import Exchange
|
||
from ccxt.abstract.bybit import ImplicitAPI
|
||
import hashlib
|
||
from ccxt.base.types import Any, Balances, BorrowInterest, Conversion, CrossBorrowRate, Currencies, Currency, DepositAddress, FundingHistory, Greeks, Int, LedgerEntry, Leverage, LeverageTier, LeverageTiers, Liquidation, LongShortRatio, Market, Num, Option, OptionChain, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, Transaction, MarketInterface, 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 AccountSuspended
|
||
from ccxt.base.errors import ArgumentsRequired
|
||
from ccxt.base.errors import BadRequest
|
||
from ccxt.base.errors import BadSymbol
|
||
from ccxt.base.errors import NoChange
|
||
from ccxt.base.errors import MarginModeAlreadySet
|
||
from ccxt.base.errors import ManualInteractionNeeded
|
||
from ccxt.base.errors import InsufficientFunds
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import InvalidNonce
|
||
from ccxt.base.errors import RequestTimeout
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class bybit(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(bybit, self).describe(), {
|
||
'id': 'bybit',
|
||
'name': 'Bybit',
|
||
'countries': ['VG'], # British Virgin Islands
|
||
'version': 'v5',
|
||
'userAgent': None,
|
||
'rateLimit': 20,
|
||
'hostname': 'bybit.com', # bybit.com, bytick.com, bybit.nl, bybit.com.hk
|
||
'pro': True,
|
||
'certified': True,
|
||
'has': {
|
||
'CORS': True,
|
||
'spot': True,
|
||
'margin': True,
|
||
'swap': True,
|
||
'future': True,
|
||
'option': True,
|
||
'borrowCrossMargin': True,
|
||
'cancelAllOrders': True,
|
||
'cancelAllOrdersAfter': True,
|
||
'cancelOrder': True,
|
||
'cancelOrders': True,
|
||
'cancelOrdersForSymbols': True,
|
||
'closeAllPositions': False,
|
||
'closePosition': False,
|
||
'createConvertTrade': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketSellOrderWithCost': True,
|
||
'createOrder': True,
|
||
'createOrders': True,
|
||
'createOrderWithTakeProfitAndStopLoss': True,
|
||
'createPostOnlyOrder': True,
|
||
'createReduceOnlyOrder': True,
|
||
'createStopLimitOrder': True,
|
||
'createStopLossOrder': True,
|
||
'createStopMarketOrder': True,
|
||
'createStopOrder': True,
|
||
'createTakeProfitOrder': True,
|
||
'createTrailingAmountOrder': True,
|
||
'createTriggerOrder': True,
|
||
'editOrder': True,
|
||
'editOrders': True,
|
||
'fetchAllGreeks': True,
|
||
'fetchBalance': True,
|
||
'fetchBidsAsks': 'emulated',
|
||
'fetchBorrowInterest': False, # temporarily disabled, doesn't work
|
||
'fetchBorrowRateHistories': False,
|
||
'fetchBorrowRateHistory': False,
|
||
'fetchCanceledAndClosedOrders': True,
|
||
'fetchCanceledOrders': True,
|
||
'fetchClosedOrder': True,
|
||
'fetchClosedOrders': True,
|
||
'fetchConvertCurrencies': True,
|
||
'fetchConvertQuote': True,
|
||
'fetchConvertTrade': True,
|
||
'fetchConvertTradeHistory': True,
|
||
'fetchCrossBorrowRate': True,
|
||
'fetchCrossBorrowRates': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDeposit': False,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': False,
|
||
'fetchDepositAddressesByNetwork': True,
|
||
'fetchDeposits': True,
|
||
'fetchDepositWithdrawFee': 'emulated',
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': True,
|
||
'fetchFundingRate': 'emulated', # emulated in exchange
|
||
'fetchFundingRateHistory': True,
|
||
'fetchFundingRates': True,
|
||
'fetchGreeks': True,
|
||
'fetchIndexOHLCV': True,
|
||
'fetchIsolatedBorrowRate': False,
|
||
'fetchIsolatedBorrowRates': False,
|
||
'fetchLedger': True,
|
||
'fetchLeverage': True,
|
||
'fetchLeverageTiers': True,
|
||
'fetchLongShortRatio': False,
|
||
'fetchLongShortRatioHistory': True,
|
||
'fetchMarginAdjustmentHistory': False,
|
||
'fetchMarketLeverageTiers': True,
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': True,
|
||
'fetchMyLiquidations': True,
|
||
'fetchMySettlementHistory': True,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterest': True,
|
||
'fetchOpenInterestHistory': True,
|
||
'fetchOpenOrder': True,
|
||
'fetchOpenOrders': True,
|
||
'fetchOption': True,
|
||
'fetchOptionChain': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrders': False,
|
||
'fetchOrderTrades': True,
|
||
'fetchPosition': True,
|
||
'fetchPositionHistory': 'emulated',
|
||
'fetchPositions': True,
|
||
'fetchPositionsHistory': True,
|
||
'fetchPremiumIndexOHLCV': True,
|
||
'fetchSettlementHistory': True,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': True,
|
||
'fetchTradingFees': True,
|
||
'fetchTransactions': False,
|
||
'fetchTransfers': True,
|
||
'fetchUnderlyingAssets': False,
|
||
'fetchVolatilityHistory': True,
|
||
'fetchWithdrawals': True,
|
||
'repayCrossMargin': True,
|
||
'sandbox': True,
|
||
'setLeverage': True,
|
||
'setMarginMode': True,
|
||
'setPositionMode': True,
|
||
'transfer': True,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1m': '1',
|
||
'3m': '3',
|
||
'5m': '5',
|
||
'15m': '15',
|
||
'30m': '30',
|
||
'1h': '60',
|
||
'2h': '120',
|
||
'4h': '240',
|
||
'6h': '360',
|
||
'12h': '720',
|
||
'1d': 'D',
|
||
'1w': 'W',
|
||
'1M': 'M',
|
||
},
|
||
'urls': {
|
||
'test': {
|
||
'spot': 'https://api-testnet.{hostname}',
|
||
'futures': 'https://api-testnet.{hostname}',
|
||
'v2': 'https://api-testnet.{hostname}',
|
||
'public': 'https://api-testnet.{hostname}',
|
||
'private': 'https://api-testnet.{hostname}',
|
||
},
|
||
'logo': 'https://github.com/user-attachments/assets/97a5d0b3-de10-423d-90e1-6620960025ed',
|
||
'api': {
|
||
'spot': 'https://api.{hostname}',
|
||
'futures': 'https://api.{hostname}',
|
||
'v2': 'https://api.{hostname}',
|
||
'public': 'https://api.{hostname}',
|
||
'private': 'https://api.{hostname}',
|
||
},
|
||
'demotrading': {
|
||
'spot': 'https://api-demo.{hostname}',
|
||
'futures': 'https://api-demo.{hostname}',
|
||
'v2': 'https://api-demo.{hostname}',
|
||
'public': 'https://api-demo.{hostname}',
|
||
'private': 'https://api-demo.{hostname}',
|
||
},
|
||
'www': 'https://www.bybit.com',
|
||
'doc': [
|
||
'https://bybit-exchange.github.io/docs/inverse/',
|
||
'https://bybit-exchange.github.io/docs/linear/',
|
||
'https://github.com/bybit-exchange',
|
||
],
|
||
'fees': 'https://help.bybit.com/hc/en-us/articles/360039261154',
|
||
'referral': 'https://www.bybit.com/invite?ref=XDK12WP',
|
||
},
|
||
'api': {
|
||
'public': {
|
||
'get': {
|
||
# spot
|
||
'spot/v3/public/symbols': 1,
|
||
'spot/v3/public/quote/depth': 1,
|
||
'spot/v3/public/quote/depth/merged': 1,
|
||
'spot/v3/public/quote/trades': 1,
|
||
'spot/v3/public/quote/kline': 1,
|
||
'spot/v3/public/quote/ticker/24hr': 1,
|
||
'spot/v3/public/quote/ticker/price': 1,
|
||
'spot/v3/public/quote/ticker/bookTicker': 1,
|
||
'spot/v3/public/server-time': 1,
|
||
'spot/v3/public/infos': 1,
|
||
'spot/v3/public/margin-product-infos': 1,
|
||
'spot/v3/public/margin-ensure-tokens': 1,
|
||
# data
|
||
'v3/public/time': 1,
|
||
'contract/v3/public/copytrading/symbol/list': 1,
|
||
# derivative
|
||
'derivatives/v3/public/order-book/L2': 1,
|
||
'derivatives/v3/public/kline': 1,
|
||
'derivatives/v3/public/tickers': 1,
|
||
'derivatives/v3/public/instruments-info': 1,
|
||
'derivatives/v3/public/mark-price-kline': 1,
|
||
'derivatives/v3/public/index-price-kline': 1,
|
||
'derivatives/v3/public/funding/history-funding-rate': 1,
|
||
'derivatives/v3/public/risk-limit/list': 1,
|
||
'derivatives/v3/public/delivery-price': 1,
|
||
'derivatives/v3/public/recent-trade': 1,
|
||
'derivatives/v3/public/open-interest': 1,
|
||
'derivatives/v3/public/insurance': 1,
|
||
# v5
|
||
'v5/announcements/index': 5, # 10/s = 1000 / (20 * 5)
|
||
# market
|
||
'v5/market/time': 5,
|
||
'v5/market/kline': 5,
|
||
'v5/market/mark-price-kline': 5,
|
||
'v5/market/index-price-kline': 5,
|
||
'v5/market/premium-index-price-kline': 5,
|
||
'v5/market/instruments-info': 5,
|
||
'v5/market/orderbook': 5,
|
||
'v5/market/tickers': 5,
|
||
'v5/market/funding/history': 5,
|
||
'v5/market/recent-trade': 5,
|
||
'v5/market/open-interest': 5,
|
||
'v5/market/historical-volatility': 5,
|
||
'v5/market/insurance': 5,
|
||
'v5/market/risk-limit': 5,
|
||
'v5/market/delivery-price': 5,
|
||
'v5/market/account-ratio': 5,
|
||
# spot leverage token
|
||
'v5/spot-lever-token/info': 5,
|
||
'v5/spot-lever-token/reference': 5,
|
||
# spot margin trade
|
||
'v5/spot-margin-trade/data': 5,
|
||
'v5/spot-margin-trade/collateral': 5,
|
||
'v5/spot-cross-margin-trade/data': 5,
|
||
'v5/spot-cross-margin-trade/pledge-token': 5,
|
||
'v5/spot-cross-margin-trade/borrow-token': 5,
|
||
# crypto loan
|
||
'v5/crypto-loan/collateral-data': 5,
|
||
'v5/crypto-loan/loanable-data': 5,
|
||
# institutional lending
|
||
'v5/ins-loan/product-infos': 5,
|
||
'v5/ins-loan/ensure-tokens-convert': 5,
|
||
# earn
|
||
'v5/earn/product': 5,
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
'v5/market/instruments-info': 5,
|
||
# Legacy inverse swap
|
||
'v2/private/wallet/fund/records': 25, # 120 per minute = 2 per second => cost = 50 / 2 = 25
|
||
# spot
|
||
'spot/v3/private/order': 2.5,
|
||
'spot/v3/private/open-orders': 2.5,
|
||
'spot/v3/private/history-orders': 2.5,
|
||
'spot/v3/private/my-trades': 2.5,
|
||
'spot/v3/private/account': 2.5,
|
||
'spot/v3/private/reference': 2.5,
|
||
'spot/v3/private/record': 2.5,
|
||
'spot/v3/private/cross-margin-orders': 10,
|
||
'spot/v3/private/cross-margin-account': 10,
|
||
'spot/v3/private/cross-margin-loan-info': 10,
|
||
'spot/v3/private/cross-margin-repay-history': 10,
|
||
'spot/v3/private/margin-loan-infos': 10,
|
||
'spot/v3/private/margin-repaid-infos': 10,
|
||
'spot/v3/private/margin-ltv': 10,
|
||
# account
|
||
'asset/v3/private/transfer/inter-transfer/list/query': 50, # 60 per minute = 1 per second => cost = 50 / 1 = 50
|
||
'asset/v3/private/transfer/sub-member/list/query': 50,
|
||
'asset/v3/private/transfer/sub-member-transfer/list/query': 50,
|
||
'asset/v3/private/transfer/universal-transfer/list/query': 25,
|
||
'asset/v3/private/coin-info/query': 25, # 2/s
|
||
'asset/v3/private/deposit/address/query': 10,
|
||
'contract/v3/private/copytrading/order/list': 30, # 100 req/min = 1000 / (20 * 30) = 1.66666666667/s
|
||
'contract/v3/private/copytrading/position/list': 40, # 75 req/min = 1000 / (20 * 40) = 1.25/s
|
||
'contract/v3/private/copytrading/wallet/balance': 25, # 120 req/min = 1000 / (20 * 25) = 2/s
|
||
'contract/v3/private/position/limit-info': 25, # 120 per minute = 2 per second => cost = 50 / 2 = 25
|
||
'contract/v3/private/order/unfilled-orders': 1,
|
||
'contract/v3/private/order/list': 1,
|
||
'contract/v3/private/position/list': 1,
|
||
'contract/v3/private/execution/list': 1,
|
||
'contract/v3/private/position/closed-pnl': 1,
|
||
'contract/v3/private/account/wallet/balance': 1,
|
||
'contract/v3/private/account/fee-rate': 1,
|
||
'contract/v3/private/account/wallet/fund-records': 1,
|
||
# derivative
|
||
'unified/v3/private/order/unfilled-orders': 1,
|
||
'unified/v3/private/order/list': 1,
|
||
'unified/v3/private/position/list': 1,
|
||
'unified/v3/private/execution/list': 1,
|
||
'unified/v3/private/delivery-record': 1,
|
||
'unified/v3/private/settlement-record': 1,
|
||
'unified/v3/private/account/wallet/balance': 1,
|
||
'unified/v3/private/account/transaction-log': 1,
|
||
'unified/v3/private/account/borrow-history': 1,
|
||
'unified/v3/private/account/borrow-rate': 1,
|
||
'unified/v3/private/account/info': 1,
|
||
'user/v3/private/frozen-sub-member': 10, # 5/s
|
||
'user/v3/private/query-sub-members': 5, # 10/s
|
||
'user/v3/private/query-api': 5, # 10/s
|
||
'user/v3/private/get-member-type': 1,
|
||
'asset/v3/private/transfer/transfer-coin/list/query': 50,
|
||
'asset/v3/private/transfer/account-coin/balance/query': 50,
|
||
'asset/v3/private/transfer/account-coins/balance/query': 25,
|
||
'asset/v3/private/transfer/asset-info/query': 50,
|
||
'asset/v3/public/deposit/allowed-deposit-list/query': 0.17, # 300/s
|
||
'asset/v3/private/deposit/record/query': 10,
|
||
'asset/v3/private/withdraw/record/query': 10,
|
||
# v5
|
||
# trade
|
||
'v5/order/realtime': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/history': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/spot-borrow-check': 1, # 50/s = 1000 / (20 * 1)
|
||
# position
|
||
'v5/position/list': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/execution/list': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/closed-pnl': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/move-history': 5, # 10/s => cost = 50 / 10 = 5
|
||
# pre-upgrade
|
||
'v5/pre-upgrade/order/history': 5,
|
||
'v5/pre-upgrade/execution/list': 5,
|
||
'v5/pre-upgrade/position/closed-pnl': 5,
|
||
'v5/pre-upgrade/account/transaction-log': 5,
|
||
'v5/pre-upgrade/asset/delivery-record': 5,
|
||
'v5/pre-upgrade/asset/settlement-record': 5,
|
||
# account
|
||
'v5/account/wallet-balance': 1,
|
||
'v5/account/borrow-history': 1,
|
||
'v5/account/instruments-info': 1,
|
||
'v5/account/collateral-info': 1,
|
||
'v5/asset/coin-greeks': 1,
|
||
'v5/account/fee-rate': 10, # 5/s = 1000 / (20 * 10)
|
||
'v5/account/info': 5,
|
||
'v5/account/transaction-log': 1,
|
||
'v5/account/contract-transaction-log': 1,
|
||
'v5/account/smp-group': 1,
|
||
'v5/account/mmp-state': 5,
|
||
'v5/account/withdrawal': 5,
|
||
# asset
|
||
'v5/asset/exchange/query-coin-list': 0.5, # 100/s => cost = 50 / 100 = 0.5
|
||
'v5/asset/exchange/convert-result-query': 0.5, # 100/s => cost = 50 / 100 = 0.5
|
||
'v5/asset/exchange/query-convert-history': 0.5, # 100/s => cost = 50 / 100 = 0.5
|
||
'v5/asset/exchange/order-record': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/asset/delivery-record': 5,
|
||
'v5/asset/settlement-record': 5,
|
||
'v5/asset/transfer/query-asset-info': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/query-account-coins-balance': 25, # 2/s => cost = 50 / 2 = 25
|
||
'v5/asset/transfer/query-account-coin-balance': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/query-transfer-coin-list': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/query-inter-transfer-list': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/query-sub-member-list': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/query-universal-transfer-list': 25, # 2/s => cost = 50 / 2 = 25
|
||
'v5/asset/deposit/query-allowed-list': 5,
|
||
'v5/asset/deposit/query-record': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/deposit/query-sub-member-record': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/deposit/query-internal-record': 5,
|
||
'v5/asset/deposit/query-address': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/deposit/query-sub-member-address': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/coin/query-info': 28, # should be 25 but exceeds ratelimit unless the weight is 28 or higher
|
||
'v5/asset/withdraw/query-address': 10,
|
||
'v5/asset/withdraw/query-record': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/withdraw/withdrawable-amount': 5,
|
||
'v5/asset/withdraw/vasp/list': 5,
|
||
# user
|
||
'v5/user/query-sub-members': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/user/query-api': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/user/sub-apikeys': 5,
|
||
'v5/user/get-member-type': 5,
|
||
'v5/user/aff-customer-info': 5,
|
||
'v5/user/del-submember': 5,
|
||
'v5/user/submembers': 5,
|
||
# affilate
|
||
'v5/affiliate/aff-user-list': 5,
|
||
# spot leverage token
|
||
'v5/spot-lever-token/order-record': 1, # 50/s => cost = 50 / 50 = 1
|
||
# spot margin trade
|
||
'v5/spot-margin-trade/interest-rate-history': 5,
|
||
'v5/spot-margin-trade/state': 5,
|
||
'v5/spot-margin-trade/max-borrowable': 5,
|
||
'v5/spot-margin-trade/position-tiers': 5,
|
||
'v5/spot-margin-trade/coinstate': 5,
|
||
'v5/spot-margin-trade/repayment-available-amount': 5,
|
||
'v5/spot-cross-margin-trade/loan-info': 1, # 50/s => cost = 50 / 50 = 1
|
||
'v5/spot-cross-margin-trade/account': 1, # 50/s => cost = 50 / 50 = 1
|
||
'v5/spot-cross-margin-trade/orders': 1, # 50/s => cost = 50 / 50 = 1
|
||
'v5/spot-cross-margin-trade/repay-history': 1, # 50/s => cost = 50 / 50 = 1
|
||
# crypto loan
|
||
'v5/crypto-loan/borrowable-collateralisable-number': 5,
|
||
'v5/crypto-loan/ongoing-orders': 5,
|
||
'v5/crypto-loan/repayment-history': 5,
|
||
'v5/crypto-loan/borrow-history': 5,
|
||
'v5/crypto-loan/max-collateral-amount': 5,
|
||
'v5/crypto-loan/adjustment-history': 5,
|
||
# institutional lending
|
||
'v5/ins-loan/product-infos': 5,
|
||
'v5/ins-loan/ensure-tokens-convert': 5,
|
||
'v5/ins-loan/loan-order': 5,
|
||
'v5/ins-loan/repaid-history': 5,
|
||
'v5/ins-loan/ltv-convert': 5,
|
||
# c2c lending
|
||
'v5/lending/info': 5,
|
||
'v5/lending/history-order': 5,
|
||
'v5/lending/account': 5,
|
||
# broker
|
||
'v5/broker/earning-record': 5, # deprecated
|
||
'v5/broker/earnings-info': 5,
|
||
'v5/broker/account-info': 5,
|
||
'v5/broker/asset/query-sub-member-deposit-record': 10,
|
||
# earn
|
||
'v5/earn/product': 5,
|
||
'v5/earn/order': 5,
|
||
'v5/earn/position': 5,
|
||
'v5/earn/yield': 5,
|
||
'v5/earn/hourly-yield': 5,
|
||
},
|
||
'post': {
|
||
# spot
|
||
'spot/v3/private/order': 2.5,
|
||
'spot/v3/private/cancel-order': 2.5,
|
||
'spot/v3/private/cancel-orders': 2.5,
|
||
'spot/v3/private/cancel-orders-by-ids': 2.5,
|
||
'spot/v3/private/purchase': 2.5,
|
||
'spot/v3/private/redeem': 2.5,
|
||
'spot/v3/private/cross-margin-loan': 10,
|
||
'spot/v3/private/cross-margin-repay': 10,
|
||
# account
|
||
'asset/v3/private/transfer/inter-transfer': 150, # 20 per minute = 0.333 per second => cost = 50 / 0.3333 = 150
|
||
'asset/v3/private/withdraw/create': 300,
|
||
'asset/v3/private/withdraw/cancel': 50,
|
||
'asset/v3/private/transfer/sub-member-transfer': 150,
|
||
'asset/v3/private/transfer/transfer-sub-member-save': 150,
|
||
'asset/v3/private/transfer/universal-transfer': 10, # 5/s
|
||
'user/v3/private/create-sub-member': 10, # 5/s
|
||
'user/v3/private/create-sub-api': 10, # 5/s
|
||
'user/v3/private/update-api': 10, # 5/s
|
||
'user/v3/private/delete-api': 10, # 5/s
|
||
'user/v3/private/update-sub-api': 10, # 5/s
|
||
'user/v3/private/delete-sub-api': 10, # 5/s
|
||
# contract
|
||
'contract/v3/private/copytrading/order/create': 30, # 100 req/min = 1000 / (20 * 30) = 1.66666666667/s
|
||
'contract/v3/private/copytrading/order/cancel': 30,
|
||
'contract/v3/private/copytrading/order/close': 30,
|
||
'contract/v3/private/copytrading/position/close': 40, # 75 req/min = 1000 / (20 * 40) = 1.25/s
|
||
'contract/v3/private/copytrading/position/set-leverage': 40,
|
||
'contract/v3/private/copytrading/wallet/transfer': 25, # 120 req/min = 1000 / (20 * 25) = 2/s
|
||
'contract/v3/private/copytrading/order/trading-stop': 2.5,
|
||
'contract/v3/private/order/create': 1,
|
||
'contract/v3/private/order/cancel': 1,
|
||
'contract/v3/private/order/cancel-all': 1,
|
||
'contract/v3/private/order/replace': 1,
|
||
'contract/v3/private/position/set-auto-add-margin': 1,
|
||
'contract/v3/private/position/switch-isolated': 1,
|
||
'contract/v3/private/position/switch-mode': 1,
|
||
'contract/v3/private/position/switch-tpsl-mode': 1,
|
||
'contract/v3/private/position/set-leverage': 1,
|
||
'contract/v3/private/position/trading-stop': 1,
|
||
'contract/v3/private/position/set-risk-limit': 1,
|
||
'contract/v3/private/account/setMarginMode': 1,
|
||
# derivative
|
||
'unified/v3/private/order/create': 30, # 100 req/min(shared) = 1000 / (20 * 30) = 1.66666666667/s
|
||
'unified/v3/private/order/replace': 30,
|
||
'unified/v3/private/order/cancel': 30,
|
||
'unified/v3/private/order/create-batch': 30,
|
||
'unified/v3/private/order/replace-batch': 30,
|
||
'unified/v3/private/order/cancel-batch': 30,
|
||
'unified/v3/private/order/cancel-all': 30,
|
||
'unified/v3/private/position/set-leverage': 2.5,
|
||
'unified/v3/private/position/tpsl/switch-mode': 2.5,
|
||
'unified/v3/private/position/set-risk-limit': 2.5,
|
||
'unified/v3/private/position/trading-stop': 2.5,
|
||
'unified/v3/private/account/upgrade-unified-account': 2.5,
|
||
'unified/v3/private/account/setMarginMode': 2.5,
|
||
# tax
|
||
'fht/compliance/tax/v3/private/registertime': 50,
|
||
'fht/compliance/tax/v3/private/create': 50,
|
||
'fht/compliance/tax/v3/private/status': 50,
|
||
'fht/compliance/tax/v3/private/url': 50,
|
||
# v5
|
||
# trade
|
||
'v5/order/create': 2.5, # 20/s = 1000 / (20 * 2.5)
|
||
'v5/order/amend': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/cancel': 2.5,
|
||
'v5/order/cancel-all': 50, # 1/s = 1000 / (20 * 50)
|
||
'v5/order/create-batch': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/amend-batch': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/cancel-batch': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/order/disconnected-cancel-all': 5,
|
||
# position
|
||
'v5/position/set-leverage': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/switch-isolated': 5,
|
||
'v5/position/set-tpsl-mode': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/switch-mode': 5,
|
||
'v5/position/set-risk-limit': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/trading-stop': 5, # 10/s => cost = 50 / 10 = 5
|
||
'v5/position/set-auto-add-margin': 5,
|
||
'v5/position/add-margin': 5,
|
||
'v5/position/move-positions': 5,
|
||
'v5/position/confirm-pending-mmr': 5,
|
||
# account
|
||
'v5/account/upgrade-to-uta': 5,
|
||
'v5/account/quick-repayment': 5,
|
||
'v5/account/set-margin-mode': 5,
|
||
'v5/account/set-hedging-mode': 5,
|
||
'v5/account/mmp-modify': 5,
|
||
'v5/account/mmp-reset': 5,
|
||
'v5/account/borrow': 5,
|
||
'v5/account/repay': 5,
|
||
'v5/account/no-convert-repay': 5,
|
||
# asset
|
||
'v5/asset/exchange/quote-apply': 1, # 50/s
|
||
'v5/asset/exchange/convert-execute': 1, # 50/s
|
||
'v5/asset/transfer/inter-transfer': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/transfer/save-transfer-sub-member': 150, # 1/3/s => cost = 50 / 1/3 = 150
|
||
'v5/asset/transfer/universal-transfer': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/asset/deposit/deposit-to-account': 5,
|
||
'v5/asset/withdraw/create': 50, # 1/s => cost = 50 / 1 = 50
|
||
'v5/asset/withdraw/cancel': 50, # 1/s => cost = 50 / 1 = 50
|
||
# user
|
||
'v5/user/create-sub-member': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/create-sub-api': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/frozen-sub-member': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/update-api': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/update-sub-api': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/delete-api': 10, # 5/s => cost = 50 / 5 = 10
|
||
'v5/user/delete-sub-api': 10, # 5/s => cost = 50 / 5 = 10
|
||
# spot leverage token
|
||
'v5/spot-lever-token/purchase': 2.5, # 20/s => cost = 50 / 20 = 2.5
|
||
'v5/spot-lever-token/redeem': 2.5, # 20/s => cost = 50 / 20 = 2.5
|
||
# spot margin trade
|
||
'v5/spot-margin-trade/switch-mode': 5,
|
||
'v5/spot-margin-trade/set-leverage': 5,
|
||
'v5/spot-cross-margin-trade/loan': 2.5, # 20/s => cost = 50 / 20 = 2.5
|
||
'v5/spot-cross-margin-trade/repay': 2.5, # 20/s => cost = 50 / 20 = 2.5
|
||
'v5/spot-cross-margin-trade/switch': 2.5, # 20/s => cost = 50 / 20 = 2.5
|
||
# crypto loan
|
||
'v5/crypto-loan/borrow': 5,
|
||
'v5/crypto-loan/repay': 5,
|
||
'v5/crypto-loan/adjust-ltv': 5,
|
||
# institutional lending
|
||
'v5/ins-loan/association-uid': 5,
|
||
# c2c lending
|
||
'v5/lending/purchase': 5,
|
||
'v5/lending/redeem': 5,
|
||
'v5/lending/redeem-cancel': 5,
|
||
'v5/account/set-collateral-switch': 5,
|
||
'v5/account/set-collateral-switch-batch': 5,
|
||
# demo trading
|
||
'v5/account/demo-apply-money': 5,
|
||
# broker
|
||
'v5/broker/award/info': 5,
|
||
'v5/broker/award/distribute-award': 5,
|
||
'v5/broker/award/distribution-record': 5,
|
||
# earn
|
||
'v5/earn/place-order': 5,
|
||
},
|
||
},
|
||
},
|
||
'httpExceptions': {
|
||
'403': RateLimitExceeded, # Forbidden -- You request too many times
|
||
},
|
||
'exceptions': {
|
||
# Uncodumented explanation of error strings:
|
||
# - oc_diff: order cost needed to place self order
|
||
# - new_oc: total order cost of open orders including the order you are trying to open
|
||
# - ob: order balance - the total cost of current open orders
|
||
# - ab: available balance
|
||
'exact': {
|
||
'-10009': BadRequest, # {"ret_code":-10009,"ret_msg":"Invalid period!","result":null,"token":null}
|
||
'-1004': BadRequest, # {"ret_code":-1004,"ret_msg":"Missing required parameter \u0027symbol\u0027","ext_code":null,"ext_info":null,"result":null}
|
||
'-1021': BadRequest, # {"ret_code":-1021,"ret_msg":"Timestamp for self request is outside of the recvWindow.","ext_code":null,"ext_info":null,"result":null}
|
||
'-1103': BadRequest, # An unknown parameter was sent.
|
||
'-1140': InvalidOrder, # {"ret_code":-1140,"ret_msg":"Transaction amount lower than the minimum.","result":{},"ext_code":"","ext_info":null,"time_now":"1659204910.248576"}
|
||
'-1197': InvalidOrder, # {"ret_code":-1197,"ret_msg":"Your order quantity to buy is too large. The filled price may deviate significantly from the market price. Please try again","result":{},"ext_code":"","ext_info":null,"time_now":"1659204531.979680"}
|
||
'-2013': InvalidOrder, # {"ret_code":-2013,"ret_msg":"Order does not exist.","ext_code":null,"ext_info":null,"result":null}
|
||
'-2015': AuthenticationError, # Invalid API-key, IP, or permissions for action.
|
||
'-6017': BadRequest, # Repayment amount has exceeded the total liability
|
||
'-6025': BadRequest, # Amount to borrow cannot be lower than the min. amount to borrow(per transaction)
|
||
'-6029': BadRequest, # Amount to borrow has exceeded the user's estimated max amount to borrow
|
||
'5004': ExchangeError, # {"retCode":5004,"retMsg":"Server Timeout","result":null,"retExtInfo":{},"time":1667577060106}
|
||
'7001': BadRequest, # {"retCode":7001,"retMsg":"request params type error"}
|
||
'10001': BadRequest, # parameter error
|
||
'10002': InvalidNonce, # request expired, check your timestamp and recv_window
|
||
'10003': AuthenticationError, # Invalid apikey
|
||
'10004': AuthenticationError, # invalid sign
|
||
'10005': PermissionDenied, # permission denied for current apikey
|
||
'10006': RateLimitExceeded, # too many requests
|
||
'10007': AuthenticationError, # api_key not found in your request parameters
|
||
'10008': AccountSuspended, # User had been banned
|
||
'10009': AuthenticationError, # IP had been banned
|
||
'10010': PermissionDenied, # request ip mismatch
|
||
'10014': BadRequest, # Request is duplicate
|
||
'10016': ExchangeError, # {"retCode":10016,"retMsg":"System error. Please try again later."}
|
||
'10017': BadRequest, # request path not found or request method is invalid
|
||
'10018': RateLimitExceeded, # exceed ip rate limit
|
||
'10020': PermissionDenied, # {"retCode":10020,"retMsg":"your account is not a unified margin account, please update your account","result":null,"retExtInfo":null,"time":1664783731123}
|
||
'10024': PermissionDenied, # Compliance rules triggered
|
||
'10027': PermissionDenied, # Trading Banned
|
||
'10028': PermissionDenied, # The API can only be accessed by unified account users.
|
||
'10029': PermissionDenied, # The requested symbol is invalid, please check symbol whitelist
|
||
'12137': InvalidOrder, # {"retCode":12137,"retMsg":"Order quantity has too many decimals.","result":{},"retExtInfo":{},"time":1695900943033}
|
||
'12201': BadRequest, # {"retCode":12201,"retMsg":"Invalid orderCategory parameter.","result":{},"retExtInfo":null,"time":1666699391220}
|
||
'12141': BadRequest, # "retCode":12141,"retMsg":"Duplicate clientOrderId.","result":{},"retExtInfo":{},"time":1686134298989}
|
||
'100028': PermissionDenied, # The API cannot be accessed by unified account users.
|
||
'110001': OrderNotFound, # Order does not exist
|
||
'110003': InvalidOrder, # Order price is out of permissible range
|
||
'110004': InsufficientFunds, # Insufficient wallet balance
|
||
'110005': InvalidOrder, # position status
|
||
'110006': InsufficientFunds, # cannot afford estimated position_margin
|
||
'110007': InsufficientFunds, # {"retCode":110007,"retMsg":"ab not enough for new order","result":{},"retExtInfo":{},"time":1668838414793}
|
||
'110008': InvalidOrder, # Order has been finished or canceled
|
||
'110009': InvalidOrder, # The number of stop orders exceeds maximum limit allowed
|
||
'110010': InvalidOrder, # Order already cancelled
|
||
'110011': InvalidOrder, # Any adjustments made will trigger immediate liquidation
|
||
'110012': InsufficientFunds, # Available balance not enough
|
||
'110013': BadRequest, # Due to risk limit, cannot set leverage
|
||
'110014': InsufficientFunds, # Available balance not enough to add margin
|
||
'110015': BadRequest, # the position is in cross_margin
|
||
'110016': InvalidOrder, # Requested quantity of contracts exceeds risk limit, please adjust your risk limit level before trying again
|
||
'110017': InvalidOrder, # Reduce-only rule not satisfied
|
||
'110018': BadRequest, # userId illegal
|
||
'110019': InvalidOrder, # orderId illegal
|
||
'110020': InvalidOrder, # number of active orders greater than 500
|
||
'110021': InvalidOrder, # Open Interest exceeded
|
||
'110022': InvalidOrder, # qty has been limited, cannot modify the order to add qty
|
||
'110023': InvalidOrder, # This contract only supports position reduction operation, please contact customer service for details
|
||
'110024': BadRequest, # You have an existing position, so position mode cannot be switched
|
||
'110025': NoChange, # Position mode is not modified
|
||
'110026': MarginModeAlreadySet, # Cross/isolated margin mode is not modified
|
||
'110027': NoChange, # Margin is not modified
|
||
'110028': BadRequest, # Open orders exist, so you cannot change position mode
|
||
'110029': BadRequest, # Hedge mode is not available for self symbol
|
||
'110030': InvalidOrder, # Duplicate orderId
|
||
'110031': InvalidOrder, # risk limit info does not exists
|
||
'110032': InvalidOrder, # Illegal order
|
||
'110033': InvalidOrder, # Margin cannot be set without open position
|
||
'110034': InvalidOrder, # There is no net position
|
||
'110035': InvalidOrder, # Cancel order is not completed before liquidation
|
||
'110036': InvalidOrder, # Cross margin mode is not allowed to change leverage
|
||
'110037': InvalidOrder, # User setting list does not have self symbol
|
||
'110038': InvalidOrder, # Portfolio margin mode is not allowed to change leverage
|
||
'110039': InvalidOrder, # Maintain margin rate is too high, which may trigger liquidation
|
||
'110040': InvalidOrder, # Order will trigger forced liquidation, please resubmit the order
|
||
'110041': InvalidOrder, # Skip liquidation is not allowed when a position or maker order exists
|
||
'110042': InvalidOrder, # Pre-delivery status can only reduce positions
|
||
'110043': BadRequest, # Set leverage not modified
|
||
'110044': InsufficientFunds, # Insufficient available margin
|
||
'110045': InsufficientFunds, # Insufficient wallet balance
|
||
'110046': BadRequest, # Any adjustments made will trigger immediate liquidation
|
||
'110047': BadRequest, # Risk limit cannot be adjusted due to insufficient available margin
|
||
'110048': BadRequest, # Risk limit cannot be adjusted current/expected position value held exceeds the revised risk limit
|
||
'110049': BadRequest, # Tick notes can only be numbers
|
||
'110050': BadRequest, # Coin is not in the range of selected
|
||
'110051': InsufficientFunds, # The user's available balance cannot cover the lowest price of the current market
|
||
'110052': InsufficientFunds, # User's available balance is insufficient to set a price
|
||
'110053': InsufficientFunds, # The user's available balance cannot cover the current market price and upper limit price
|
||
'110054': InvalidOrder, # This position has at least one take profit link order, so the take profit and stop loss mode cannot be switched
|
||
'110055': InvalidOrder, # This position has at least one stop loss link order, so the take profit and stop loss mode cannot be switched
|
||
'110056': InvalidOrder, # This position has at least one trailing stop link order, so the take profit and stop loss mode cannot be switched
|
||
'110057': InvalidOrder, # Conditional order or limit order contains TP/SL related params
|
||
'110058': InvalidOrder, # Insufficient number of remaining position size to set take profit and stop loss
|
||
'110059': InvalidOrder, # In the case of partial filled of the open order, it is not allowed to modify the take profit and stop loss settings of the open order
|
||
'110060': BadRequest, # Under full TP/SL mode, it is not allowed to modify TP/SL
|
||
'110061': BadRequest, # Under partial TP/SL mode, TP/SL set more than 20
|
||
'110062': BadRequest, # Institution MMP profile not found.
|
||
'110063': ExchangeError, # Settlement in progress! xxx not available for trades.
|
||
'110064': InvalidOrder, # The number of contracts modified cannot be less than or equal to the filled quantity
|
||
'110065': PermissionDenied, # MMP hasn't yet been enabled for your account. Please contact your BD manager.
|
||
'110066': ExchangeError, # No trading is allowed at the current time
|
||
'110067': PermissionDenied, # unified account is not support
|
||
'110068': PermissionDenied, # Leveraged user trading is not allowed
|
||
'110069': PermissionDenied, # Do not allow OTC lending users to trade
|
||
'110070': InvalidOrder, # ETP symbols are not allowed to be traded
|
||
'110071': ExchangeError, # Sorry, we're revamping the Unified Margin Account! Currently, new upgrades are not supported. If you have any questions, please contact our 24/7 customer support.
|
||
'110072': InvalidOrder, # OrderLinkedID is duplicate
|
||
'110073': ExchangeError, # Set margin mode failed
|
||
'110092': InvalidOrder, # expect Rising, but trigger_price[XXXXX] <= current[XXXXX]
|
||
'110093': InvalidOrder, # expect Falling, but trigger_price[XXXXX] >= current[XXXXX]
|
||
'110094': InvalidOrder, # Order notional value below the lower limit
|
||
'130006': InvalidOrder, # {"ret_code":130006,"ret_msg":"The number of contracts exceeds maximum limit allowed: too large","ext_code":"","ext_info":"","result":null,"time_now":"1658397095.099030","rate_limit_status":99,"rate_limit_reset_ms":1658397095097,"rate_limit":100}
|
||
'130021': InsufficientFunds, # {"ret_code":130021,"ret_msg":"orderfix price failed for CannotAffordOrderCost.","ext_code":"","ext_info":"","result":null,"time_now":"1644588250.204878","rate_limit_status":98,"rate_limit_reset_ms":1644588250200,"rate_limit":100} | {"ret_code":130021,"ret_msg":"oc_diff[1707966351], new_oc[1707966351] with ob[....]+AB[....]","ext_code":"","ext_info":"","result":null,"time_now":"1658395300.872766","rate_limit_status":99,"rate_limit_reset_ms":1658395300855,"rate_limit":100} caused issues/9149#issuecomment-1146559498
|
||
'130074': InvalidOrder, # {"ret_code":130074,"ret_msg":"expect Rising, but trigger_price[190000000] \u003c= current[211280000]??LastPrice","ext_code":"","ext_info":"","result":null,"time_now":"1655386638.067076","rate_limit_status":97,"rate_limit_reset_ms":1655386638065,"rate_limit":100}
|
||
'131001': InsufficientFunds, # {"retCode":131001,"retMsg":"the available balance is not sufficient to cover the handling fee","result":{},"retExtInfo":{},"time":1666892821245}
|
||
'131084': ExchangeError, # Withdraw failed because of Uta Upgrading
|
||
'131200': ExchangeError, # Service error
|
||
'131201': ExchangeError, # Internal error
|
||
'131202': BadRequest, # Invalid memberId
|
||
'131203': BadRequest, # Request parameter error
|
||
'131204': BadRequest, # Account info error
|
||
'131205': BadRequest, # Query transfer error
|
||
'131206': ExchangeError, # Fail to transfer
|
||
'131207': BadRequest, # Account not exist
|
||
'131208': ExchangeError, # Forbid transfer
|
||
'131209': BadRequest, # Get subMember relation error
|
||
'131210': BadRequest, # Amount accuracy error
|
||
'131211': BadRequest, # fromAccountType can't be the same
|
||
'131212': InsufficientFunds, # Insufficient balance
|
||
'131213': BadRequest, # TransferLTV check error
|
||
'131214': BadRequest, # TransferId exist
|
||
'131215': BadRequest, # Amount error
|
||
'131216': ExchangeError, # Query balance error
|
||
'131217': ExchangeError, # Risk check error
|
||
'131231': NotSupported, # Transfers into self account are not supported
|
||
'131232': NotSupported, # Transfers out self account are not supported
|
||
'131002': BadRequest, # Parameter error
|
||
'131003': ExchangeError, # Interal error
|
||
'131004': AuthenticationError, # KYC needed
|
||
'131085': InsufficientFunds, # Withdrawal amount is greater than your availale balance(the deplayed withdrawal is triggered)
|
||
'131086': BadRequest, # Withdrawal amount exceeds risk limit(the risk limit of margin trade is triggered)
|
||
'131088': BadRequest, # The withdrawal amount exceeds the remaining withdrawal limit of your identity verification level. The current available amount for withdrawal : %s
|
||
'131089': BadRequest, # User sensitive operation, withdrawal is prohibited within 24 hours
|
||
'131090': ExchangeError, # User withdraw has been banned
|
||
'131091': ExchangeError, # Blocked login status does not allow withdrawals
|
||
'131092': ExchangeError, # User status is abnormal
|
||
'131093': ExchangeError, # The withdrawal address is not in the whitelist
|
||
'131094': BadRequest, # UserId is not in the whitelist
|
||
'131095': BadRequest, # Withdrawl amount exceeds the 24 hour platform limit
|
||
'131096': BadRequest, # Withdraw amount does not satify the lower limit or upper limit
|
||
'131097': ExchangeError, # Withdrawal of self currency has been closed
|
||
'131098': ExchangeError, # Withdrawal currently is not availble from new address
|
||
'131099': ExchangeError, # Hot wallet status can cancel the withdraw
|
||
'140001': OrderNotFound, # Order does not exist
|
||
'140003': InvalidOrder, # Order price is out of permissible range
|
||
'140004': InsufficientFunds, # Insufficient wallet balance
|
||
'140005': InvalidOrder, # position status
|
||
'140006': InsufficientFunds, # cannot afford estimated position_margin
|
||
'140007': InsufficientFunds, # Insufficient available balance
|
||
'140008': InvalidOrder, # Order has been finished or canceled
|
||
'140009': InvalidOrder, # The number of stop orders exceeds maximum limit allowed
|
||
'140010': InvalidOrder, # Order already cancelled
|
||
'140011': InvalidOrder, # Any adjustments made will trigger immediate liquidation
|
||
'140012': InsufficientFunds, # Available balance not enough
|
||
'140013': BadRequest, # Due to risk limit, cannot set leverage
|
||
'140014': InsufficientFunds, # Available balance not enough to add margin
|
||
'140015': InvalidOrder, # the position is in cross_margin
|
||
'140016': InvalidOrder, # Requested quantity of contracts exceeds risk limit, please adjust your risk limit level before trying again
|
||
'140017': InvalidOrder, # Reduce-only rule not satisfied
|
||
'140018': BadRequest, # userId illegal
|
||
'140019': InvalidOrder, # orderId illegal
|
||
'140020': InvalidOrder, # number of active orders greater than 500
|
||
'140021': InvalidOrder, # Open Interest exceeded
|
||
'140022': InvalidOrder, # qty has been limited, cannot modify the order to add qty
|
||
'140023': InvalidOrder, # This contract only supports position reduction operation, please contact customer service for details
|
||
'140024': BadRequest, # You have an existing position, so position mode cannot be switched
|
||
'140025': BadRequest, # Position mode is not modified
|
||
'140026': BadRequest, # Cross/isolated margin mode is not modified
|
||
'140027': BadRequest, # Margin is not modified
|
||
'140028': InvalidOrder, # Open orders exist, so you cannot change position mode
|
||
'140029': BadRequest, # Hedge mode is not available for self symbol
|
||
'140030': InvalidOrder, # Duplicate orderId
|
||
'140031': BadRequest, # risk limit info does not exists
|
||
'140032': InvalidOrder, # Illegal order
|
||
'140033': InvalidOrder, # Margin cannot be set without open position
|
||
'140034': InvalidOrder, # There is no net position
|
||
'140035': InvalidOrder, # Cancel order is not completed before liquidation
|
||
'140036': BadRequest, # Cross margin mode is not allowed to change leverage
|
||
'140037': InvalidOrder, # User setting list does not have self symbol
|
||
'140038': BadRequest, # Portfolio margin mode is not allowed to change leverage
|
||
'140039': BadRequest, # Maintain margin rate is too high, which may trigger liquidation
|
||
'140040': InvalidOrder, # Order will trigger forced liquidation, please resubmit the order
|
||
'140041': InvalidOrder, # Skip liquidation is not allowed when a position or maker order exists
|
||
'140042': InvalidOrder, # Pre-delivery status can only reduce positions
|
||
'140043': BadRequest, # Set leverage not modified
|
||
'140044': InsufficientFunds, # Insufficient available margin
|
||
'140045': InsufficientFunds, # Insufficient wallet balance
|
||
'140046': BadRequest, # Any adjustments made will trigger immediate liquidation
|
||
'140047': BadRequest, # Risk limit cannot be adjusted due to insufficient available margin
|
||
'140048': BadRequest, # Risk limit cannot be adjusted current/expected position value held exceeds the revised risk limit
|
||
'140049': BadRequest, # Tick notes can only be numbers
|
||
'140050': InvalidOrder, # Coin is not in the range of selected
|
||
'140051': InsufficientFunds, # The user's available balance cannot cover the lowest price of the current market
|
||
'140052': InsufficientFunds, # User's available balance is insufficient to set a price
|
||
'140053': InsufficientFunds, # The user's available balance cannot cover the current market price and upper limit price
|
||
'140054': InvalidOrder, # This position has at least one take profit link order, so the take profit and stop loss mode cannot be switched
|
||
'140055': InvalidOrder, # This position has at least one stop loss link order, so the take profit and stop loss mode cannot be switched
|
||
'140056': InvalidOrder, # This position has at least one trailing stop link order, so the take profit and stop loss mode cannot be switched
|
||
'140057': InvalidOrder, # Conditional order or limit order contains TP/SL related params
|
||
'140058': InvalidOrder, # Insufficient number of remaining position size to set take profit and stop loss
|
||
'140059': InvalidOrder, # In the case of partial filled of the open order, it is not allowed to modify the take profit and stop loss settings of the open order
|
||
'140060': BadRequest, # Under full TP/SL mode, it is not allowed to modify TP/SL
|
||
'140061': BadRequest, # Under partial TP/SL mode, TP/SL set more than 20
|
||
'140062': BadRequest, # Institution MMP profile not found.
|
||
'140063': ExchangeError, # Settlement in progress! xxx not available for trades.
|
||
'140064': InvalidOrder, # The number of contracts modified cannot be less than or equal to the filled quantity
|
||
'140065': PermissionDenied, # MMP hasn't yet been enabled for your account. Please contact your BD manager.
|
||
'140066': ExchangeError, # No trading is allowed at the current time
|
||
'140067': PermissionDenied, # unified account is not support
|
||
'140068': PermissionDenied, # Leveraged user trading is not allowed
|
||
'140069': PermissionDenied, # Do not allow OTC lending users to trade
|
||
'140070': InvalidOrder, # ETP symbols are not allowed to be traded
|
||
'170001': ExchangeError, # Internal error.
|
||
'170005': InvalidOrder, # Too many new orders; current limit is %s orders per %s.
|
||
'170007': RequestTimeout, # Timeout waiting for response from backend server.
|
||
'170010': InvalidOrder, # Purchase failed: Exceed the maximum position limit of leveraged tokens, the current available limit is %s USDT
|
||
'170011': InvalidOrder, # "Purchase failed: Exceed the maximum position limit of innovation tokens,
|
||
'170019': InvalidOrder, # the current available limit is replaceKey0 USDT"
|
||
'170031': ExchangeError, # The feature has been suspended
|
||
'170032': ExchangeError, # Network error. Please try again later
|
||
'170033': InsufficientFunds, # margin Insufficient account balance
|
||
'170034': InsufficientFunds, # Liability over flow in spot leverage trade!
|
||
'170035': BadRequest, # Submitted to the system for processing!
|
||
'170036': BadRequest, # You haven't enabled Cross Margin Trading yet. To do so, please head to the PC trading site or the Bybit app
|
||
'170037': BadRequest, # Cross Margin Trading not yet supported by the selected coin
|
||
'170105': BadRequest, # Parameter '%s' was empty.
|
||
'170115': InvalidOrder, # Invalid timeInForce.
|
||
'170116': InvalidOrder, # Invalid orderType.
|
||
'170117': InvalidOrder, # Invalid side.
|
||
'170121': InvalidOrder, # Invalid symbol.
|
||
'170124': InvalidOrder, # Order amount too large.
|
||
'170130': BadRequest, # Data sent for paramter '%s' is not valid.
|
||
'170131': InsufficientFunds, # Balance insufficient
|
||
'170132': InvalidOrder, # Order price too high.
|
||
'170133': InvalidOrder, # Order price lower than the minimum.
|
||
'170134': InvalidOrder, # Order price decimal too long.
|
||
'170135': InvalidOrder, # Order quantity too large.
|
||
'170136': InvalidOrder, # Order quantity lower than the minimum.
|
||
'170137': InvalidOrder, # Order volume decimal too long
|
||
'170139': InvalidOrder, # Order has been filled.
|
||
'170140': InvalidOrder, # Transaction amount lower than the minimum.
|
||
'170141': InvalidOrder, # Duplicate clientOrderId
|
||
'170142': InvalidOrder, # Order has been canceled
|
||
'170143': InvalidOrder, # Cannot be found on order book
|
||
'170144': InvalidOrder, # Order has been locked
|
||
'170145': InvalidOrder, # This order type does not support cancellation
|
||
'170146': InvalidOrder, # Order creation timeout
|
||
'170147': InvalidOrder, # Order cancellation timeout
|
||
'170148': InvalidOrder, # Market order amount decimal too long
|
||
'170149': ExchangeError, # Create order failed
|
||
'170150': ExchangeError, # Cancel order failed
|
||
'170151': InvalidOrder, # The trading pair is not open yet
|
||
'170157': InvalidOrder, # The trading pair is not available for api trading
|
||
'170159': InvalidOrder, # Market Order is not supported within the first %s minutes of newly launched pairs due to risk control.
|
||
'170190': InvalidOrder, # Cancel order has been finished
|
||
'170191': InvalidOrder, # Can not cancel order, please try again later
|
||
'170192': InvalidOrder, # Order price cannot be higher than %s .
|
||
'170193': InvalidOrder, # Buy order price cannot be higher than %s.
|
||
'170194': InvalidOrder, # Sell order price cannot be lower than %s.
|
||
'170195': InvalidOrder, # Please note that your order may not be filled
|
||
'170196': InvalidOrder, # Please note that your order may not be filled
|
||
'170197': InvalidOrder, # Your order quantity to buy is too large. The filled price may deviate significantly from the market price. Please try again
|
||
'170198': InvalidOrder, # Your order quantity to sell is too large. The filled price may deviate significantly from the market price. Please try again
|
||
'170199': InvalidOrder, # Your order quantity to buy is too large. The filled price may deviate significantly from the nav. Please try again.
|
||
'170200': InvalidOrder, # Your order quantity to sell is too large. The filled price may deviate significantly from the nav. Please try again.
|
||
'170201': PermissionDenied, # Your account has been restricted for trades. If you have any questions, please email us at support@bybit.com
|
||
'170202': InvalidOrder, # Invalid orderFilter parameter.
|
||
'170203': InvalidOrder, # Please enter the TP/SL price.
|
||
'170204': InvalidOrder, # trigger price cannot be higher than 110% price.
|
||
'170206': InvalidOrder, # trigger price cannot be lower than 90% of qty.
|
||
'170210': InvalidOrder, # New order rejected.
|
||
'170213': OrderNotFound, # Order does not exist.
|
||
'170217': InvalidOrder, # Only LIMIT-MAKER order is supported for the current pair.
|
||
'170218': InvalidOrder, # The LIMIT-MAKER order is rejected due to invalid price.
|
||
'170221': BadRequest, # This coin does not exist.
|
||
'170222': RateLimitExceeded, # Too many hasattr(self, requests) time frame.
|
||
'170223': InsufficientFunds, # Your Spot Account with Institutional Lending triggers an alert or liquidation.
|
||
'170224': PermissionDenied, # You're not a user of the Innovation Zone.
|
||
'170226': InsufficientFunds, # Your Spot Account for Margin Trading is being liquidated.
|
||
'170227': ExchangeError, # This feature is not supported.
|
||
'170228': InvalidOrder, # The purchase amount of each order exceeds the estimated maximum purchase amount.
|
||
'170229': InvalidOrder, # The sell quantity per order exceeds the estimated maximum sell quantity.
|
||
'170234': ExchangeError, # System Error
|
||
'170241': ManualInteractionNeeded, # To proceed with trading, users must read through and confirm that they fully understand the project's risk disclosure document.
|
||
'175000': InvalidOrder, # The serialNum is already in use.
|
||
'175001': InvalidOrder, # Daily purchase limit has been exceeded. Please try again later.
|
||
'175002': InvalidOrder, # There's a large number of purchase orders. Please try again later.
|
||
'175003': InsufficientFunds, # Insufficient available balance. Please make a deposit and try again.
|
||
'175004': InvalidOrder, # Daily redemption limit has been exceeded. Please try again later.
|
||
'175005': InvalidOrder, # There's a large number of redemption orders. Please try again later.
|
||
'175006': InsufficientFunds, # Insufficient available balance. Please make a deposit and try again.
|
||
'175007': InvalidOrder, # Order not found.
|
||
'175008': InvalidOrder, # Purchase period hasn't started yet.
|
||
'175009': InvalidOrder, # Purchase amount has exceeded the upper limit.
|
||
'175010': PermissionDenied, # You haven't passed the quiz yet! To purchase and/or redeem an LT, please complete the quiz first.
|
||
'175012': InvalidOrder, # Redemption period hasn't started yet.
|
||
'175013': InvalidOrder, # Redemption amount has exceeded the upper limit.
|
||
'175014': InvalidOrder, # Purchase of the LT has been temporarily suspended.
|
||
'175015': InvalidOrder, # Redemption of the LT has been temporarily suspended.
|
||
'175016': InvalidOrder, # Invalid format. Please check the length and numeric precision.
|
||
'175017': InvalidOrder, # Failed to place order:Exceed the maximum position limit of leveraged tokens, the current available limit is XXXX USDT
|
||
'175027': ExchangeError, # Subscriptions and redemptions are temporarily unavailable while account upgrade is in progress
|
||
'176002': BadRequest, # Query user account info error
|
||
'176004': BadRequest, # Query order history start time exceeds end time
|
||
'176003': BadRequest, # Query user loan history error
|
||
'176006': BadRequest, # Repayment Failed
|
||
'176005': BadRequest, # Failed to borrow
|
||
'176008': BadRequest, # You haven't enabled Cross Margin Trading yet. To do so
|
||
'176007': BadRequest, # User not found
|
||
'176010': BadRequest, # Failed to locate the coins to borrow
|
||
'176009': BadRequest, # You haven't enabled Cross Margin Trading yet. To do so
|
||
'176012': BadRequest, # Pair not available
|
||
'176011': BadRequest, # Cross Margin Trading not yet supported by the selected coin
|
||
'176014': BadRequest, # Repeated repayment requests
|
||
'176013': BadRequest, # Cross Margin Trading not yet supported by the selected pair
|
||
'176015': InsufficientFunds, # Insufficient available balance
|
||
'176016': BadRequest, # No repayment required
|
||
'176017': BadRequest, # Repayment amount has exceeded the total liability
|
||
'176018': BadRequest, # Settlement in progress
|
||
'176019': BadRequest, # Liquidation in progress
|
||
'176020': BadRequest, # Failed to locate repayment history
|
||
'176021': BadRequest, # Repeated borrowing requests
|
||
'176022': BadRequest, # Coins to borrow not generally available yet
|
||
'176023': BadRequest, # Pair to borrow not generally available yet
|
||
'176024': BadRequest, # Invalid user status
|
||
'176025': BadRequest, # Amount to borrow cannot be lower than the min. amount to borrow(per transaction)
|
||
'176026': BadRequest, # Amount to borrow cannot be larger than the max. amount to borrow(per transaction)
|
||
'176027': BadRequest, # Amount to borrow cannot be higher than the max. amount to borrow per user
|
||
'176028': BadRequest, # Amount to borrow has exceeded Bybit's max. amount to borrow
|
||
'176029': BadRequest, # Amount to borrow has exceeded the user's estimated max. amount to borrow
|
||
'176030': BadRequest, # Query user loan info error
|
||
'176031': BadRequest, # Number of decimals has exceeded the maximum precision
|
||
'176034': BadRequest, # The leverage ratio is out of range
|
||
'176035': PermissionDenied, # Failed to close the leverage switch during liquidation
|
||
'176036': PermissionDenied, # Failed to adjust leverage switch during forced liquidation
|
||
'176037': PermissionDenied, # For non-unified transaction users, the operation failed
|
||
'176038': BadRequest, # The spot leverage is closed and the current operation is not allowed
|
||
'176039': BadRequest, # Borrowing, current operation is not allowed
|
||
'176040': BadRequest, # There is a spot leverage order, and the adjustment of the leverage switch failed!
|
||
'181000': BadRequest, # category is null
|
||
'181001': BadRequest, # category only support linear or option or spot.
|
||
'181002': InvalidOrder, # symbol is null.
|
||
'181003': InvalidOrder, # side is null.
|
||
'181004': InvalidOrder, # side only support Buy or Sell.
|
||
'182000': InvalidOrder, # symbol related quote price is null
|
||
'181017': BadRequest, # OrderStatus must be final status
|
||
'20001': OrderNotFound, # Order not exists
|
||
'20003': InvalidOrder, # missing parameter side
|
||
'20004': InvalidOrder, # invalid parameter side
|
||
'20005': InvalidOrder, # missing parameter symbol
|
||
'20006': InvalidOrder, # invalid parameter symbol
|
||
'20007': InvalidOrder, # missing parameter order_type
|
||
'20008': InvalidOrder, # invalid parameter order_type
|
||
'20009': InvalidOrder, # missing parameter qty
|
||
'20010': InvalidOrder, # qty must be greater than 0
|
||
'20011': InvalidOrder, # qty must be an integer
|
||
'20012': InvalidOrder, # qty must be greater than zero and less than 1 million
|
||
'20013': InvalidOrder, # missing parameter price
|
||
'20014': InvalidOrder, # price must be greater than 0
|
||
'20015': InvalidOrder, # missing parameter time_in_force
|
||
'20016': InvalidOrder, # invalid value for parameter time_in_force
|
||
'20017': InvalidOrder, # missing parameter order_id
|
||
'20018': InvalidOrder, # invalid date format
|
||
'20019': InvalidOrder, # missing parameter stop_px
|
||
'20020': InvalidOrder, # missing parameter base_price
|
||
'20021': InvalidOrder, # missing parameter stop_order_id
|
||
'20022': BadRequest, # missing parameter leverage
|
||
'20023': BadRequest, # leverage must be a number
|
||
'20031': BadRequest, # leverage must be greater than zero
|
||
'20070': BadRequest, # missing parameter margin
|
||
'20071': BadRequest, # margin must be greater than zero
|
||
'20084': BadRequest, # order_id or order_link_id is required
|
||
'30001': BadRequest, # order_link_id is repeated
|
||
'30003': InvalidOrder, # qty must be more than the minimum allowed
|
||
'30004': InvalidOrder, # qty must be less than the maximum allowed
|
||
'30005': InvalidOrder, # price exceeds maximum allowed
|
||
'30007': InvalidOrder, # price exceeds minimum allowed
|
||
'30008': InvalidOrder, # invalid order_type
|
||
'30009': ExchangeError, # no position found
|
||
'30010': InsufficientFunds, # insufficient wallet balance
|
||
'30011': PermissionDenied, # operation not allowed is undergoing liquidation
|
||
'30012': PermissionDenied, # operation not allowed is undergoing ADL
|
||
'30013': PermissionDenied, # position is in liq or adl status
|
||
'30014': InvalidOrder, # invalid closing order, qty should not greater than size
|
||
'30015': InvalidOrder, # invalid closing order, side should be opposite
|
||
'30016': ExchangeError, # TS and SL must be cancelled first while closing position
|
||
'30017': InvalidOrder, # estimated fill price cannot be lower than current Buy liq_price
|
||
'30018': InvalidOrder, # estimated fill price cannot be higher than current Sell liq_price
|
||
'30019': InvalidOrder, # cannot attach TP/SL params for non-zero position when placing non-opening position order
|
||
'30020': InvalidOrder, # position already has TP/SL params
|
||
'30021': InvalidOrder, # cannot afford estimated position_margin
|
||
'30022': InvalidOrder, # estimated buy liq_price cannot be higher than current mark_price
|
||
'30023': InvalidOrder, # estimated sell liq_price cannot be lower than current mark_price
|
||
'30024': InvalidOrder, # cannot set TP/SL/TS for zero-position
|
||
'30025': InvalidOrder, # trigger price should bigger than 10% of last price
|
||
'30026': InvalidOrder, # price too high
|
||
'30027': InvalidOrder, # price set for Take profit should be higher than Last Traded Price
|
||
'30028': InvalidOrder, # price set for Stop loss should be between Liquidation price and Last Traded Price
|
||
'30029': InvalidOrder, # price set for Stop loss should be between Last Traded Price and Liquidation price
|
||
'30030': InvalidOrder, # price set for Take profit should be lower than Last Traded Price
|
||
'30031': InsufficientFunds, # insufficient available balance for order cost
|
||
'30032': InvalidOrder, # order has been filled or cancelled
|
||
'30033': RateLimitExceeded, # The number of stop orders exceeds maximum limit allowed
|
||
'30034': OrderNotFound, # no order found
|
||
'30035': RateLimitExceeded, # too fast to cancel
|
||
'30036': ExchangeError, # the expected position value after order execution exceeds the current risk limit
|
||
'30037': InvalidOrder, # order already cancelled
|
||
'30041': ExchangeError, # no position found
|
||
'30042': InsufficientFunds, # insufficient wallet balance
|
||
'30043': InvalidOrder, # operation not allowed is undergoing liquidation
|
||
'30044': InvalidOrder, # operation not allowed is undergoing AD
|
||
'30045': InvalidOrder, # operation not allowed is not normal status
|
||
'30049': InsufficientFunds, # insufficient available balance
|
||
'30050': ExchangeError, # any adjustments made will trigger immediate liquidation
|
||
'30051': ExchangeError, # due to risk limit, cannot adjust leverage
|
||
'30052': ExchangeError, # leverage can not less than 1
|
||
'30054': ExchangeError, # position margin is invalid
|
||
'30057': ExchangeError, # requested quantity of contracts exceeds risk limit
|
||
'30063': ExchangeError, # reduce-only rule not satisfied
|
||
'30067': InsufficientFunds, # insufficient available balance
|
||
'30068': ExchangeError, # exit value must be positive
|
||
'30074': InvalidOrder, # can't create the stop order, because you expect the order will be triggered when the LastPrice(or IndexPrice、 MarkPrice, determined by trigger_by) is raising to stop_px, but the LastPrice(or IndexPrice、 MarkPrice) is already equal to or greater than stop_px, please adjust base_price or stop_px
|
||
'30075': InvalidOrder, # can't create the stop order, because you expect the order will be triggered when the LastPrice(or IndexPrice、 MarkPrice, determined by trigger_by) is falling to stop_px, but the LastPrice(or IndexPrice、 MarkPrice) is already equal to or less than stop_px, please adjust base_price or stop_px
|
||
'30078': ExchangeError, # {"ret_code":30078,"ret_msg":"","ext_code":"","ext_info":"","result":null,"time_now":"1644853040.916000","rate_limit_status":73,"rate_limit_reset_ms":1644853040912,"rate_limit":75}
|
||
# '30084': BadRequest, # Isolated not modified, see handleErrors below
|
||
'33004': AuthenticationError, # apikey already expired
|
||
'34026': ExchangeError, # the limit is no change
|
||
'34036': BadRequest, # {"ret_code":34036,"ret_msg":"leverage not modified","ext_code":"","ext_info":"","result":null,"time_now":"1652376449.258918","rate_limit_status":74,"rate_limit_reset_ms":1652376449255,"rate_limit":75}
|
||
'35015': BadRequest, # {"ret_code":35015,"ret_msg":"Qty not in range","ext_code":"","ext_info":"","result":null,"time_now":"1652277215.821362","rate_limit_status":99,"rate_limit_reset_ms":1652277215819,"rate_limit":100}
|
||
'340099': ExchangeError, # Server error
|
||
'3400045': ExchangeError, # Set margin mode failed
|
||
'3100116': BadRequest, # {"retCode":3100116,"retMsg":"Order quantity below the lower limit 0.01.","result":null,"retExtMap":{"key0":"0.01"}}
|
||
'3100198': BadRequest, # {"retCode":3100198,"retMsg":"orderLinkId can not be empty.","result":null,"retExtMap":{}}
|
||
'3200300': InsufficientFunds, # {"retCode":3200300,"retMsg":"Insufficient margin balance.","result":null,"retExtMap":{}}
|
||
},
|
||
'broad': {
|
||
'Not supported symbols': BadSymbol, # {"retCode":10001,"retMsg":"Not supported symbols","result":{},"retExtInfo":{},"time":1726147060461}
|
||
'Request timeout': RequestTimeout, # {"retCode":10016,"retMsg":"Request timeout, please try again later","result":{},"retExtInfo":{},"time":1675307914985}
|
||
'unknown orderInfo': OrderNotFound, # {"ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100}
|
||
'invalid api_key': AuthenticationError, # {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"}
|
||
# the below two issues are caused: issues/9149#issuecomment-1146559498, when response is such: {"ret_code":130021,"ret_msg":"oc_diff[1707966351], new_oc[1707966351] with ob[....]+AB[....]","ext_code":"","ext_info":"","result":null,"time_now":"1658395300.872766","rate_limit_status":99,"rate_limit_reset_ms":1658395300855,"rate_limit":100}
|
||
'oc_diff': InsufficientFunds,
|
||
'new_oc': InsufficientFunds,
|
||
'openapi sign params error!': AuthenticationError, # {"retCode":10001,"retMsg":"empty value: apiTimestamp[] apiKey[] apiSignature[xxxxxxxxxxxxxxxxxxxxxxx]: openapi sign params error!","result":null,"retExtInfo":null,"time":1664789597123}
|
||
},
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
'options': {
|
||
'usePrivateInstrumentsInfo': False,
|
||
'enableDemoTrading': False,
|
||
'fetchMarkets': {
|
||
'types': ['spot', 'linear', 'inverse', 'option'],
|
||
},
|
||
'enableUnifiedMargin': None,
|
||
'enableUnifiedAccount': None,
|
||
'unifiedMarginStatus': None,
|
||
'createMarketBuyOrderRequiresPrice': False, # only True for classic accounts
|
||
'createUnifiedMarginAccount': False,
|
||
'defaultType': 'swap', # 'swap', 'future', 'option', 'spot'
|
||
'defaultSubType': 'linear', # 'linear', 'inverse'
|
||
'defaultSettle': 'USDT', # USDC for USDC settled markets
|
||
'code': 'BTC',
|
||
'recvWindow': 5 * 1000, # 5 sec default
|
||
'timeDifference': 0, # the difference between system clock and exchange server clock
|
||
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
|
||
'loadAllOptions': False, # load all possible option markets, adds signficant load time
|
||
'loadExpiredOptions': False, # loads expired options, to load all possible expired options set loadAllOptions to True
|
||
'brokerId': 'CCXT',
|
||
'accountsByType': {
|
||
'spot': 'SPOT',
|
||
'margin': 'SPOT',
|
||
'future': 'CONTRACT',
|
||
'swap': 'CONTRACT',
|
||
'option': 'OPTION',
|
||
'investment': 'INVESTMENT',
|
||
'unified': 'UNIFIED',
|
||
'funding': 'FUND',
|
||
'fund': 'FUND',
|
||
'contract': 'CONTRACT',
|
||
},
|
||
'accountsById': {
|
||
'SPOT': 'spot',
|
||
'MARGIN': 'spot',
|
||
'CONTRACT': 'contract',
|
||
'OPTION': 'option',
|
||
'INVESTMENT': 'investment',
|
||
'UNIFIED': 'unified',
|
||
'FUND': 'fund',
|
||
},
|
||
'networks': {
|
||
'ERC20': 'ETH',
|
||
'TRC20': 'TRX',
|
||
'BEP20': 'BSC',
|
||
'SOL': 'SOL',
|
||
'ACA': 'ACA',
|
||
'ADA': 'ADA',
|
||
'ALGO': 'ALGO',
|
||
'APT': 'APTOS',
|
||
'AR': 'AR',
|
||
'ARBONE': 'ARBI',
|
||
'AVAXC': 'CAVAX',
|
||
'AVAXX': 'XAVAX',
|
||
'ATOM': 'ATOM',
|
||
'BCH': 'BCH',
|
||
'BEP2': 'BNB',
|
||
'CHZ': 'CHZ',
|
||
'DCR': 'DCR',
|
||
'DGB': 'DGB',
|
||
'DOGE': 'DOGE',
|
||
'DOT': 'DOT',
|
||
'EGLD': 'EGLD',
|
||
'EOS': 'EOS',
|
||
'ETC': 'ETC',
|
||
'ETHF': 'ETHF',
|
||
'ETHW': 'ETHW',
|
||
'FIL': 'FIL',
|
||
'STEP': 'FITFI',
|
||
'FLOW': 'FLOW',
|
||
'FTM': 'FTM',
|
||
'GLMR': 'GLMR',
|
||
'HBAR': 'HBAR',
|
||
'HNT': 'HNT',
|
||
'ICP': 'ICP',
|
||
'ICX': 'ICX',
|
||
'KDA': 'KDA',
|
||
'KLAY': 'KLAY',
|
||
'KMA': 'KMA',
|
||
'KSM': 'KSM',
|
||
'LTC': 'LTC',
|
||
# 'TERRA': 'LUNANEW',
|
||
# 'TERRACLASSIC': 'LUNA',
|
||
'MATIC': 'MATIC',
|
||
'MINA': 'MINA',
|
||
'MOVR': 'MOVR',
|
||
'NEAR': 'NEAR',
|
||
'NEM': 'NEM',
|
||
'OASYS': 'OAS',
|
||
'OASIS': 'ROSE',
|
||
'OMNI': 'OMNI',
|
||
'ONE': 'ONE',
|
||
'OPTIMISM': 'OP',
|
||
'POKT': 'POKT',
|
||
'QTUM': 'QTUM',
|
||
'RVN': 'RVN',
|
||
'SC': 'SC',
|
||
'SCRT': 'SCRT',
|
||
'STX': 'STX',
|
||
'THETA': 'THETA',
|
||
'TON': 'TON',
|
||
'WAVES': 'WAVES',
|
||
'WAX': 'WAXP',
|
||
'XDC': 'XDC',
|
||
'XEC': 'XEC',
|
||
'XLM': 'XLM',
|
||
'XRP': 'XRP',
|
||
'XTZ': 'XTZ',
|
||
'XYM': 'XYM',
|
||
'ZEN': 'ZEN',
|
||
'ZIL': 'ZIL',
|
||
'ZKSYNC': 'ZKSYNC',
|
||
# todo: uncomment after consensus
|
||
# 'CADUCEUS': 'CMP',
|
||
# 'KON': 'KON', # konpay, "konchain"
|
||
# 'AURORA': 'AURORA',
|
||
# 'BITCOINGOLD': 'BTG',
|
||
},
|
||
'networksById': {
|
||
'ETH': 'ERC20',
|
||
'TRX': 'TRC20',
|
||
'BSC': 'BEP20',
|
||
'OMNI': 'OMNI',
|
||
'SPL': 'SOL',
|
||
},
|
||
'defaultNetwork': 'ERC20',
|
||
'defaultNetworks': {
|
||
'USDT': 'TRC20',
|
||
},
|
||
'intervals': {
|
||
'5m': '5min',
|
||
'15m': '15min',
|
||
'30m': '30min',
|
||
'1h': '1h',
|
||
'4h': '4h',
|
||
'1d': '1d',
|
||
},
|
||
'useMarkPriceForPositionCollateral': False, # use mark price for position collateral
|
||
},
|
||
'features': {
|
||
'default': {
|
||
'sandbox': True,
|
||
'createOrder': {
|
||
'marginMode': False,
|
||
'triggerPrice': True,
|
||
'triggerPriceType': {
|
||
'last': True,
|
||
'mark': True,
|
||
'index': True,
|
||
},
|
||
'triggerDirection': True,
|
||
'stopLossPrice': True,
|
||
'takeProfitPrice': True,
|
||
'attachedStopLossTakeProfit': {
|
||
'triggerPriceType': {
|
||
'last': True,
|
||
'mark': True,
|
||
'index': True,
|
||
},
|
||
'price': True,
|
||
},
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': False,
|
||
},
|
||
'hedged': True,
|
||
'selfTradePrevention': True, # todo: implement
|
||
'trailing': True,
|
||
'iceberg': False,
|
||
'leverage': False,
|
||
'marketBuyRequiresPrice': False,
|
||
'marketBuyByCost': True,
|
||
},
|
||
'createOrders': {
|
||
'max': 10,
|
||
},
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'limit': 100,
|
||
'daysBack': 365 * 2, # 2 years
|
||
'untilDays': 7, # days between start-end
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': False,
|
||
'limit': 50,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': None,
|
||
'fetchClosedOrders': {
|
||
'marginMode': False,
|
||
'limit': 50,
|
||
'daysBack': 365 * 2, # 2 years
|
||
'daysBackCanceled': 1,
|
||
'untilDays': 7,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 1000,
|
||
},
|
||
'editOrders': {
|
||
'max': 10,
|
||
},
|
||
},
|
||
'spot': {
|
||
'extends': 'default',
|
||
'fetchCurrencies': {
|
||
'private': True,
|
||
},
|
||
'createOrder': {
|
||
'triggerPriceType': None,
|
||
'triggerDirection': False,
|
||
'attachedStopLossTakeProfit': {
|
||
'triggerPriceType': None,
|
||
'price': True,
|
||
},
|
||
'marketBuyRequiresPrice': True,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': {
|
||
'extends': 'default',
|
||
},
|
||
'inverse': {
|
||
'extends': 'default',
|
||
},
|
||
},
|
||
'future': {
|
||
'linear': {
|
||
'extends': 'default',
|
||
},
|
||
'inverse': {
|
||
'extends': 'default',
|
||
},
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'feeSide': 'get',
|
||
'tierBased': True,
|
||
'percentage': True,
|
||
'taker': 0.00075,
|
||
'maker': 0.0001,
|
||
},
|
||
'funding': {
|
||
'tierBased': False,
|
||
'percentage': False,
|
||
'withdraw': {},
|
||
'deposit': {},
|
||
},
|
||
},
|
||
})
|
||
|
||
def enable_demo_trading(self, enable: bool):
|
||
"""
|
||
enables or disables demo trading mode
|
||
|
||
https://bybit-exchange.github.io/docs/v5/demo
|
||
|
||
:param boolean [enable]: True if demo trading should be enabled, False otherwise
|
||
"""
|
||
if self.isSandboxModeEnabled:
|
||
raise NotSupported(self.id + ' demo trading does not support in sandbox environment')
|
||
# enable demo trading in bybit, see: https://bybit-exchange.github.io/docs/v5/demo
|
||
if enable:
|
||
self.urls['apiBackupDemoTrading'] = self.urls['api']
|
||
self.urls['api'] = self.urls['demotrading']
|
||
elif 'apiBackupDemoTrading' in self.urls:
|
||
self.urls['api'] = self.urls['apiBackupDemoTrading']
|
||
newUrls = self.omit(self.urls, 'apiBackupDemoTrading')
|
||
self.urls = newUrls
|
||
self.options['enableDemoTrading'] = enable
|
||
|
||
def nonce(self):
|
||
return self.milliseconds() - self.options['timeDifference']
|
||
|
||
def add_pagination_cursor_to_result(self, response):
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list_n(result, ['list', 'rows', 'data', 'dataList'], [])
|
||
paginationCursor = self.safe_string_2(result, 'nextPageCursor', 'cursor')
|
||
dataLength = len(data)
|
||
if (paginationCursor is not None) and (dataLength > 0):
|
||
first = data[0]
|
||
first['nextPageCursor'] = paginationCursor
|
||
data[0] = first
|
||
return data
|
||
|
||
def is_unified_enabled(self, params={}):
|
||
"""
|
||
|
||
https://bybit-exchange.github.io/docs/v5/user/apikey-info#http-request
|
||
https://bybit-exchange.github.io/docs/v5/account/account-info
|
||
|
||
returns [enableUnifiedMargin, enableUnifiedAccount] so the user can check if unified account is enabled
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns any: [enableUnifiedMargin, enableUnifiedAccount]
|
||
"""
|
||
# The API key of user id must own one of permissions will be allowed to call following API endpoints:
|
||
# SUB UID: "Account Transfer"
|
||
# MASTER UID: "Account Transfer", "Subaccount Transfer", "Withdrawal"
|
||
enableUnifiedMargin = self.safe_bool(self.options, 'enableUnifiedMargin')
|
||
enableUnifiedAccount = self.safe_bool(self.options, 'enableUnifiedAccount')
|
||
if enableUnifiedMargin is None or enableUnifiedAccount is None:
|
||
if self.options['enableDemoTrading']:
|
||
# info endpoint is not available in demo trading
|
||
# so we're assuming UTA is enabled
|
||
self.options['enableUnifiedMargin'] = False
|
||
self.options['enableUnifiedAccount'] = True
|
||
self.options['unifiedMarginStatus'] = 6
|
||
return [self.options['enableUnifiedMargin'], self.options['enableUnifiedAccount']]
|
||
rawPromises = [self.privateGetV5UserQueryApi(params), self.privateGetV5AccountInfo(params)]
|
||
promises = rawPromises
|
||
response = promises[0]
|
||
accountInfo = promises[1]
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "",
|
||
# "result": {
|
||
# "id": "13770661",
|
||
# "note": "XXXXXX",
|
||
# "apiKey": "XXXXXX",
|
||
# "readOnly": 0,
|
||
# "secret": "",
|
||
# "permissions": {
|
||
# "ContractTrade": [...],
|
||
# "Spot": [...],
|
||
# "Wallet": [...],
|
||
# "Options": [...],
|
||
# "Derivatives": [...],
|
||
# "CopyTrading": [...],
|
||
# "BlockTrade": [...],
|
||
# "Exchange": [...],
|
||
# "NFT": [...],
|
||
# },
|
||
# "ips": [...],
|
||
# "type": 1,
|
||
# "deadlineDay": 83,
|
||
# "expiredAt": "2023-05-15T03:21:05Z",
|
||
# "createdAt": "2022-10-16T02:24:40Z",
|
||
# "unified": 0,
|
||
# "uta": 0,
|
||
# "userID": 24600000,
|
||
# "inviterID": 0,
|
||
# "vipLevel": "No VIP",
|
||
# "mktMakerLevel": "0",
|
||
# "affiliateID": 0,
|
||
# "rsaPublicKey": "",
|
||
# "isMaster": False
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1676891757649
|
||
# }
|
||
# account info
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "marginMode": "REGULAR_MARGIN",
|
||
# "updatedTime": "1697078946000",
|
||
# "unifiedMarginStatus": 4,
|
||
# "dcpStatus": "OFF",
|
||
# "timeWindow": 10,
|
||
# "smpGroup": 0,
|
||
# "isMasterTrader": False,
|
||
# "spotHedgingStatus": "OFF"
|
||
# }
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
accountResult = self.safe_dict(accountInfo, 'result', {})
|
||
self.options['enableUnifiedMargin'] = self.safe_integer(result, 'unified') == 1
|
||
self.options['enableUnifiedAccount'] = self.safe_integer(result, 'uta') == 1
|
||
self.options['unifiedMarginStatus'] = self.safe_integer(accountResult, 'unifiedMarginStatus', 6) # default to uta 2.0 pro if not found
|
||
return [self.options['enableUnifiedMargin'], self.options['enableUnifiedAccount']]
|
||
|
||
def upgrade_unified_trade_account(self, params={}):
|
||
"""
|
||
upgrades the account to unified trade account *warning* self is irreversible
|
||
|
||
https://bybit-exchange.github.io/docs/v5/account/upgrade-unified-account
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns any: nothing
|
||
"""
|
||
return self.privatePostV5AccountUpgradeToUta(params)
|
||
|
||
def create_expired_option_market(self, symbol: str):
|
||
# support expired option contracts
|
||
quote = None
|
||
settle = None
|
||
optionParts = symbol.split('-')
|
||
symbolBase = symbol.split('/')
|
||
base = None
|
||
expiry = None
|
||
if symbol.find('/') > -1:
|
||
base = self.safe_string(symbolBase, 0)
|
||
expiry = self.safe_string(optionParts, 1)
|
||
symbolQuoteAndSettle = self.safe_string(symbolBase, 1)
|
||
splitQuote = symbolQuoteAndSettle.split(':')
|
||
quoteAndSettle = self.safe_string(splitQuote, 0)
|
||
quote = quoteAndSettle
|
||
settle = quoteAndSettle
|
||
else:
|
||
base = self.safe_string(optionParts, 0)
|
||
expiry = self.convert_market_id_expire_date(self.safe_string(optionParts, 1))
|
||
if symbol.endswith('-USDT'):
|
||
quote = 'USDT'
|
||
settle = 'USDT'
|
||
else:
|
||
quote = 'USDC'
|
||
settle = 'USDC'
|
||
strike = self.safe_string(optionParts, 2)
|
||
optionType = self.safe_string(optionParts, 3)
|
||
datetime = self.convert_expire_date(expiry)
|
||
timestamp = self.parse8601(datetime)
|
||
amountPrecision = None
|
||
pricePrecision = None
|
||
# hard coded amount and price precisions from fetchOptionMarkets
|
||
if base == 'BTC':
|
||
amountPrecision = self.parse_number('0.01')
|
||
pricePrecision = self.parse_number('5')
|
||
elif base == 'ETH':
|
||
amountPrecision = self.parse_number('0.1')
|
||
pricePrecision = self.parse_number('0.1')
|
||
elif base == 'SOL':
|
||
amountPrecision = self.parse_number('1')
|
||
pricePrecision = self.parse_number('0.01')
|
||
return {
|
||
'id': base + '-' + self.convert_expire_date_to_market_id_date(expiry) + '-' + strike + '-' + optionType,
|
||
'symbol': base + '/' + quote + ':' + settle + '-' + expiry + '-' + strike + '-' + optionType,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': base,
|
||
'quoteId': quote,
|
||
'settleId': settle,
|
||
'active': False,
|
||
'type': 'option',
|
||
'linear': None,
|
||
'inverse': None,
|
||
'spot': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': True,
|
||
'margin': False,
|
||
'contract': True,
|
||
'contractSize': self.parse_number('1'),
|
||
'expiry': timestamp,
|
||
'expiryDatetime': datetime,
|
||
'optionType': 'call' if (optionType == 'C') else 'put',
|
||
'strike': self.parse_number(strike),
|
||
'precision': {
|
||
'amount': amountPrecision,
|
||
'price': pricePrecision,
|
||
},
|
||
'limits': {
|
||
'amount': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'info': None,
|
||
}
|
||
|
||
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
|
||
isOption = (marketId is not None) and ((marketId.find('-C') > -1) or (marketId.find('-P') > -1))
|
||
if isOption and not (marketId in self.markets_by_id):
|
||
# handle expired option contracts
|
||
return self.create_expired_option_market(marketId)
|
||
return super(bybit, self).safe_market(marketId, market, delimiter, marketType)
|
||
|
||
def get_bybit_type(self, method, market, params={}):
|
||
type = None
|
||
type, params = self.handle_market_type_and_params(method, market, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params(method, market, params)
|
||
if type == 'option' or type == 'spot':
|
||
return [type, params]
|
||
return [subType, params]
|
||
|
||
def get_amount(self, symbol: str, amount: float):
|
||
# some markets like options might not have the precision available
|
||
# and we shouldn't crash in those cases
|
||
market = self.market(symbol)
|
||
emptyPrecisionAmount = (market['precision']['amount'] is None)
|
||
amountString = self.number_to_string(amount)
|
||
if not emptyPrecisionAmount and (amountString != '0'):
|
||
return self.amount_to_precision(symbol, amount)
|
||
return amountString
|
||
|
||
def get_price(self, symbol: str, price: str):
|
||
if price is None:
|
||
return price
|
||
market = self.market(symbol)
|
||
emptyPrecisionPrice = (market['precision']['price'] is None)
|
||
if not emptyPrecisionPrice:
|
||
return self.price_to_precision(symbol, price)
|
||
return price
|
||
|
||
def get_cost(self, symbol: str, cost: str):
|
||
market = self.market(symbol)
|
||
emptyPrecisionPrice = (market['precision']['price'] is None)
|
||
if not emptyPrecisionPrice:
|
||
return self.cost_to_precision(symbol, cost)
|
||
return cost
|
||
|
||
def fetch_time(self, params={}) -> Int:
|
||
"""
|
||
fetches the current integer timestamp in milliseconds from the exchange server
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/time
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns int: the current integer timestamp in milliseconds from the exchange server
|
||
"""
|
||
response = self.publicGetV5MarketTime(params)
|
||
#
|
||
# {
|
||
# "retCode": "0",
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "timeSecond": "1666879482",
|
||
# "timeNano": "1666879482792685914"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": "1666879482792"
|
||
# }
|
||
#
|
||
return self.safe_integer(response, 'time')
|
||
|
||
def fetch_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies on an exchange
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/coin-info
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
if not self.check_required_credentials(False):
|
||
return {}
|
||
if self.options['enableDemoTrading']:
|
||
return {}
|
||
response = self.privateGetV5AssetCoinQueryInfo(params)
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "",
|
||
# "result": {
|
||
# "rows": [
|
||
# {
|
||
# "name": "BTC",
|
||
# "coin": "BTC",
|
||
# "remainAmount": "150",
|
||
# "chains": [
|
||
# {
|
||
# "chainType": "BTC",
|
||
# "confirmation": "10000",
|
||
# "withdrawFee": "0.0005",
|
||
# "depositMin": "0.0005",
|
||
# "withdrawMin": "0.001",
|
||
# "chain": "BTC",
|
||
# "chainDeposit": "1",
|
||
# "chainWithdraw": "1",
|
||
# "minAccuracy": "8"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672194582264
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
rows = self.safe_list(data, 'rows', [])
|
||
result: dict = {}
|
||
for i in range(0, len(rows)):
|
||
currency = rows[i]
|
||
currencyId = self.safe_string(currency, 'coin')
|
||
code = self.safe_currency_code(currencyId)
|
||
name = self.safe_string(currency, 'name')
|
||
chains = self.safe_list(currency, 'chains', [])
|
||
networks: dict = {}
|
||
for j in range(0, len(chains)):
|
||
chain = chains[j]
|
||
networkId = self.safe_string(chain, 'chain')
|
||
networkCode = self.network_id_to_code(networkId)
|
||
networks[networkCode] = {
|
||
'info': chain,
|
||
'id': networkId,
|
||
'network': networkCode,
|
||
'active': None,
|
||
'deposit': self.safe_integer(chain, 'chainDeposit') == 1,
|
||
'withdraw': self.safe_integer(chain, 'chainWithdraw') == 1,
|
||
'fee': self.safe_number(chain, 'withdrawFee'),
|
||
'precision': self.parse_number(self.parse_precision(self.safe_string(chain, 'minAccuracy'))),
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': self.safe_number(chain, 'withdrawMin'),
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': self.safe_number(chain, 'depositMin'),
|
||
'max': None,
|
||
},
|
||
},
|
||
}
|
||
result[code] = self.safe_currency_structure({
|
||
'info': currency,
|
||
'code': code,
|
||
'id': currencyId,
|
||
'name': name,
|
||
'active': None,
|
||
'deposit': None,
|
||
'withdraw': None,
|
||
'fee': None,
|
||
'precision': None,
|
||
'limits': {
|
||
'amount': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'withdraw': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'networks': networks,
|
||
'type': 'crypto', # atm exchange api provides only cryptos
|
||
})
|
||
return result
|
||
|
||
def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
retrieves data on all markets for bybit
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/instrument
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
if self.options['adjustForTimeDifference']:
|
||
self.load_time_difference()
|
||
promisesUnresolved = []
|
||
types = None
|
||
defaultTypes = ['spot', 'linear', 'inverse', 'option']
|
||
fetchMarketsOptions = self.safe_dict(self.options, 'fetchMarkets')
|
||
if fetchMarketsOptions is not None:
|
||
types = self.safe_list(fetchMarketsOptions, 'types', defaultTypes)
|
||
else:
|
||
# for backward-compatibility
|
||
types = self.safe_list(self.options, 'fetchMarkets', defaultTypes)
|
||
for i in range(0, len(types)):
|
||
marketType = types[i]
|
||
if marketType == 'spot':
|
||
promisesUnresolved.append(self.fetch_spot_markets(params))
|
||
elif marketType == 'linear':
|
||
promisesUnresolved.append(self.fetch_future_markets({'category': 'linear'}))
|
||
elif marketType == 'inverse':
|
||
promisesUnresolved.append(self.fetch_future_markets({'category': 'inverse'}))
|
||
elif marketType == 'option':
|
||
promisesUnresolved.append(self.fetch_option_markets({'baseCoin': 'BTC'}))
|
||
promisesUnresolved.append(self.fetch_option_markets({'baseCoin': 'ETH'}))
|
||
promisesUnresolved.append(self.fetch_option_markets({'baseCoin': 'SOL'}))
|
||
else:
|
||
raise ExchangeError(self.id + ' fetchMarkets() self.options fetchMarkets "' + marketType + '" is not a supported market type')
|
||
promises = promisesUnresolved
|
||
spotMarkets = self.safe_list(promises, 0, [])
|
||
linearMarkets = self.safe_list(promises, 1, [])
|
||
inverseMarkets = self.safe_list(promises, 2, [])
|
||
btcOptionMarkets = self.safe_list(promises, 3, [])
|
||
ethOptionMarkets = self.safe_list(promises, 4, [])
|
||
solOptionMarkets = self.safe_list(promises, 5, [])
|
||
futureMarkets = self.array_concat(linearMarkets, inverseMarkets)
|
||
optionMarkets = self.array_concat(btcOptionMarkets, ethOptionMarkets)
|
||
optionMarkets = self.array_concat(optionMarkets, solOptionMarkets)
|
||
derivativeMarkets = self.array_concat(futureMarkets, optionMarkets)
|
||
return self.array_concat(spotMarkets, derivativeMarkets)
|
||
|
||
def fetch_spot_markets(self, params) -> List[Market]:
|
||
request: dict = {
|
||
'category': 'spot',
|
||
}
|
||
usePrivateInstrumentsInfo = self.safe_bool(self.options, 'usePrivateInstrumentsInfo', False)
|
||
response: dict = None
|
||
if usePrivateInstrumentsInfo:
|
||
response = self.privateGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "spot",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "baseCoin": "BTC",
|
||
# "quoteCoin": "USDT",
|
||
# "innovation": "0",
|
||
# "status": "Trading",
|
||
# "marginTrading": "both",
|
||
# "lotSizeFilter": {
|
||
# "basePrecision": "0.000001",
|
||
# "quotePrecision": "0.00000001",
|
||
# "minOrderQty": "0.00004",
|
||
# "maxOrderQty": "63.01197227",
|
||
# "minOrderAmt": "1",
|
||
# "maxOrderAmt": "100000"
|
||
# },
|
||
# "priceFilter": {
|
||
# "tickSize": "0.01"
|
||
# }
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672712468011
|
||
# }
|
||
#
|
||
responseResult = self.safe_dict(response, 'result', {})
|
||
markets = self.safe_list(responseResult, 'list', [])
|
||
result = []
|
||
takerFee = self.parse_number('0.001')
|
||
makerFee = self.parse_number('0.001')
|
||
for i in range(0, len(markets)):
|
||
market = markets[i]
|
||
id = self.safe_string(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseCoin')
|
||
quoteId = self.safe_string(market, 'quoteCoin')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
symbol = base + '/' + quote
|
||
status = self.safe_string(market, 'status')
|
||
active = (status == 'Trading')
|
||
lotSizeFilter = self.safe_dict(market, 'lotSizeFilter')
|
||
priceFilter = self.safe_dict(market, 'priceFilter')
|
||
quotePrecision = self.safe_number(lotSizeFilter, 'quotePrecision')
|
||
marginTrading = self.safe_string(market, 'marginTrading', 'none')
|
||
allowsMargin = marginTrading != 'none'
|
||
result.append(self.safe_market_structure({
|
||
'id': id,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': None,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': None,
|
||
'type': 'spot',
|
||
'spot': True,
|
||
'margin': allowsMargin,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'active': active,
|
||
'contract': False,
|
||
'linear': None,
|
||
'inverse': None,
|
||
'taker': takerFee,
|
||
'maker': makerFee,
|
||
'contractSize': None,
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.safe_number(lotSizeFilter, 'basePrecision'),
|
||
'price': self.safe_number(priceFilter, 'tickSize', quotePrecision),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': self.parse_number('1'),
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(lotSizeFilter, 'minOrderQty'),
|
||
'max': self.safe_number(lotSizeFilter, 'maxOrderQty'),
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': self.safe_number(lotSizeFilter, 'minOrderAmt'),
|
||
'max': self.safe_number(lotSizeFilter, 'maxOrderAmt'),
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
}))
|
||
return result
|
||
|
||
def fetch_future_markets(self, params) -> List[Market]:
|
||
params = self.extend(params)
|
||
params['limit'] = 1000 # minimize number of requests
|
||
preLaunchMarkets = []
|
||
usePrivateInstrumentsInfo = self.safe_bool(self.options, 'usePrivateInstrumentsInfo', False)
|
||
response: dict = None
|
||
if usePrivateInstrumentsInfo:
|
||
response = self.privateGetV5MarketInstrumentsInfo(params)
|
||
else:
|
||
linearPromises = [
|
||
self.publicGetV5MarketInstrumentsInfo(params),
|
||
self.publicGetV5MarketInstrumentsInfo(self.extend(params, {'status': 'PreLaunch'})),
|
||
]
|
||
promises = linearPromises
|
||
response = self.safe_dict(promises, 0, {})
|
||
preLaunchMarkets = self.safe_dict(promises, 1, {})
|
||
data = self.safe_dict(response, 'result', {})
|
||
markets = self.safe_list(data, 'list', [])
|
||
paginationCursor = self.safe_string(data, 'nextPageCursor')
|
||
if paginationCursor is not None:
|
||
while(paginationCursor is not None):
|
||
params['cursor'] = paginationCursor
|
||
responseInner: dict = None
|
||
if usePrivateInstrumentsInfo:
|
||
responseInner = self.privateGetV5MarketInstrumentsInfo(params)
|
||
else:
|
||
responseInner = self.publicGetV5MarketInstrumentsInfo(params)
|
||
dataNew = self.safe_dict(responseInner, 'result', {})
|
||
rawMarkets = self.safe_list(dataNew, 'list', [])
|
||
rawMarketsLength = len(rawMarkets)
|
||
if rawMarketsLength == 0:
|
||
break
|
||
markets = self.array_concat(rawMarkets, markets)
|
||
paginationCursor = self.safe_string(dataNew, 'nextPageCursor')
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "contractType": "LinearPerpetual",
|
||
# "status": "Trading",
|
||
# "baseCoin": "BTC",
|
||
# "quoteCoin": "USDT",
|
||
# "launchTime": "1585526400000",
|
||
# "deliveryTime": "0",
|
||
# "deliveryFeeRate": "",
|
||
# "priceScale": "2",
|
||
# "leverageFilter": {
|
||
# "minLeverage": "1",
|
||
# "maxLeverage": "100.00",
|
||
# "leverageStep": "0.01"
|
||
# },
|
||
# "priceFilter": {
|
||
# "minPrice": "0.50",
|
||
# "maxPrice": "999999.00",
|
||
# "tickSize": "0.50"
|
||
# },
|
||
# "lotSizeFilter": {
|
||
# "maxOrderQty": "100.000",
|
||
# "minOrderQty": "0.001",
|
||
# "qtyStep": "0.001",
|
||
# "postOnlyMaxOrderQty": "1000.000"
|
||
# },
|
||
# "unifiedMarginTrade": True,
|
||
# "fundingInterval": 480,
|
||
# "settleCoin": "USDT"
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": ""
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672712495660
|
||
# }
|
||
#
|
||
preLaunchData = self.safe_dict(preLaunchMarkets, 'result', {})
|
||
preLaunchMarketsList = self.safe_list(preLaunchData, 'list', [])
|
||
markets = self.array_concat(markets, preLaunchMarketsList)
|
||
result = []
|
||
category = self.safe_string(data, 'category')
|
||
for i in range(0, len(markets)):
|
||
market = markets[i]
|
||
if category is None:
|
||
category = self.safe_string(market, 'category')
|
||
linear = (category == 'linear')
|
||
inverse = (category == 'inverse')
|
||
contractType = self.safe_string(market, 'contractType')
|
||
inverseFutures = (contractType == 'InverseFutures')
|
||
linearFutures = (contractType == 'LinearFutures')
|
||
linearPerpetual = (contractType == 'LinearPerpetual')
|
||
inversePerpetual = (contractType == 'InversePerpetual')
|
||
id = self.safe_string(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseCoin')
|
||
quoteId = self.safe_string(market, 'quoteCoin')
|
||
defaultSettledId = quoteId if linear else baseId
|
||
settleId = self.safe_string(market, 'settleCoin', defaultSettledId)
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
settle = None
|
||
if linearPerpetual and (settleId == 'USD'):
|
||
settle = 'USDC'
|
||
else:
|
||
settle = self.safe_currency_code(settleId)
|
||
symbol = base + '/' + quote
|
||
lotSizeFilter = self.safe_dict(market, 'lotSizeFilter', {})
|
||
priceFilter = self.safe_dict(market, 'priceFilter', {})
|
||
leverage = self.safe_dict(market, 'leverageFilter', {})
|
||
status = self.safe_string(market, 'status')
|
||
swap = linearPerpetual or inversePerpetual
|
||
future = inverseFutures or linearFutures
|
||
type = None
|
||
if swap:
|
||
type = 'swap'
|
||
elif future:
|
||
type = 'future'
|
||
expiry = None
|
||
# some swaps have deliveryTime meaning delisting time
|
||
if not swap:
|
||
expiry = self.omit_zero(self.safe_string(market, 'deliveryTime'))
|
||
if expiry is not None:
|
||
expiry = int(expiry)
|
||
expiryDatetime = self.iso8601(expiry)
|
||
symbol = symbol + ':' + settle
|
||
if expiry is not None:
|
||
symbol = symbol + '-' + self.yymmdd(expiry)
|
||
contractSize = self.safe_number_2(lotSizeFilter, 'minTradingQty', 'minOrderQty') if inverse else self.parse_number('1')
|
||
result.append(self.safe_market_structure({
|
||
'id': id,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': settleId,
|
||
'type': type,
|
||
'spot': False,
|
||
'margin': None,
|
||
'swap': swap,
|
||
'future': future,
|
||
'option': False,
|
||
'active': (status == 'Trading'),
|
||
'contract': True,
|
||
'linear': linear,
|
||
'inverse': inverse,
|
||
'taker': self.safe_number(market, 'takerFee', self.parse_number('0.0006')),
|
||
'maker': self.safe_number(market, 'makerFee', self.parse_number('0.0001')),
|
||
'contractSize': contractSize,
|
||
'expiry': expiry,
|
||
'expiryDatetime': expiryDatetime,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.safe_number(lotSizeFilter, 'qtyStep'),
|
||
'price': self.safe_number(priceFilter, 'tickSize'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': self.safe_number(leverage, 'minLeverage'),
|
||
'max': self.safe_number(leverage, 'maxLeverage'),
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number_2(lotSizeFilter, 'minTradingQty', 'minOrderQty'),
|
||
'max': self.safe_number_2(lotSizeFilter, 'maxTradingQty', 'maxOrderQty'),
|
||
},
|
||
'price': {
|
||
'min': self.safe_number(priceFilter, 'minPrice'),
|
||
'max': self.safe_number(priceFilter, 'maxPrice'),
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': self.safe_integer(market, 'launchTime'),
|
||
'info': market,
|
||
}))
|
||
return result
|
||
|
||
def fetch_option_markets(self, params) -> List[Market]:
|
||
request: dict = {
|
||
'category': 'option',
|
||
}
|
||
usePrivateInstrumentsInfo = self.safe_bool(self.options, 'usePrivateInstrumentsInfo', False)
|
||
response: dict = None
|
||
if usePrivateInstrumentsInfo:
|
||
response = self.privateGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
data = self.safe_dict(response, 'result', {})
|
||
markets = self.safe_list(data, 'list', [])
|
||
if self.options['loadAllOptions']:
|
||
request['limit'] = 1000
|
||
paginationCursor = self.safe_string(data, 'nextPageCursor')
|
||
if paginationCursor is not None:
|
||
while(paginationCursor is not None):
|
||
request['cursor'] = paginationCursor
|
||
responseInner: dict = None
|
||
if usePrivateInstrumentsInfo:
|
||
responseInner = self.privateGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
else:
|
||
responseInner = self.publicGetV5MarketInstrumentsInfo(self.extend(request, params))
|
||
dataNew = self.safe_dict(responseInner, 'result', {})
|
||
rawMarkets = self.safe_list(dataNew, 'list', [])
|
||
rawMarketsLength = len(rawMarkets)
|
||
if rawMarketsLength == 0:
|
||
break
|
||
markets = self.array_concat(rawMarkets, markets)
|
||
paginationCursor = self.safe_string(dataNew, 'nextPageCursor')
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "nextPageCursor": "0%2C2",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-29DEC23-80000-C",
|
||
# "status": "Trading",
|
||
# "baseCoin": "BTC",
|
||
# "quoteCoin": "USD",
|
||
# "settleCoin": "USDC",
|
||
# "optionsType": "Call",
|
||
# "launchTime": "1688630400000",
|
||
# "deliveryTime": "1703836800000",
|
||
# "deliveryFeeRate": "0.00015",
|
||
# "priceFilter": {
|
||
# "minPrice": "5",
|
||
# "maxPrice": "10000000",
|
||
# "tickSize": "5"
|
||
# },
|
||
# "lotSizeFilter": {
|
||
# "maxOrderQty": "500",
|
||
# "minOrderQty": "0.01",
|
||
# "qtyStep": "0.01"
|
||
# }
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1688873094448
|
||
# }
|
||
#
|
||
result = []
|
||
for i in range(0, len(markets)):
|
||
market = markets[i]
|
||
id = self.safe_string(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseCoin')
|
||
quoteId = self.safe_string(market, 'quoteCoin')
|
||
settleId = self.safe_string(market, 'settleCoin')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
settle = self.safe_currency_code(settleId)
|
||
lotSizeFilter = self.safe_dict(market, 'lotSizeFilter', {})
|
||
priceFilter = self.safe_dict(market, 'priceFilter', {})
|
||
status = self.safe_string(market, 'status')
|
||
expiry = self.safe_integer(market, 'deliveryTime')
|
||
splitId = id.split('-')
|
||
strike = self.safe_string(splitId, 2)
|
||
optionLetter = self.safe_string(splitId, 3)
|
||
isActive = (status == 'Trading')
|
||
isInverse = base == settle
|
||
if isActive or (self.options['loadAllOptions']) or (self.options['loadExpiredOptions']):
|
||
result.append(self.safe_market_structure({
|
||
'id': id,
|
||
'symbol': base + '/' + quote + ':' + settle + '-' + self.yymmdd(expiry) + '-' + strike + '-' + optionLetter,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': settleId,
|
||
'type': 'option',
|
||
'subType': None,
|
||
'spot': False,
|
||
'margin': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': True,
|
||
'active': isActive,
|
||
'contract': True,
|
||
'linear': not isInverse,
|
||
'inverse': isInverse,
|
||
'taker': self.safe_number(market, 'takerFee', self.parse_number('0.0006')),
|
||
'maker': self.safe_number(market, 'makerFee', self.parse_number('0.0001')),
|
||
'contractSize': self.parse_number('1'),
|
||
'expiry': expiry,
|
||
'expiryDatetime': self.iso8601(expiry),
|
||
'strike': self.parse_number(strike),
|
||
'optionType': self.safe_string_lower(market, 'optionsType'),
|
||
'precision': {
|
||
'amount': self.safe_number(lotSizeFilter, 'qtyStep'),
|
||
'price': self.safe_number(priceFilter, 'tickSize'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(lotSizeFilter, 'minOrderQty'),
|
||
'max': self.safe_number(lotSizeFilter, 'maxOrderQty'),
|
||
},
|
||
'price': {
|
||
'min': self.safe_number(priceFilter, 'minPrice'),
|
||
'max': self.safe_number(priceFilter, 'maxPrice'),
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': self.safe_integer(market, 'launchTime'),
|
||
'info': market,
|
||
}))
|
||
return result
|
||
|
||
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "bid1Price": "20517.96",
|
||
# "bid1Size": "2",
|
||
# "ask1Price": "20527.77",
|
||
# "ask1Size": "1.862172",
|
||
# "lastPrice": "20533.13",
|
||
# "prevPrice24h": "20393.48",
|
||
# "price24hPcnt": "0.0068",
|
||
# "highPrice24h": "21128.12",
|
||
# "lowPrice24h": "20318.89",
|
||
# "turnover24h": "243765620.65899866",
|
||
# "volume24h": "11801.27771",
|
||
# "usdIndexPrice": "20784.12009279"
|
||
# }
|
||
#
|
||
# linear/inverse
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSD",
|
||
# "lastPrice": "16597.00",
|
||
# "indexPrice": "16598.54",
|
||
# "markPrice": "16596.00",
|
||
# "prevPrice24h": "16464.50",
|
||
# "price24hPcnt": "0.008047",
|
||
# "highPrice24h": "30912.50",
|
||
# "lowPrice24h": "15700.00",
|
||
# "prevPrice1h": "16595.50",
|
||
# "openInterest": "373504107",
|
||
# "openInterestValue": "22505.67",
|
||
# "turnover24h": "2352.94950046",
|
||
# "volume24h": "49337318",
|
||
# "fundingRate": "-0.001034",
|
||
# "nextFundingTime": "1672387200000",
|
||
# "predictedDeliveryPrice": "",
|
||
# "basisRate": "",
|
||
# "deliveryFeeRate": "",
|
||
# "deliveryTime": "0",
|
||
# "ask1Size": "1",
|
||
# "bid1Price": "16596.00",
|
||
# "ask1Price": "16597.50",
|
||
# "bid1Size": "1"
|
||
# }
|
||
#
|
||
# option
|
||
#
|
||
# {
|
||
# "symbol": "BTC-30DEC22-18000-C",
|
||
# "bid1Price": "0",
|
||
# "bid1Size": "0",
|
||
# "bid1Iv": "0",
|
||
# "ask1Price": "435",
|
||
# "ask1Size": "0.66",
|
||
# "ask1Iv": "5",
|
||
# "lastPrice": "435",
|
||
# "highPrice24h": "435",
|
||
# "lowPrice24h": "165",
|
||
# "markPrice": "0.00000009",
|
||
# "indexPrice": "16600.55",
|
||
# "markIv": "0.7567",
|
||
# "underlyingPrice": "16590.42",
|
||
# "openInterest": "6.3",
|
||
# "turnover24h": "2482.73",
|
||
# "volume24h": "0.15",
|
||
# "totalVolume": "99",
|
||
# "totalTurnover": "1967653",
|
||
# "delta": "0.00000001",
|
||
# "gamma": "0.00000001",
|
||
# "vega": "0.00000004",
|
||
# "theta": "-0.00000152",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "86"
|
||
# }
|
||
#
|
||
isSpot = self.safe_string(ticker, 'openInterestValue') is None
|
||
timestamp = self.safe_integer(ticker, 'time')
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
type = 'spot' if isSpot else 'contract'
|
||
market = self.safe_market(marketId, market, None, type)
|
||
symbol = self.safe_symbol(marketId, market, None, type)
|
||
last = self.safe_string(ticker, 'lastPrice')
|
||
open = self.safe_string(ticker, 'prevPrice24h')
|
||
percentage = self.safe_string(ticker, 'price24hPcnt')
|
||
percentage = Precise.string_mul(percentage, '100')
|
||
quoteVolume = self.safe_string(ticker, 'turnover24h')
|
||
baseVolume = self.safe_string(ticker, 'volume24h')
|
||
bid = self.safe_string(ticker, 'bid1Price')
|
||
ask = self.safe_string(ticker, 'ask1Price')
|
||
high = self.safe_string(ticker, 'highPrice24h')
|
||
low = self.safe_string(ticker, 'lowPrice24h')
|
||
return self.safe_ticker({
|
||
'symbol': symbol,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': high,
|
||
'low': low,
|
||
'bid': bid,
|
||
'bidVolume': self.safe_string_2(ticker, 'bidSize', 'bid1Size'),
|
||
'ask': ask,
|
||
'askVolume': self.safe_string_2(ticker, 'askSize', 'ask1Size'),
|
||
'vwap': None,
|
||
'open': open,
|
||
'close': last,
|
||
'last': last,
|
||
'previousClose': None,
|
||
'change': None,
|
||
'percentage': percentage,
|
||
'average': None,
|
||
'baseVolume': baseVolume,
|
||
'quoteVolume': quoteVolume,
|
||
'markPrice': self.safe_string(ticker, 'markPrice'),
|
||
'indexPrice': self.safe_string(ticker, 'indexPrice'),
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
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://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchTicker() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'baseCoin': '', Base coin. For option only
|
||
# 'expDate': '', Expiry date. e.g., 25DEC22. For option only
|
||
}
|
||
category = None
|
||
category, params = self.get_bybit_type('fetchTicker', market, params)
|
||
request['category'] = category
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSD",
|
||
# "lastPrice": "16597.00",
|
||
# "indexPrice": "16598.54",
|
||
# "markPrice": "16596.00",
|
||
# "prevPrice24h": "16464.50",
|
||
# "price24hPcnt": "0.008047",
|
||
# "highPrice24h": "30912.50",
|
||
# "lowPrice24h": "15700.00",
|
||
# "prevPrice1h": "16595.50",
|
||
# "openInterest": "373504107",
|
||
# "openInterestValue": "22505.67",
|
||
# "turnover24h": "2352.94950046",
|
||
# "volume24h": "49337318",
|
||
# "fundingRate": "-0.001034",
|
||
# "nextFundingTime": "1672387200000",
|
||
# "predictedDeliveryPrice": "",
|
||
# "basisRate": "",
|
||
# "deliveryFeeRate": "",
|
||
# "deliveryTime": "0",
|
||
# "ask1Size": "1",
|
||
# "bid1Price": "16596.00",
|
||
# "ask1Price": "16597.50",
|
||
# "bid1Size": "1"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672376496682
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
tickers = self.safe_list(result, 'list', [])
|
||
rawTicker = self.safe_dict(tickers, 0)
|
||
return self.parse_ticker(rawTicker, market)
|
||
|
||
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str[] symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: *contract only* 'linear', 'inverse'
|
||
:param str [params.baseCoin]: *option only* base coin, default is 'BTC'
|
||
:returns dict: an array of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
code = self.safe_string_n(params, ['code', 'currency', 'baseCoin'])
|
||
market = None
|
||
parsedSymbols = None
|
||
if symbols is not None:
|
||
parsedSymbols = []
|
||
marketTypeInfo = self.handle_market_type_and_params('fetchTickers', None, params)
|
||
defaultType = marketTypeInfo[0] # don't omit here
|
||
# we can't use marketSymbols here due to the conflicing ids between markets
|
||
currentType = None
|
||
for i in range(0, len(symbols)):
|
||
symbol = symbols[i]
|
||
# using safeMarket here because if the user provides for instance BTCUSDT and "type": "spot" in params we should
|
||
# infer the market type from the type provided and not from the conflicting id(BTCUSDT might be swap or spot)
|
||
isExchangeSpecificSymbol = (symbol.find('/') == -1)
|
||
if isExchangeSpecificSymbol:
|
||
market = self.safe_market(symbol, None, None, defaultType)
|
||
else:
|
||
market = self.market(symbol)
|
||
if currentType is None:
|
||
currentType = market['type']
|
||
elif market['type'] != currentType:
|
||
raise BadRequest(self.id + ' fetchTickers can only accept a list of symbols of the same type')
|
||
if market['option']:
|
||
if code is not None and code != market['base']:
|
||
raise BadRequest(self.id + ' fetchTickers the base currency must be the same for all symbols, self endpoint only supports one base currency at a time. Read more about it here: https://bybit-exchange.github.io/docs/v5/market/tickers')
|
||
if code is None:
|
||
code = market['base']
|
||
params = self.omit(params, ['code', 'currency'])
|
||
parsedSymbols.append(market['symbol'])
|
||
request: dict = {
|
||
# 'symbol': market['id'],
|
||
# 'baseCoin': '', # Base coin. For option only
|
||
# 'expDate': '', # Expiry date. e.g., 25DEC22. For option only
|
||
}
|
||
category = None
|
||
category, params = self.get_bybit_type('fetchTickers', market, params)
|
||
request['category'] = category
|
||
if category == 'option':
|
||
request['category'] = 'option'
|
||
if code is None:
|
||
code = 'BTC'
|
||
request['baseCoin'] = code
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSD",
|
||
# "lastPrice": "16597.00",
|
||
# "indexPrice": "16598.54",
|
||
# "markPrice": "16596.00",
|
||
# "prevPrice24h": "16464.50",
|
||
# "price24hPcnt": "0.008047",
|
||
# "highPrice24h": "30912.50",
|
||
# "lowPrice24h": "15700.00",
|
||
# "prevPrice1h": "16595.50",
|
||
# "openInterest": "373504107",
|
||
# "openInterestValue": "22505.67",
|
||
# "turnover24h": "2352.94950046",
|
||
# "volume24h": "49337318",
|
||
# "fundingRate": "-0.001034",
|
||
# "nextFundingTime": "1672387200000",
|
||
# "predictedDeliveryPrice": "",
|
||
# "basisRate": "",
|
||
# "deliveryFeeRate": "",
|
||
# "deliveryTime": "0",
|
||
# "ask1Size": "1",
|
||
# "bid1Price": "16596.00",
|
||
# "ask1Price": "16597.50",
|
||
# "bid1Size": "1"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672376496682
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
tickerList = self.safe_list(result, 'list', [])
|
||
return self.parse_tickers(tickerList, parsedSymbols)
|
||
|
||
def fetch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches the bid and ask price and volume for multiple markets
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str[]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: *contract only* 'linear', 'inverse'
|
||
:param str [params.baseCoin]: *option only* base coin, default is 'BTC'
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
return self.fetch_tickers(symbols, params)
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
#
|
||
# [
|
||
# "1621162800",
|
||
# "49592.43",
|
||
# "49644.91",
|
||
# "49342.37",
|
||
# "49349.42",
|
||
# "1451.59",
|
||
# "2.4343353100000003"
|
||
# ]
|
||
#
|
||
volumeIndex = 6 if (market['inverse']) else 5
|
||
return [
|
||
self.safe_integer(ohlcv, 0),
|
||
self.safe_number(ohlcv, 1),
|
||
self.safe_number(ohlcv, 2),
|
||
self.safe_number(ohlcv, 3),
|
||
self.safe_number(ohlcv, 4),
|
||
self.safe_number(ohlcv, volumeIndex),
|
||
]
|
||
|
||
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
||
"""
|
||
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/kline
|
||
https://bybit-exchange.github.io/docs/v5/market/mark-kline
|
||
https://bybit-exchange.github.io/docs/v5/market/index-kline
|
||
https://bybit-exchange.github.io/docs/v5/market/preimum-index-kline
|
||
|
||
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
||
:param str timeframe: the length of time each candle represents
|
||
:param int [since]: timestamp in ms of the earliest candle to fetch
|
||
:param int [limit]: the maximum amount of candles to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch orders for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a symbol argument')
|
||
self.load_markets()
|
||
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, 1000)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if limit is None:
|
||
limit = 200 # default is 200 when requested with `since`
|
||
if since is not None:
|
||
request['start'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit # max 1000, default 1000
|
||
request, params = self.handle_until_option('end', request, params)
|
||
request['interval'] = self.safe_string(self.timeframes, timeframe, timeframe)
|
||
response = None
|
||
if market['spot']:
|
||
request['category'] = 'spot'
|
||
response = self.publicGetV5MarketKline(self.extend(request, params))
|
||
else:
|
||
price = self.safe_string(params, 'price')
|
||
params = self.omit(params, 'price')
|
||
if market['linear']:
|
||
request['category'] = 'linear'
|
||
elif market['inverse']:
|
||
request['category'] = 'inverse'
|
||
else:
|
||
raise NotSupported(self.id + ' fetchOHLCV() is not supported for option markets')
|
||
if price == 'mark':
|
||
response = self.publicGetV5MarketMarkPriceKline(self.extend(request, params))
|
||
elif price == 'index':
|
||
response = self.publicGetV5MarketIndexPriceKline(self.extend(request, params))
|
||
elif price == 'premiumIndex':
|
||
response = self.publicGetV5MarketPremiumIndexPriceKline(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetV5MarketKline(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "symbol": "BTCUSD",
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# [
|
||
# "1670608800000",
|
||
# "17071",
|
||
# "17073",
|
||
# "17027",
|
||
# "17055.5",
|
||
# "268611",
|
||
# "15.74462667"
|
||
# ],
|
||
# [
|
||
# "1670605200000",
|
||
# "17071.5",
|
||
# "17071.5",
|
||
# "17061",
|
||
# "17071",
|
||
# "4177",
|
||
# "0.24469757"
|
||
# ],
|
||
# [
|
||
# "1670601600000",
|
||
# "17086.5",
|
||
# "17088",
|
||
# "16978",
|
||
# "17071.5",
|
||
# "6356",
|
||
# "0.37288112"
|
||
# ]
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672025956592
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
ohlcvs = self.safe_list(result, 'list', [])
|
||
return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
|
||
|
||
def parse_funding_rate(self, ticker, market: Market = None) -> FundingRate:
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "bidPrice": "19255",
|
||
# "askPrice": "19255.5",
|
||
# "lastPrice": "19255.50",
|
||
# "lastTickDirection": "ZeroPlusTick",
|
||
# "prevPrice24h": "18634.50",
|
||
# "price24hPcnt": "0.033325",
|
||
# "highPrice24h": "19675.00",
|
||
# "lowPrice24h": "18610.00",
|
||
# "prevPrice1h": "19278.00",
|
||
# "markPrice": "19255.00",
|
||
# "indexPrice": "19260.68",
|
||
# "openInterest": "48069.549",
|
||
# "turnover24h": "4686694853.047006",
|
||
# "volume24h": "243730.252",
|
||
# "fundingRate": "0.0001",
|
||
# "nextFundingTime": "1663689600000",
|
||
# "predictedDeliveryPrice": "",
|
||
# "basisRate": "",
|
||
# "deliveryFeeRate": "",
|
||
# "deliveryTime": "0"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(ticker, 'timestamp') # added artificially to avoid changing the signature
|
||
ticker = self.omit(ticker, 'timestamp')
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market, None, 'swap')
|
||
fundingRate = self.safe_number(ticker, 'fundingRate')
|
||
fundingTimestamp = self.safe_integer(ticker, 'nextFundingTime')
|
||
markPrice = self.safe_number(ticker, 'markPrice')
|
||
indexPrice = self.safe_number(ticker, 'indexPrice')
|
||
info = self.safe_dict(self.safe_market(marketId, market, None, 'swap'), 'info')
|
||
fundingInterval = self.safe_integer(info, 'fundingInterval')
|
||
intervalString = None
|
||
if fundingInterval is not None:
|
||
interval = self.parse_to_int(fundingInterval / 60)
|
||
intervalString = str(interval) + 'h'
|
||
return {
|
||
'info': ticker,
|
||
'symbol': symbol,
|
||
'markPrice': markPrice,
|
||
'indexPrice': indexPrice,
|
||
'interestRate': None,
|
||
'estimatedSettlePrice': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'fundingRate': fundingRate,
|
||
'fundingTimestamp': fundingTimestamp,
|
||
'fundingDatetime': self.iso8601(fundingTimestamp),
|
||
'nextFundingRate': None,
|
||
'nextFundingTimestamp': None,
|
||
'nextFundingDatetime': None,
|
||
'previousFundingRate': None,
|
||
'previousFundingTimestamp': None,
|
||
'previousFundingDatetime': None,
|
||
'interval': intervalString,
|
||
}
|
||
|
||
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
||
"""
|
||
fetches funding rates for multiple markets
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str[] symbols: unified symbols of the markets to fetch the funding rates for, all market funding rates are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
request: dict = {}
|
||
if symbols is not None:
|
||
symbols = self.market_symbols(symbols)
|
||
market = self.market(symbols[0])
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength == 1:
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchFundingRates', market, params)
|
||
if type != 'swap':
|
||
raise NotSupported(self.id + ' fetchFundingRates() does not support ' + type + ' markets')
|
||
else:
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchFundingRates', market, params, 'linear')
|
||
request['category'] = subType
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "bidPrice": "19255",
|
||
# "askPrice": "19255.5",
|
||
# "lastPrice": "19255.50",
|
||
# "lastTickDirection": "ZeroPlusTick",
|
||
# "prevPrice24h": "18634.50",
|
||
# "price24hPcnt": "0.033325",
|
||
# "highPrice24h": "19675.00",
|
||
# "lowPrice24h": "18610.00",
|
||
# "prevPrice1h": "19278.00",
|
||
# "markPrice": "19255.00",
|
||
# "indexPrice": "19260.68",
|
||
# "openInterest": "48069.549",
|
||
# "turnover24h": "4686694853.047006",
|
||
# "volume24h": "243730.252",
|
||
# "fundingRate": "0.0001",
|
||
# "nextFundingTime": "1663689600000",
|
||
# "predictedDeliveryPrice": "",
|
||
# "basisRate": "",
|
||
# "deliveryFeeRate": "",
|
||
# "deliveryTime": "0"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": null,
|
||
# "time": 1663670053454
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
tickerList = self.safe_list(data, 'list', [])
|
||
timestamp = self.safe_integer(response, 'time')
|
||
for i in range(0, len(tickerList)):
|
||
tickerList[i]['timestamp'] = timestamp # will be removed inside the parser
|
||
return self.parse_funding_rates(tickerList, symbols)
|
||
|
||
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical funding rate prices
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/history-fund-rate
|
||
|
||
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
||
:param int [since]: timestamp in ms of the earliest funding rate to fetch
|
||
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest funding rate
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params, 200)
|
||
if limit is None:
|
||
limit = 200
|
||
request: dict = {
|
||
# 'category': '', # Product type. linear,inverse
|
||
# 'symbol': '', # Symbol name
|
||
# 'startTime': 0, # The start timestamp(ms)
|
||
# 'endTime': 0, # The end timestamp(ms)
|
||
'limit': limit, # Limit for data size per page. [1, 200]. Default: 200
|
||
}
|
||
market = self.market(symbol)
|
||
symbol = market['symbol']
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchFundingRateHistory', market, params)
|
||
if type == 'spot' or type == 'option':
|
||
raise NotSupported(self.id + ' fetchFundingRateHistory() only support linear and inverse market')
|
||
request['category'] = type
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
else:
|
||
if since is not None:
|
||
# end time is required when since is not empty
|
||
fundingInterval = 60 * 60 * 8 * 1000
|
||
request['endTime'] = since + limit * fundingInterval
|
||
response = self.publicGetV5MarketFundingHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHPERP",
|
||
# "fundingRate": "0.0001",
|
||
# "fundingRateTimestamp": "1672041600000"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672051897447
|
||
# }
|
||
#
|
||
rates = []
|
||
result = self.safe_dict(response, 'result')
|
||
resultList = self.safe_list(result, 'list')
|
||
for i in range(0, len(resultList)):
|
||
entry = resultList[i]
|
||
timestamp = self.safe_integer(entry, 'fundingRateTimestamp')
|
||
rates.append({
|
||
'info': entry,
|
||
'symbol': self.safe_symbol(self.safe_string(entry, 'symbol'), None, None, 'swap'),
|
||
'fundingRate': self.safe_number(entry, 'fundingRate'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
})
|
||
sorted = self.sort_by(rates, 'timestamp')
|
||
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# public https://bybit-exchange.github.io/docs/v5/market/recent-trade
|
||
#
|
||
# {
|
||
# "execId": "666042b4-50c6-58f3-bd9c-89b2088663ff",
|
||
# "symbol": "ETHUSD",
|
||
# "price": "1162.95",
|
||
# "size": "1",
|
||
# "side": "Sell",
|
||
# "time": "1669191277315",
|
||
# "isBlockTrade": False
|
||
# }
|
||
#
|
||
# private trades classic spot https://bybit-exchange.github.io/docs/v5/position/execution
|
||
#
|
||
# {
|
||
# "symbol": "QNTUSDT",
|
||
# "orderId": "1538686353240339712",
|
||
# "orderLinkId": "",
|
||
# "side": "Sell",
|
||
# "orderPrice": "",
|
||
# "orderQty": "",
|
||
# "leavesQty": "",
|
||
# "orderType": "Limit",
|
||
# "stopOrderType": "",
|
||
# "execFee": "0.040919",
|
||
# "execId": "2210000000097330907",
|
||
# "execPrice": "98.6",
|
||
# "execQty": "0.415",
|
||
# "execType": "",
|
||
# "execValue": "",
|
||
# "execTime": "1698161716634",
|
||
# "isMaker": True,
|
||
# "feeRate": "",
|
||
# "tradeIv": "",
|
||
# "markIv": "",
|
||
# "markPrice": "",
|
||
# "indexPrice": "",
|
||
# "underlyingPrice": "",
|
||
# "blockTradeId": ""
|
||
# }
|
||
#
|
||
# private trades unified https://bybit-exchange.github.io/docs/v5/position/execution
|
||
#
|
||
# {
|
||
# "symbol": "QNTUSDT",
|
||
# "orderType": "Limit",
|
||
# "underlyingPrice": "",
|
||
# "orderLinkId": "1549452573428424449",
|
||
# "orderId": "1549452573428424448",
|
||
# "stopOrderType": "",
|
||
# "execTime": "1699445151998",
|
||
# "feeRate": "0.00025",
|
||
# "tradeIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "",
|
||
# "execPrice": "102.8",
|
||
# "markIv": "",
|
||
# "orderQty": "3.652",
|
||
# "orderPrice": "102.8",
|
||
# "execValue": "1.028",
|
||
# "closedSize": "",
|
||
# "execType": "Trade",
|
||
# "seq": "19157444346",
|
||
# "side": "Buy",
|
||
# "indexPrice": "",
|
||
# "leavesQty": "3.642",
|
||
# "isMaker": True,
|
||
# "execFee": "0.0000025",
|
||
# "execId": "2210000000101610464",
|
||
# "execQty": "0.01",
|
||
# "nextPageCursor": "267951%3A0%2C38567%3A0"
|
||
# },
|
||
#
|
||
# private USDC settled trades
|
||
#
|
||
# {
|
||
# "symbol": "ETHPERP",
|
||
# "orderLinkId": "",
|
||
# "side": "Buy",
|
||
# "orderId": "aad0ee44-ce12-4112-aeee-b7829f6c3a26",
|
||
# "execFee": "0.0210",
|
||
# "feeRate": "0.000600",
|
||
# "blockTradeId": "",
|
||
# "tradeTime": "1669196417930",
|
||
# "execPrice": "1162.15",
|
||
# "lastLiquidityInd": "TAKER",
|
||
# "execValue": "34.8645",
|
||
# "execType": "Trade",
|
||
# "execQty": "0.030",
|
||
# "tradeId": "0e94eaf5-b08e-5505-b43f-7f1f30b1ca80"
|
||
# }
|
||
#
|
||
# watchMyTrades execution.fast
|
||
#
|
||
# {
|
||
# "category": "linear",
|
||
# "symbol": "ICPUSDT",
|
||
# "execId": "3510f361-0add-5c7b-a2e7-9679810944fc",
|
||
# "execPrice": "12.015",
|
||
# "execQty": "3000",
|
||
# "orderId": "443d63fa-b4c3-4297-b7b1-23bca88b04dc",
|
||
# "isMaker": False,
|
||
# "orderLinkId": "test-00001",
|
||
# "side": "Sell",
|
||
# "execTime": "1716800399334",
|
||
# "seq": 34771365464
|
||
# }
|
||
#
|
||
# watchMyTrades execution
|
||
#
|
||
# {
|
||
# "category": "linear",
|
||
# "symbol": "BTCUSDT",
|
||
# "closedSize": "0",
|
||
# "execFee": "0.0679239",
|
||
# "execId": "135dbae5-cbed-5275-9290-3956bb2ed907",
|
||
# "execPrice": "123498",
|
||
# "execQty": "0.001",
|
||
# "execType": "Trade",
|
||
# "execValue": "123.498",
|
||
# "feeRate": "0.00055",
|
||
# "tradeIv": "",
|
||
# "markIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "122392",
|
||
# "indexPrice": "",
|
||
# "underlyingPrice": "",
|
||
# "leavesQty": "0",
|
||
# "orderId": "aee7453a-a100-465f-857a-3db780e9329a",
|
||
# "orderLinkId": "",
|
||
# "orderPrice": "123615.9",
|
||
# "orderQty": "0.001",
|
||
# "orderType": "Market",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "side": "Buy",
|
||
# "execTime": "1757837580469",
|
||
# "isLeverage": "0",
|
||
# "isMaker": False,
|
||
# "seq": 9517074055,
|
||
# "marketUnit": "",
|
||
# "execPnl": "0",
|
||
# "createType": "CreateByUser",
|
||
# "extraFees": [],
|
||
# "feeCoin": "USDT"
|
||
# }
|
||
#
|
||
id = self.safe_string_n(trade, ['execId', 'id', 'tradeId'])
|
||
marketId = self.safe_string(trade, 'symbol')
|
||
marketType = 'contract' if ('createType' in trade) else 'spot'
|
||
category = self.safe_string(trade, 'category')
|
||
if category is not None:
|
||
marketType = 'spot' if (category == 'spot') else 'contract'
|
||
if market is not None:
|
||
marketType = market['type']
|
||
market = self.safe_market(marketId, market, None, marketType)
|
||
symbol = market['symbol']
|
||
amountString = self.safe_string_n(trade, ['execQty', 'orderQty', 'size'])
|
||
priceString = self.safe_string_n(trade, ['execPrice', 'orderPrice', 'price'])
|
||
costString = self.safe_string(trade, 'execValue')
|
||
timestamp = self.safe_integer_n(trade, ['time', 'execTime', 'tradeTime'])
|
||
side = self.safe_string_lower(trade, 'side')
|
||
if side is None:
|
||
isBuyer = self.safe_integer(trade, 'isBuyer')
|
||
if isBuyer is not None:
|
||
side = 'buy' if isBuyer else 'sell'
|
||
isMaker = self.safe_bool(trade, 'isMaker')
|
||
takerOrMaker = None
|
||
if isMaker is not None:
|
||
takerOrMaker = 'maker' if isMaker else 'taker'
|
||
else:
|
||
lastLiquidityInd = self.safe_string(trade, 'lastLiquidityInd')
|
||
if lastLiquidityInd == 'UNKNOWN':
|
||
lastLiquidityInd = None
|
||
if lastLiquidityInd is not None:
|
||
if (lastLiquidityInd == 'TAKER') or (lastLiquidityInd == 'MAKER'):
|
||
takerOrMaker = lastLiquidityInd.lower()
|
||
else:
|
||
takerOrMaker = 'maker' if (lastLiquidityInd == 'AddedLiquidity') else 'taker'
|
||
orderType = self.safe_string_lower(trade, 'orderType')
|
||
if orderType == 'unknown':
|
||
orderType = None
|
||
feeCostString = self.safe_string(trade, 'execFee')
|
||
fee = None
|
||
if feeCostString is not None:
|
||
feeRateString = self.safe_string(trade, 'feeRate')
|
||
feeCurrencyCode = None
|
||
if market['spot']:
|
||
if Precise.string_gt(feeCostString, '0'):
|
||
if side == 'buy':
|
||
feeCurrencyCode = market['base']
|
||
else:
|
||
feeCurrencyCode = market['quote']
|
||
else:
|
||
if side == 'buy':
|
||
feeCurrencyCode = market['quote']
|
||
else:
|
||
feeCurrencyCode = market['base']
|
||
else:
|
||
feeCurrencyCode = market['base'] if market['inverse'] else market['settle']
|
||
fee = {
|
||
'cost': feeCostString,
|
||
'currency': self.safe_string(trade, 'feeCoin', feeCurrencyCode),
|
||
'rate': feeRateString,
|
||
}
|
||
return self.safe_trade({
|
||
'id': id,
|
||
'info': trade,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': symbol,
|
||
'order': self.safe_string(trade, 'orderId'),
|
||
'type': orderType,
|
||
'side': side,
|
||
'takerOrMaker': takerOrMaker,
|
||
'price': priceString,
|
||
'amount': amountString,
|
||
'cost': costString,
|
||
'fee': fee,
|
||
}, market)
|
||
|
||
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://bybit-exchange.github.io/docs/v5/market/recent-trade
|
||
|
||
:param str symbol: unified symbol of the market to fetch trades for
|
||
:param int [since]: timestamp in ms of the earliest trade to fetch
|
||
:param int [limit]: the maximum amount of trades to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchTrades() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'baseCoin': '', # Base coin. For option only. If not passed, return BTC data by default
|
||
# 'optionType': 'Call', # Option type. Call or Put. For option only
|
||
}
|
||
if limit is not None:
|
||
# spot: [1,60], default: 60.
|
||
# others: [1,1000], default: 500
|
||
request['limit'] = limit
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchTrades', market, params)
|
||
request['category'] = type
|
||
response = self.publicGetV5MarketRecentTrade(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "spot",
|
||
# "list": [
|
||
# {
|
||
# "execId": "2100000000007764263",
|
||
# "symbol": "BTCUSDT",
|
||
# "price": "16618.49",
|
||
# "size": "0.00012",
|
||
# "side": "Buy",
|
||
# "time": "1672052955758",
|
||
# "isBlockTrade": False
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672053054358
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
trades = self.safe_list(result, 'list', [])
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
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://bybit-exchange.github.io/docs/v5/market/orderbook
|
||
|
||
:param str symbol: unified symbol of the market to fetch the order book for
|
||
:param int [limit]: the maximum amount of order book entries to return
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrderBook() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
defaultLimit = 25
|
||
if market['spot']:
|
||
# limit: [1, 50]. Default: 1
|
||
defaultLimit = 50
|
||
request['category'] = 'spot'
|
||
else:
|
||
if market['option']:
|
||
# limit: [1, 25]. Default: 1
|
||
request['category'] = 'option'
|
||
elif market['linear']:
|
||
# limit: [1, 500]. Default: 25
|
||
request['category'] = 'linear'
|
||
elif market['inverse']:
|
||
# limit: [1, 500]. Default: 25
|
||
request['category'] = 'inverse'
|
||
request['limit'] = limit if (limit is not None) else defaultLimit
|
||
response = self.publicGetV5MarketOrderbook(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "s": "BTCUSDT",
|
||
# "a": [
|
||
# [
|
||
# "16638.64",
|
||
# "0.008479"
|
||
# ]
|
||
# ],
|
||
# "b": [
|
||
# [
|
||
# "16638.27",
|
||
# "0.305749"
|
||
# ]
|
||
# ],
|
||
# "ts": 1672765737733,
|
||
# "u": 5277055
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672765737734
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
timestamp = self.safe_integer(result, 'ts')
|
||
return self.parse_order_book(result, symbol, timestamp, 'b', 'a')
|
||
|
||
def parse_balance(self, response) -> Balances:
|
||
#
|
||
# cross
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "acctBalanceSum": "0.122995614474732872",
|
||
# "debtBalanceSum": "0.011734191124529754",
|
||
# "loanAccountList": [
|
||
# {
|
||
# "free": "0.001143855",
|
||
# "interest": "0",
|
||
# "loan": "0",
|
||
# "locked": "0",
|
||
# "tokenId": "BTC",
|
||
# "total": "0.001143855"
|
||
# },
|
||
# {
|
||
# "free": "200.00005568",
|
||
# "interest": "0.0008391",
|
||
# "loan": "200",
|
||
# "locked": "0",
|
||
# "tokenId": "USDT",
|
||
# "total": "200.00005568"
|
||
# },
|
||
# ],
|
||
# "riskRate": "0.0954",
|
||
# "status": 1
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1669843584123
|
||
# }
|
||
#
|
||
# funding
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "memberId": "533285",
|
||
# "accountType": "FUND",
|
||
# "balance": [
|
||
# {
|
||
# "coin": "USDT",
|
||
# "transferBalance": "1010",
|
||
# "walletBalance": "1010",
|
||
# "bonus": ""
|
||
# },
|
||
# {
|
||
# "coin": "USDC",
|
||
# "transferBalance": "0",
|
||
# "walletBalance": "0",
|
||
# "bonus": ""
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1675865290069
|
||
# }
|
||
#
|
||
# spot & swap
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "totalEquity": "18070.32797922",
|
||
# "accountIMRate": "0.0101",
|
||
# "totalMarginBalance": "18070.32797922",
|
||
# "totalInitialMargin": "182.60183684",
|
||
# "accountType": "UNIFIED",
|
||
# "totalAvailableBalance": "17887.72614237",
|
||
# "accountMMRate": "0",
|
||
# "totalPerpUPL": "-0.11001349",
|
||
# "totalWalletBalance": "18070.43799271",
|
||
# "accountLTV": "0.017",
|
||
# "totalMaintenanceMargin": "0.38106773",
|
||
# "coin": [
|
||
# {
|
||
# "availableToBorrow": "2.5",
|
||
# "bonus": "0",
|
||
# "accruedInterest": "0",
|
||
# "availableToWithdraw": "0.805994",
|
||
# "totalOrderIM": "0",
|
||
# "equity": "0.805994",
|
||
# "totalPositionMM": "0",
|
||
# "usdValue": "12920.95352538",
|
||
# "unrealisedPnl": "0",
|
||
# "borrowAmount": "0",
|
||
# "totalPositionIM": "0",
|
||
# "walletBalance": "0.805994",
|
||
# "cumRealisedPnl": "0",
|
||
# "coin": "BTC"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672125441042
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(response, 'time')
|
||
result: dict = {
|
||
'info': response,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
responseResult = self.safe_dict(response, 'result', {})
|
||
currencyList = self.safe_list_n(responseResult, ['loanAccountList', 'list', 'balance'])
|
||
if currencyList is None:
|
||
# usdc wallet
|
||
code = 'USDC'
|
||
account = self.account()
|
||
account['free'] = self.safe_string(responseResult, 'availableBalance')
|
||
account['total'] = self.safe_string(responseResult, 'walletBalance')
|
||
result[code] = account
|
||
else:
|
||
for i in range(0, len(currencyList)):
|
||
entry = currencyList[i]
|
||
accountType = self.safe_string(entry, 'accountType')
|
||
if accountType == 'UNIFIED' or accountType == 'CONTRACT' or accountType == 'SPOT':
|
||
coins = self.safe_list(entry, 'coin')
|
||
for j in range(0, len(coins)):
|
||
account = self.account()
|
||
coinEntry = coins[j]
|
||
loan = self.safe_string(coinEntry, 'borrowAmount')
|
||
interest = self.safe_string(coinEntry, 'accruedInterest')
|
||
if (loan is not None) and (interest is not None):
|
||
account['debt'] = Precise.string_add(loan, interest)
|
||
account['total'] = self.safe_string(coinEntry, 'walletBalance')
|
||
free = self.safe_string_2(coinEntry, 'availableToWithdraw', 'free')
|
||
if free is not None:
|
||
account['free'] = free
|
||
else:
|
||
locked = self.safe_string(coinEntry, 'locked', '0')
|
||
totalPositionIm = self.safe_string(coinEntry, 'totalPositionIM', '0')
|
||
totalOrderIm = self.safe_string(coinEntry, 'totalOrderIM', '0')
|
||
totalUsed = Precise.string_add(locked, totalPositionIm)
|
||
totalUsed = Precise.string_add(totalUsed, totalOrderIm)
|
||
account['used'] = totalUsed
|
||
# account['used'] = self.safe_string(coinEntry, 'locked')
|
||
currencyId = self.safe_string(coinEntry, 'coin')
|
||
code = self.safe_currency_code(currencyId)
|
||
result[code] = account
|
||
else:
|
||
account = self.account()
|
||
loan = self.safe_string(entry, 'loan')
|
||
interest = self.safe_string(entry, 'interest')
|
||
if (loan is not None) and (interest is not None):
|
||
account['debt'] = Precise.string_add(loan, interest)
|
||
account['total'] = self.safe_string_2(entry, 'total', 'walletBalance')
|
||
account['free'] = self.safe_string_n(entry, ['free', 'availableBalanceWithoutConvert', 'availableBalance', 'transferBalance'])
|
||
account['used'] = self.safe_string(entry, 'locked')
|
||
currencyId = self.safe_string_n(entry, ['tokenId', 'coin', 'currencyCoin'])
|
||
code = self.safe_currency_code(currencyId)
|
||
result[code] = account
|
||
return self.safe_balance(result)
|
||
|
||
def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/spot-margin-normal/account-info
|
||
https://bybit-exchange.github.io/docs/v5/asset/all-balance
|
||
https://bybit-exchange.github.io/docs/v5/account/wallet-balance
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: wallet type, ['spot', 'swap', 'funding']
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
type = None
|
||
# don't use getBybitType here
|
||
type, params = self.handle_market_type_and_params('fetchBalance', None, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
|
||
if (type == 'swap') or (type == 'future'):
|
||
type = subType
|
||
lowercaseRawType = type.lower() if (type is not None) else None
|
||
isSpot = (type == 'spot')
|
||
isLinear = (type == 'linear')
|
||
isInverse = (type == 'inverse')
|
||
isFunding = (lowercaseRawType == 'fund') or (lowercaseRawType == 'funding')
|
||
if isUnifiedAccount:
|
||
unifiedMarginStatus = self.safe_integer(self.options, 'unifiedMarginStatus', 6)
|
||
if unifiedMarginStatus < 5:
|
||
# it's not uta.20 where inverse are unified
|
||
if isInverse:
|
||
type = 'contract'
|
||
else:
|
||
type = 'unified'
|
||
else:
|
||
type = 'unified' # uta.20 where inverse are unified
|
||
else:
|
||
if isLinear or isInverse:
|
||
type = 'contract'
|
||
accountTypes = self.safe_dict(self.options, 'accountsByType', {})
|
||
unifiedType = self.safe_string_upper(accountTypes, type, type)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params)
|
||
response = None
|
||
if isSpot and (marginMode is not None):
|
||
response = self.privateGetV5SpotCrossMarginTradeAccount(self.extend(request, params))
|
||
elif isFunding:
|
||
# use self endpoint only we have no other choice
|
||
# because it requires transfer permission
|
||
request['accountType'] = 'FUND'
|
||
response = self.privateGetV5AssetTransferQueryAccountCoinsBalance(self.extend(request, params))
|
||
else:
|
||
request['accountType'] = unifiedType
|
||
response = self.privateGetV5AccountWalletBalance(self.extend(request, params))
|
||
#
|
||
# cross
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "acctBalanceSum": "0.122995614474732872",
|
||
# "debtBalanceSum": "0.011734191124529754",
|
||
# "loanAccountList": [
|
||
# {
|
||
# "free": "0.001143855",
|
||
# "interest": "0",
|
||
# "loan": "0",
|
||
# "locked": "0",
|
||
# "tokenId": "BTC",
|
||
# "total": "0.001143855"
|
||
# },
|
||
# {
|
||
# "free": "200.00005568",
|
||
# "interest": "0.0008391",
|
||
# "loan": "200",
|
||
# "locked": "0",
|
||
# "tokenId": "USDT",
|
||
# "total": "200.00005568"
|
||
# },
|
||
# ],
|
||
# "riskRate": "0.0954",
|
||
# "status": 1
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1669843584123
|
||
# }
|
||
#
|
||
# funding
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "memberId": "533285",
|
||
# "accountType": "FUND",
|
||
# "balance": [
|
||
# {
|
||
# "coin": "USDT",
|
||
# "transferBalance": "1010",
|
||
# "walletBalance": "1010",
|
||
# "bonus": ""
|
||
# },
|
||
# {
|
||
# "coin": "USDC",
|
||
# "transferBalance": "0",
|
||
# "walletBalance": "0",
|
||
# "bonus": ""
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1675865290069
|
||
# }
|
||
#
|
||
# spot & swap
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "totalEquity": "18070.32797922",
|
||
# "accountIMRate": "0.0101",
|
||
# "totalMarginBalance": "18070.32797922",
|
||
# "totalInitialMargin": "182.60183684",
|
||
# "accountType": "UNIFIED",
|
||
# "totalAvailableBalance": "17887.72614237",
|
||
# "accountMMRate": "0",
|
||
# "totalPerpUPL": "-0.11001349",
|
||
# "totalWalletBalance": "18070.43799271",
|
||
# "accountLTV": "0.017",
|
||
# "totalMaintenanceMargin": "0.38106773",
|
||
# "coin": [
|
||
# {
|
||
# "availableToBorrow": "2.5",
|
||
# "bonus": "0",
|
||
# "accruedInterest": "0",
|
||
# "availableToWithdraw": "0.805994",
|
||
# "totalOrderIM": "0",
|
||
# "equity": "0.805994",
|
||
# "totalPositionMM": "0",
|
||
# "usdValue": "12920.95352538",
|
||
# "unrealisedPnl": "0",
|
||
# "borrowAmount": "0",
|
||
# "totalPositionIM": "0",
|
||
# "walletBalance": "0.805994",
|
||
# "cumRealisedPnl": "0",
|
||
# "coin": "BTC"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672125441042
|
||
# }
|
||
#
|
||
return self.parse_balance(response)
|
||
|
||
def parse_order_status(self, status: Str):
|
||
statuses: dict = {
|
||
# v3 spot
|
||
'NEW': 'open',
|
||
'PARTIALLY_FILLED': 'open',
|
||
'FILLED': 'closed',
|
||
'CANCELED': 'canceled',
|
||
'PENDING_CANCEL': 'open',
|
||
'PENDING_NEW': 'open',
|
||
'REJECTED': 'rejected',
|
||
'PARTIALLY_FILLED_CANCELLED': 'closed', # context: https://github.com/ccxt/ccxt/issues/18685
|
||
# v3 contract / unified margin / unified account
|
||
'Created': 'open',
|
||
'New': 'open',
|
||
'Rejected': 'rejected', # order is triggered but failed upon being placed
|
||
'PartiallyFilled': 'open',
|
||
'PartiallyFilledCanceled': 'closed', # context: https://github.com/ccxt/ccxt/issues/18685
|
||
'Filled': 'closed',
|
||
'PendingCancel': 'open',
|
||
'Cancelled': 'canceled',
|
||
# below self line the status only pertains to conditional orders
|
||
'Untriggered': 'open',
|
||
'Deactivated': 'canceled',
|
||
'Triggered': 'open',
|
||
'Active': 'open',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_time_in_force(self, timeInForce: Str):
|
||
timeInForces: dict = {
|
||
'GoodTillCancel': 'GTC',
|
||
'ImmediateOrCancel': 'IOC',
|
||
'FillOrKill': 'FOK',
|
||
'PostOnly': 'PO',
|
||
}
|
||
return self.safe_string(timeInForces, timeInForce, timeInForce)
|
||
|
||
def parse_order(self, order: dict, market: Market = None) -> Order:
|
||
#
|
||
# v1 for usdc normal account
|
||
# {
|
||
# "symbol": "BTCPERP",
|
||
# "orderType": "Market",
|
||
# "orderLinkId": "",
|
||
# "orderId": "36190ad3-de08-4b83-9ad3-56942f684b79",
|
||
# "cancelType": "UNKNOWN",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "orderStatus": "Filled",
|
||
# "updateTimeStamp": "1692769133267",
|
||
# "takeProfit": "0.0000",
|
||
# "cumExecValue": "259.6830",
|
||
# "createdAt": "1692769133261",
|
||
# "blockTradeId": "",
|
||
# "orderPnl": "",
|
||
# "price": "24674.7",
|
||
# "tpTriggerBy": "UNKNOWN",
|
||
# "timeInForce": "ImmediateOrCancel",
|
||
# "updatedAt": "1692769133267",
|
||
# "basePrice": "0.0",
|
||
# "realisedPnl": "0.0000",
|
||
# "side": "Sell",
|
||
# "triggerPrice": "0.0",
|
||
# "cumExecFee": "0.1429",
|
||
# "leavesQty": "0.000",
|
||
# "cashFlow": "",
|
||
# "slTriggerBy": "UNKNOWN",
|
||
# "iv": "",
|
||
# "closeOnTrigger": "UNKNOWN",
|
||
# "cumExecQty": "0.010",
|
||
# "reduceOnly": 0,
|
||
# "qty": "0.010",
|
||
# "stopLoss": "0.0000",
|
||
# "triggerBy": "UNKNOWN",
|
||
# "orderIM": ""
|
||
# }
|
||
#
|
||
# v5
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderType": "Market",
|
||
# "orderLinkId": "",
|
||
# "slLimitPrice": "0",
|
||
# "orderId": "f5f2d355-9a11-4af3-9b83-aa1d6ab6ddfe",
|
||
# "cancelType": "UNKNOWN",
|
||
# "avgPrice": "122529.9",
|
||
# "stopOrderType": "",
|
||
# "lastPriceOnCreated": "123747.9",
|
||
# "orderStatus": "Filled",
|
||
# "createType": "CreateByUser",
|
||
# "takeProfit": "",
|
||
# "cumExecValue": "122.5299",
|
||
# "tpslMode": "",
|
||
# "smpType": "None",
|
||
# "triggerDirection": 0,
|
||
# "blockTradeId": "",
|
||
# "cumFeeDetail": {
|
||
# "USDT": "0.06739145"
|
||
# },
|
||
# "rejectReason": "EC_NoError",
|
||
# "isLeverage": "",
|
||
# "price": "120518",
|
||
# "orderIv": "",
|
||
# "createdTime": "1757837618905",
|
||
# "tpTriggerBy": "",
|
||
# "positionIdx": 0,
|
||
# "timeInForce": "IOC",
|
||
# "leavesValue": "0",
|
||
# "updatedTime": "1757837618909",
|
||
# "side": "Sell",
|
||
# "smpGroup": 0,
|
||
# "triggerPrice": "",
|
||
# "tpLimitPrice": "0",
|
||
# "cumExecFee": "0.06739145",
|
||
# "slTriggerBy": "",
|
||
# "leavesQty": "0",
|
||
# "closeOnTrigger": False,
|
||
# "slippageToleranceType": "UNKNOWN",
|
||
# "placeType": "",
|
||
# "cumExecQty": "0.001",
|
||
# "reduceOnly": True,
|
||
# "qty": "0.001",
|
||
# "stopLoss": "",
|
||
# "smpOrderId": "",
|
||
# "slippageTolerance": "0",
|
||
# "triggerBy": "",
|
||
# "extraFees": ""
|
||
# }
|
||
#
|
||
# createOrders failed order
|
||
# {
|
||
# "category": "linear",
|
||
# "symbol": "LTCUSDT",
|
||
# "orderId": '',
|
||
# "orderLinkId": '',
|
||
# "createAt": '',
|
||
# "code": "10001",
|
||
# "msg": "The number of contracts exceeds maximum limit allowed: too large"
|
||
# }
|
||
#
|
||
code = self.safe_string(order, 'code')
|
||
if code is not None:
|
||
if code != '0':
|
||
category = self.safe_string(order, 'category')
|
||
inferredMarketType = 'spot' if (category == 'spot') else 'contract'
|
||
return self.safe_order({
|
||
'info': order,
|
||
'status': 'rejected',
|
||
'id': self.safe_string(order, 'orderId'),
|
||
'clientOrderId': self.safe_string(order, 'orderLinkId'),
|
||
'symbol': self.safe_symbol(self.safe_string(order, 'symbol'), None, None, inferredMarketType),
|
||
})
|
||
marketId = self.safe_string(order, 'symbol')
|
||
isContract = ('tpslMode' in order)
|
||
marketType = None
|
||
if market is not None:
|
||
marketType = market['type']
|
||
else:
|
||
marketType = 'contract' if isContract else 'spot'
|
||
market = self.safe_market(marketId, market, None, marketType)
|
||
symbol = market['symbol']
|
||
timestamp = self.safe_integer_2(order, 'createdTime', 'createdAt')
|
||
marketUnit = self.safe_string(order, 'marketUnit', 'baseCoin')
|
||
id = self.safe_string(order, 'orderId')
|
||
type = self.safe_string_lower(order, 'orderType')
|
||
price = self.safe_string(order, 'price')
|
||
amount: Str = None
|
||
cost: Str = None
|
||
if marketUnit == 'baseCoin':
|
||
amount = self.safe_string(order, 'qty')
|
||
cost = self.safe_string(order, 'cumExecValue')
|
||
else:
|
||
cost = self.safe_string(order, 'cumExecValue')
|
||
filled = self.safe_string(order, 'cumExecQty')
|
||
remaining = self.safe_string(order, 'leavesQty')
|
||
lastTradeTimestamp = self.safe_integer_2(order, 'updatedTime', 'updatedAt')
|
||
rawStatus = self.safe_string(order, 'orderStatus')
|
||
status = self.parse_order_status(rawStatus)
|
||
side = self.safe_string_lower(order, 'side')
|
||
fee = None
|
||
cumFeeDetail = self.safe_dict(order, 'cumFeeDetail', {})
|
||
feeCoins = list(cumFeeDetail.keys())
|
||
feeCoinId = self.safe_string(feeCoins, 0)
|
||
if feeCoinId is not None:
|
||
fee = {
|
||
'cost': self.safe_number(cumFeeDetail, feeCoinId),
|
||
'currency': feeCoinId,
|
||
}
|
||
clientOrderId = self.safe_string(order, 'orderLinkId')
|
||
if (clientOrderId is not None) and (len(clientOrderId) < 1):
|
||
clientOrderId = None
|
||
avgPrice = self.omit_zero(self.safe_string(order, 'avgPrice'))
|
||
rawTimeInForce = self.safe_string(order, 'timeInForce')
|
||
timeInForce = self.parse_time_in_force(rawTimeInForce)
|
||
triggerPrice = self.omit_zero(self.safe_string(order, 'triggerPrice'))
|
||
reduceOnly = self.safe_bool(order, 'reduceOnly')
|
||
takeProfitPrice = self.omit_zero(self.safe_string(order, 'takeProfit'))
|
||
stopLossPrice = self.omit_zero(self.safe_string(order, 'stopLoss'))
|
||
triggerDirection = self.safe_string(order, 'triggerDirection')
|
||
isAscending = (triggerDirection == '1')
|
||
isStopOrderType2 = (triggerPrice is not None) and reduceOnly
|
||
if (stopLossPrice is None) and isStopOrderType2:
|
||
# check if order is stop order type 2 - stopLossPrice
|
||
if isAscending and (side == 'buy'):
|
||
# stopLoss order against short position
|
||
stopLossPrice = triggerPrice
|
||
if not isAscending and (side == 'sell'):
|
||
# stopLoss order against a long position
|
||
stopLossPrice = triggerPrice
|
||
if (takeProfitPrice is None) and isStopOrderType2:
|
||
# check if order is stop order type 2 - takeProfitPrice
|
||
if isAscending and (side == 'sell'):
|
||
# takeprofit order against a long position
|
||
takeProfitPrice = triggerPrice
|
||
if not isAscending and (side == 'buy'):
|
||
# takeprofit order against a short position
|
||
takeProfitPrice = triggerPrice
|
||
return self.safe_order({
|
||
'info': order,
|
||
'id': id,
|
||
'clientOrderId': clientOrderId,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'lastTradeTimestamp': lastTradeTimestamp,
|
||
'lastUpdateTimestamp': lastTradeTimestamp,
|
||
'symbol': symbol,
|
||
'type': type,
|
||
'timeInForce': timeInForce,
|
||
'postOnly': None,
|
||
'reduceOnly': self.safe_bool(order, 'reduceOnly'),
|
||
'side': side,
|
||
'price': price,
|
||
'triggerPrice': triggerPrice,
|
||
'takeProfitPrice': takeProfitPrice,
|
||
'stopLossPrice': stopLossPrice,
|
||
'amount': amount,
|
||
'cost': cost,
|
||
'average': avgPrice,
|
||
'filled': filled,
|
||
'remaining': remaining,
|
||
'status': status,
|
||
'fee': fee,
|
||
'trades': None,
|
||
}, market)
|
||
|
||
def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}) -> Order:
|
||
"""
|
||
create a market buy order by providing the symbol and cost
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/create-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
|
||
req = {
|
||
'cost': cost,
|
||
}
|
||
return self.create_order(symbol, 'market', 'buy', -1, None, self.extend(req, params))
|
||
|
||
def create_market_sell_order_with_cost(self, symbol: str, cost: float, params={}) -> Order:
|
||
"""
|
||
create a market sell order by providing the symbol and cost
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/create-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
types = self.is_unified_enabled()
|
||
enableUnifiedAccount = types[1]
|
||
if not enableUnifiedAccount:
|
||
raise NotSupported(self.id + ' createMarketSellOrderWithCost() supports UTA accounts only')
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketSellOrderWithCost() supports spot orders only')
|
||
req = {
|
||
'cost': cost,
|
||
}
|
||
return self.create_order(symbol, 'market', 'sell', -1, None, self.extend(req, params))
|
||
|
||
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
||
"""
|
||
create a trade order
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/create-order
|
||
https://bybit-exchange.github.io/docs/v5/position/trading-stop
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.timeInForce]: "GTC", "IOC", "FOK"
|
||
:param bool [params.postOnly]: True or False whether the order is post-only
|
||
:param bool [params.reduceOnly]: True or False whether the order is reduce-only
|
||
:param str [params.positionIdx]: *contracts only* 0 for one-way mode, 1 buy side of hedged mode, 2 sell side of hedged mode
|
||
:param bool [params.hedged]: *contracts only* True for hedged mode, False for one way mode, default is False
|
||
:param int [params.isLeverage]: *unified spot only* False then spot trading True then margin trading
|
||
:param str [params.tpslMode]: *contract only* 'Full' or 'Partial'
|
||
:param str [params.mmp]: *option only* market maker protection
|
||
:param str [params.triggerDirection]: *contract only* the direction for trigger orders, 'ascending' or 'descending'
|
||
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
|
||
:param float [params.stopLossPrice]: The price at which a stop loss order is triggered at
|
||
:param float [params.takeProfitPrice]: The price at which a take profit order is triggered at
|
||
:param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered
|
||
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
||
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered
|
||
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
||
:param str [params.trailingAmount]: the quote amount to trail away from the current market price
|
||
:param str [params.trailingTriggerPrice]: the price to trigger a trailing order, default uses the price argument
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
parts = self.is_unified_enabled()
|
||
enableUnifiedAccount = parts[1]
|
||
trailingAmount = self.safe_string_2(params, 'trailingAmount', 'trailingStop')
|
||
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
||
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
||
isTrailingAmountOrder = trailingAmount is not None
|
||
isStopLoss = stopLossPrice is not None
|
||
isTakeProfit = takeProfitPrice is not None
|
||
orderRequest = self.create_order_request(symbol, type, side, amount, price, params, enableUnifiedAccount)
|
||
defaultMethod = None
|
||
if (isTrailingAmountOrder or isStopLoss or isTakeProfit) and not market['spot']:
|
||
defaultMethod = 'privatePostV5PositionTradingStop'
|
||
else:
|
||
defaultMethod = 'privatePostV5OrderCreate'
|
||
method = None
|
||
method, params = self.handle_option_and_params(params, 'createOrder', 'method', defaultMethod)
|
||
response = None
|
||
if method == 'privatePostV5PositionTradingStop':
|
||
response = self.privatePostV5PositionTradingStop(orderRequest)
|
||
else:
|
||
response = self.privatePostV5OrderCreate(orderRequest) # already extended inside createOrderRequest
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "orderId": "1321003749386327552",
|
||
# "orderLinkId": "spot-test-postonly"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672211918471
|
||
# }
|
||
#
|
||
order = self.safe_dict(response, 'result', {})
|
||
return self.parse_order(order, market)
|
||
|
||
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}, isUTA=True):
|
||
market = self.market(symbol)
|
||
symbol = market['symbol']
|
||
lowerCaseType = type.lower()
|
||
if (price is None) and (lowerCaseType == 'limit'):
|
||
raise ArgumentsRequired(self.id + ' createOrder requires a price argument for limit orders')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'side': self.capitalize(side),
|
||
# 'orderType': self.capitalize(lowerCaseType), # limit or market
|
||
# 'timeInForce': 'GTC', # IOC, FOK, PostOnly
|
||
# 'takeProfit': 123.45, # take profit price, only take effect upon opening the position
|
||
# 'stopLoss': 123.45, # stop loss price, only take effect upon opening the position
|
||
# 'reduceOnly': False, # reduce only, required for linear orders
|
||
# when creating a closing order, bybit recommends a True value for
|
||
# closeOnTrigger to avoid failing due to insufficient available margin
|
||
# 'closeOnTrigger': False, required for linear orders
|
||
# 'orderLinkId': 'string', # unique client order id, max 36 characters
|
||
# 'triggerPrice': 123.46, # trigger price, required for conditional orders
|
||
# 'triggerBy': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# 'tpTriggerby': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# 'slTriggerBy': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# 'mmp': False # market maker protection
|
||
# 'positionIdx': 0, # Position mode. Unified account has one-way mode only(0)
|
||
# 'triggerDirection': 1, # Conditional order param. Used to identify the expected direction of the conditional order. 1: triggered when market price rises to triggerPrice 2: triggered when market price falls to triggerPrice
|
||
# Valid for spot only.
|
||
# 'isLeverage': 0, # Whether to borrow. 0(default): False, 1: True
|
||
# 'orderFilter': 'Order' # Order,tpslOrder. If not passed, Order by default
|
||
# Valid for option only.
|
||
# 'orderIv': '0', # Implied volatility; parameters are passed according to the real value; for example, for 10%, 0.1 is passed
|
||
}
|
||
hedged = self.safe_bool(params, 'hedged', False)
|
||
reduceOnly = self.safe_bool(params, 'reduceOnly')
|
||
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
|
||
stopLossTriggerPrice = self.safe_value(params, 'stopLossPrice')
|
||
takeProfitTriggerPrice = self.safe_value(params, 'takeProfitPrice')
|
||
stopLoss = self.safe_value(params, 'stopLoss')
|
||
takeProfit = self.safe_value(params, 'takeProfit')
|
||
trailingTriggerPrice = self.safe_string_2(params, 'trailingTriggerPrice', 'activePrice', self.number_to_string(price))
|
||
trailingAmount = self.safe_string_2(params, 'trailingAmount', 'trailingStop')
|
||
isTrailingAmountOrder = trailingAmount is not None
|
||
isTriggerOrder = triggerPrice is not None
|
||
isStopLossTriggerOrder = stopLossTriggerPrice is not None
|
||
isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None
|
||
isStopLoss = stopLoss is not None
|
||
isTakeProfit = takeProfit is not None
|
||
isMarket = lowerCaseType == 'market'
|
||
isLimit = lowerCaseType == 'limit'
|
||
isBuy = side == 'buy'
|
||
defaultMethod = None
|
||
if (isTrailingAmountOrder or isStopLossTriggerOrder or isTakeProfitTriggerOrder) and not market['spot']:
|
||
defaultMethod = 'privatePostV5PositionTradingStop'
|
||
else:
|
||
defaultMethod = 'privatePostV5OrderCreate'
|
||
method = None
|
||
method, params = self.handle_option_and_params(params, 'createOrder', 'method', defaultMethod)
|
||
isAlternativeEndpoint = method == 'privatePostV5PositionTradingStop'
|
||
amountString = self.get_amount(symbol, amount)
|
||
priceString = self.get_price(symbol, self.number_to_string(price)) if (price is not None) else None
|
||
if isTrailingAmountOrder or isAlternativeEndpoint:
|
||
if isStopLoss or isTakeProfit or isTriggerOrder or market['spot']:
|
||
raise InvalidOrder(self.id + ' the API endpoint used only supports contract trailingAmount, stopLossPrice and takeProfitPrice orders')
|
||
if isStopLossTriggerOrder or isTakeProfitTriggerOrder:
|
||
tpslMode = self.safe_string(params, 'tpslMode', 'Partial')
|
||
isFullTpsl = tpslMode == 'Full'
|
||
isPartialTpsl = tpslMode == 'Partial'
|
||
if isLimit and isFullTpsl:
|
||
raise InvalidOrder(self.id + ' tpsl orders with "full" tpslMode only support "market" type')
|
||
request['tpslMode'] = tpslMode
|
||
if isStopLossTriggerOrder:
|
||
request['stopLoss'] = self.get_price(symbol, stopLossTriggerPrice)
|
||
if isPartialTpsl:
|
||
request['slSize'] = amountString
|
||
if isLimit:
|
||
request['slOrderType'] = 'Limit'
|
||
request['slLimitPrice'] = priceString
|
||
elif isTakeProfitTriggerOrder:
|
||
request['takeProfit'] = self.get_price(symbol, takeProfitTriggerPrice)
|
||
if isPartialTpsl:
|
||
request['tpSize'] = amountString
|
||
if isLimit:
|
||
request['tpOrderType'] = 'Limit'
|
||
request['tpLimitPrice'] = priceString
|
||
else:
|
||
request['side'] = self.capitalize(side)
|
||
request['orderType'] = self.capitalize(lowerCaseType)
|
||
timeInForce = self.safe_string_lower(params, 'timeInForce') # self is same specific param
|
||
postOnly = None
|
||
postOnly, params = self.handle_post_only(isMarket, timeInForce == 'postonly', params)
|
||
if postOnly:
|
||
request['timeInForce'] = 'PostOnly'
|
||
elif timeInForce == 'gtc':
|
||
request['timeInForce'] = 'GTC'
|
||
elif timeInForce == 'fok':
|
||
request['timeInForce'] = 'FOK'
|
||
elif timeInForce == 'ioc':
|
||
request['timeInForce'] = 'IOC'
|
||
if market['spot']:
|
||
# only works for spot market
|
||
if triggerPrice is not None:
|
||
request['orderFilter'] = 'StopOrder'
|
||
elif isStopLossTriggerOrder or isTakeProfitTriggerOrder:
|
||
request['orderFilter'] = 'tpslOrder'
|
||
clientOrderId = self.safe_string(params, 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['orderLinkId'] = clientOrderId
|
||
elif market['option']:
|
||
# mandatory field for options
|
||
request['orderLinkId'] = self.uuid16()
|
||
if isLimit:
|
||
request['price'] = priceString
|
||
category = None
|
||
category, params = self.get_bybit_type('createOrderRequest', market, params)
|
||
request['category'] = category
|
||
cost = self.safe_string(params, 'cost')
|
||
params = self.omit(params, 'cost')
|
||
# if the cost is inferable, let's keep the old logic and ignore marketUnit, to minimize the impact of the changes
|
||
isMarketBuyAndCostInferable = (lowerCaseType == 'market') and (side == 'buy') and ((price is not None) or (cost is not None))
|
||
isMarketOrder = lowerCaseType == 'market'
|
||
if market['spot'] and isMarketOrder and isUTA and not isMarketBuyAndCostInferable:
|
||
# UTA account can specify the cost of the order on both sides
|
||
if (cost is not None) or (price is not None):
|
||
request['marketUnit'] = 'quoteCoin'
|
||
orderCost = None
|
||
if cost is not None:
|
||
orderCost = cost
|
||
else:
|
||
quoteAmount = Precise.string_mul(amountString, priceString)
|
||
orderCost = quoteAmount
|
||
request['qty'] = self.get_cost(symbol, orderCost)
|
||
else:
|
||
request['marketUnit'] = 'baseCoin'
|
||
request['qty'] = amountString
|
||
elif market['spot'] and isMarketOrder and (side == 'buy'):
|
||
# classic accounts
|
||
# for market buy it requires the amount of quote currency to spend
|
||
createMarketBuyOrderRequiresPrice = True
|
||
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice')
|
||
if createMarketBuyOrderRequiresPrice:
|
||
if (price is None) and (cost is None):
|
||
raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument')
|
||
else:
|
||
quoteAmount = Precise.string_mul(self.number_to_string(amount), priceString)
|
||
costRequest = cost if (cost is not None) else quoteAmount
|
||
request['qty'] = self.get_cost(symbol, costRequest)
|
||
else:
|
||
if cost is not None:
|
||
request['qty'] = self.get_cost(symbol, self.number_to_string(cost))
|
||
elif price is not None:
|
||
request['qty'] = self.get_cost(symbol, Precise.string_mul(amountString, priceString))
|
||
else:
|
||
request['qty'] = amountString
|
||
else:
|
||
if not isTrailingAmountOrder and not isAlternativeEndpoint:
|
||
request['qty'] = amountString
|
||
if isTrailingAmountOrder:
|
||
if trailingTriggerPrice is not None:
|
||
request['activePrice'] = self.get_price(symbol, trailingTriggerPrice)
|
||
request['trailingStop'] = trailingAmount
|
||
elif isTriggerOrder and not isAlternativeEndpoint:
|
||
triggerDirection = self.safe_string(params, 'triggerDirection')
|
||
params = self.omit(params, ['triggerPrice', 'stopPrice', 'triggerDirection'])
|
||
if market['spot']:
|
||
if triggerDirection is not None:
|
||
raise NotSupported(self.id + ' createOrder() : trigger order does not support triggerDirection for spot markets yet')
|
||
else:
|
||
if triggerDirection is None:
|
||
raise ArgumentsRequired(self.id + ' stop/trigger orders require a triggerDirection parameter, either "ascending" or "descending" to determine the direction of the trigger.')
|
||
isAsending = ((triggerDirection == 'ascending') or (triggerDirection == 'above') or (triggerDirection == '1'))
|
||
request['triggerDirection'] = 1 if isAsending else 2
|
||
request['triggerPrice'] = self.get_price(symbol, triggerPrice)
|
||
elif (isStopLossTriggerOrder or isTakeProfitTriggerOrder) and not isAlternativeEndpoint:
|
||
if isBuy:
|
||
request['triggerDirection'] = 1 if isStopLossTriggerOrder else 2
|
||
else:
|
||
request['triggerDirection'] = 2 if isStopLossTriggerOrder else 1
|
||
triggerPrice = stopLossTriggerPrice if isStopLossTriggerOrder else takeProfitTriggerPrice
|
||
request['triggerPrice'] = self.get_price(symbol, triggerPrice)
|
||
request['reduceOnly'] = True
|
||
if (isStopLoss or isTakeProfit) and not isAlternativeEndpoint:
|
||
if isStopLoss:
|
||
slTriggerPrice = self.safe_value_2(stopLoss, 'triggerPrice', 'stopPrice', stopLoss)
|
||
request['stopLoss'] = self.get_price(symbol, slTriggerPrice)
|
||
slLimitPrice = self.safe_value(stopLoss, 'price')
|
||
if slLimitPrice is not None:
|
||
request['tpslMode'] = 'Partial'
|
||
request['slOrderType'] = 'Limit'
|
||
request['slLimitPrice'] = self.get_price(symbol, slLimitPrice)
|
||
else:
|
||
# for spot market, we need to add self
|
||
if market['spot']:
|
||
request['slOrderType'] = 'Market'
|
||
# for spot market, we need to add self
|
||
if market['spot'] and isMarketOrder:
|
||
raise InvalidOrder(self.id + ' createOrder(): attached stopLoss is not supported for spot market orders')
|
||
if isTakeProfit:
|
||
tpTriggerPrice = self.safe_value_2(takeProfit, 'triggerPrice', 'stopPrice', takeProfit)
|
||
request['takeProfit'] = self.get_price(symbol, tpTriggerPrice)
|
||
tpLimitPrice = self.safe_value(takeProfit, 'price')
|
||
if tpLimitPrice is not None:
|
||
request['tpslMode'] = 'Partial'
|
||
request['tpOrderType'] = 'Limit'
|
||
request['tpLimitPrice'] = self.get_price(symbol, tpLimitPrice)
|
||
else:
|
||
# for spot market, we need to add self
|
||
if market['spot']:
|
||
request['tpOrderType'] = 'Market'
|
||
# for spot market, we need to add self
|
||
if market['spot'] and isMarketOrder:
|
||
raise InvalidOrder(self.id + ' createOrder(): attached takeProfit is not supported for spot market orders')
|
||
if not market['spot'] and hedged:
|
||
if reduceOnly:
|
||
params = self.omit(params, 'reduceOnly')
|
||
side = 'sell' if (side == 'buy') else 'buy'
|
||
request['positionIdx'] = 1 if (side == 'buy') else 2
|
||
params = self.omit(params, ['stopPrice', 'timeInForce', 'stopLossPrice', 'takeProfitPrice', 'postOnly', 'clientOrderId', 'triggerPrice', 'stopLoss', 'takeProfit', 'trailingAmount', 'trailingTriggerPrice', 'hedged', 'tpslMode'])
|
||
return self.extend(request, params)
|
||
|
||
def create_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
|
||
"""
|
||
create a list of trade orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/batch-place
|
||
|
||
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
accounts = self.is_unified_enabled()
|
||
isUta = accounts[1]
|
||
ordersRequests = []
|
||
orderSymbols = []
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
marketId = self.safe_string(rawOrder, 'symbol')
|
||
orderSymbols.append(marketId)
|
||
type = self.safe_string(rawOrder, 'type')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_value(rawOrder, 'amount')
|
||
price = self.safe_value(rawOrder, 'price')
|
||
orderParams = self.safe_dict(rawOrder, 'params', {})
|
||
orderRequest = self.create_order_request(marketId, type, side, amount, price, orderParams, isUta)
|
||
del orderRequest['category']
|
||
ordersRequests.append(orderRequest)
|
||
symbols = self.market_symbols(orderSymbols, None, False, True, True)
|
||
market = self.market(symbols[0])
|
||
unifiedMarginStatus = self.safe_integer(self.options, 'unifiedMarginStatus', 6)
|
||
category = None
|
||
category, params = self.get_bybit_type('createOrders', market, params)
|
||
if (category == 'inverse') and (unifiedMarginStatus < 5):
|
||
raise NotSupported(self.id + ' createOrders does not allow inverse orders for non UTA2.0 account')
|
||
request: dict = {
|
||
'category': category,
|
||
'request': ordersRequests,
|
||
}
|
||
response = self.privatePostV5OrderCreateBatch(self.extend(request, params))
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
retInfo = self.safe_dict(response, 'retExtInfo', {})
|
||
codes = self.safe_list(retInfo, 'list', [])
|
||
# self.extend the error with the unsuccessful orders
|
||
for i in range(0, len(codes)):
|
||
code = codes[i]
|
||
retCode = self.safe_integer(code, 'code')
|
||
if retCode != 0:
|
||
data[i] = self.extend(data[i], code)
|
||
#
|
||
# {
|
||
# "retCode":0,
|
||
# "retMsg":"OK",
|
||
# "result":{
|
||
# "list":[
|
||
# {
|
||
# "category":"linear",
|
||
# "symbol":"LTCUSDT",
|
||
# "orderId":"",
|
||
# "orderLinkId":"",
|
||
# "createAt":""
|
||
# },
|
||
# {
|
||
# "category":"linear",
|
||
# "symbol":"LTCUSDT",
|
||
# "orderId":"3c9f65b6-01ad-4ac0-9741-df17e02a4223",
|
||
# "orderLinkId":"",
|
||
# "createAt":"1698075516029"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo":{
|
||
# "list":[
|
||
# {
|
||
# "code":10001,
|
||
# "msg":"The number of contracts exceeds maximum limit allowed: too large"
|
||
# },
|
||
# {
|
||
# "code":0,
|
||
# "msg":"OK"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "time":1698075516029
|
||
# }
|
||
#
|
||
return self.parse_orders(data)
|
||
|
||
def edit_order_request(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'orderId': id,
|
||
# 'orderLinkId': 'string', # unique client order id, max 36 characters
|
||
# 'takeProfit': 123.45, # take profit price, only take effect upon opening the position
|
||
# 'stopLoss': 123.45, # stop loss price, only take effect upon opening the position
|
||
# 'triggerPrice': 123.45, # trigger price, required for conditional orders
|
||
# 'triggerBy': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# 'tpTriggerby': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# 'slTriggerBy': 'MarkPrice', # IndexPrice, MarkPrice, LastPrice
|
||
# Valid for option only.
|
||
# 'orderIv': '0', # Implied volatility; parameters are passed according to the real value; for example, for 10%, 0.1 is passed
|
||
}
|
||
category = None
|
||
category, params = self.get_bybit_type('editOrderRequest', market, params)
|
||
request['category'] = category
|
||
if amount is not None:
|
||
request['qty'] = self.get_amount(symbol, amount)
|
||
if price is not None:
|
||
request['price'] = self.get_price(symbol, self.number_to_string(price))
|
||
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
||
stopLossTriggerPrice = self.safe_string(params, 'stopLossPrice')
|
||
takeProfitTriggerPrice = self.safe_string(params, 'takeProfitPrice')
|
||
stopLoss = self.safe_value(params, 'stopLoss')
|
||
takeProfit = self.safe_value(params, 'takeProfit')
|
||
isStopLossTriggerOrder = stopLossTriggerPrice is not None
|
||
isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None
|
||
isStopLoss = stopLoss is not None
|
||
isTakeProfit = takeProfit is not None
|
||
if isStopLossTriggerOrder or isTakeProfitTriggerOrder:
|
||
triggerPrice = stopLossTriggerPrice if isStopLossTriggerOrder else takeProfitTriggerPrice
|
||
if triggerPrice is not None:
|
||
triggerPriceRequest = triggerPrice if (triggerPrice == '0') else self.get_price(symbol, triggerPrice)
|
||
request['triggerPrice'] = triggerPriceRequest
|
||
triggerBy = self.safe_string(params, 'triggerBy', 'LastPrice')
|
||
request['triggerBy'] = triggerBy
|
||
if isStopLoss or isTakeProfit:
|
||
if isStopLoss:
|
||
slTriggerPrice = self.safe_string_2(stopLoss, 'triggerPrice', 'stopPrice', stopLoss)
|
||
stopLossRequest = slTriggerPrice if (slTriggerPrice == '0') else self.get_price(symbol, slTriggerPrice)
|
||
request['stopLoss'] = stopLossRequest
|
||
slTriggerBy = self.safe_string(params, 'slTriggerBy', 'LastPrice')
|
||
request['slTriggerBy'] = slTriggerBy
|
||
if isTakeProfit:
|
||
tpTriggerPrice = self.safe_string_2(takeProfit, 'triggerPrice', 'stopPrice', takeProfit)
|
||
takeProfitRequest = tpTriggerPrice if (tpTriggerPrice == '0') else self.get_price(symbol, tpTriggerPrice)
|
||
request['takeProfit'] = takeProfitRequest
|
||
tpTriggerBy = self.safe_string(params, 'tpTriggerBy', 'LastPrice')
|
||
request['tpTriggerBy'] = tpTriggerBy
|
||
clientOrderId = self.safe_string(params, 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['orderLinkId'] = clientOrderId
|
||
params = self.omit(params, ['stopPrice', 'stopLossPrice', 'takeProfitPrice', 'triggerPrice', 'clientOrderId', 'stopLoss', 'takeProfit'])
|
||
return request
|
||
|
||
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://bybit-exchange.github.io/docs/v5/order/amend-order
|
||
https://bybit-exchange.github.io/docs/derivatives/unified/replace-order
|
||
https://bybit-exchange.github.io/docs/api-explorer/derivatives/trade/contract/replace-order
|
||
|
||
:param str id: cancel order id
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float price: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param float [params.triggerPrice]: The price that a trigger order is triggered at
|
||
:param float [params.stopLossPrice]: The price that a stop loss order is triggered at
|
||
:param float [params.takeProfitPrice]: The price that a take profit order is triggered at
|
||
:param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice that the attached take profit order will be triggered
|
||
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
||
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice that the attached stop loss order will be triggered
|
||
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
||
:param str [params.triggerBy]: 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for triggerPrice
|
||
:param str [params.slTriggerBy]: 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for stopLoss
|
||
:param str [params.tpTriggerby]: 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for takeProfit
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' editOrder() requires a symbol argument')
|
||
request = self.edit_order_request(id, symbol, type, side, amount, price, params)
|
||
response = self.privatePostV5OrderAmend(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "orderId": "c6f055d9-7f21-4079-913d-e6523a9cfffa",
|
||
# "orderLinkId": "linear-004"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672217093461
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
return self.safe_order({
|
||
'info': response,
|
||
'id': self.safe_string(result, 'orderId'),
|
||
})
|
||
|
||
def edit_orders(self, orders: List[OrderRequest], params={}) -> List[Order]:
|
||
"""
|
||
edit a list of trade orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/batch-amend
|
||
|
||
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
ordersRequests = []
|
||
orderSymbols = []
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
symbol = self.safe_string(rawOrder, 'symbol')
|
||
orderSymbols.append(symbol)
|
||
id = self.safe_string(rawOrder, 'id')
|
||
type = self.safe_string(rawOrder, 'type')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_value(rawOrder, 'amount')
|
||
price = self.safe_value(rawOrder, 'price')
|
||
orderParams = self.safe_dict(rawOrder, 'params', {})
|
||
orderRequest = self.edit_order_request(id, symbol, type, side, amount, price, orderParams)
|
||
del orderRequest['category']
|
||
ordersRequests.append(orderRequest)
|
||
orderSymbols = self.market_symbols(orderSymbols, None, False, True, True)
|
||
market = self.market(orderSymbols[0])
|
||
unifiedMarginStatus = self.safe_integer(self.options, 'unifiedMarginStatus', 6)
|
||
category = None
|
||
category, params = self.get_bybit_type('editOrders', market, params)
|
||
if (category == 'inverse') and (unifiedMarginStatus < 5):
|
||
raise NotSupported(self.id + ' editOrders does not allow inverse orders for non UTA2.0 account')
|
||
request: dict = {
|
||
'category': category,
|
||
'request': ordersRequests,
|
||
}
|
||
response = self.privatePostV5OrderAmendBatch(self.extend(request, params))
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
retInfo = self.safe_dict(response, 'retExtInfo', {})
|
||
codes = self.safe_list(retInfo, 'list', [])
|
||
# self.extend the error with the unsuccessful orders
|
||
for i in range(0, len(codes)):
|
||
code = codes[i]
|
||
retCode = self.safe_integer(code, 'code')
|
||
if retCode != 0:
|
||
data[i] = self.extend(data[i], code)
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "category": "option",
|
||
# "symbol": "ETH-30DEC22-500-C",
|
||
# "orderId": "b551f227-7059-4fb5-a6a6-699c04dbd2f2",
|
||
# "orderLinkId": ""
|
||
# },
|
||
# {
|
||
# "category": "option",
|
||
# "symbol": "ETH-30DEC22-700-C",
|
||
# "orderId": "fa6a595f-1a57-483f-b9d3-30e9c8235a52",
|
||
# "orderLinkId": ""
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {
|
||
# "list": [
|
||
# {
|
||
# "code": 0,
|
||
# "msg": "OK"
|
||
# },
|
||
# {
|
||
# "code": 0,
|
||
# "msg": "OK"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "time": 1672222808060
|
||
# }
|
||
#
|
||
return self.parse_orders(data)
|
||
|
||
def cancel_order_request(self, id: str, symbol: Str = None, params={}):
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'orderLinkId': 'string',
|
||
# 'orderId': id,
|
||
# conditional orders
|
||
# 'orderFilter': '', # Valid for spot only. Order,tpslOrder. If not passed, Order by default
|
||
}
|
||
if market['spot']:
|
||
# only works for spot market
|
||
isTrigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
params = self.omit(params, ['stop', 'trigger'])
|
||
request['orderFilter'] = 'StopOrder' if isTrigger else 'Order'
|
||
if id is not None: # The user can also use argument params["orderLinkId"]
|
||
request['orderId'] = id
|
||
category = None
|
||
category, params = self.get_bybit_type('cancelOrderRequest', market, params)
|
||
request['category'] = category
|
||
return self.extend(request, params)
|
||
|
||
def cancel_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
cancels an open order
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/cancel-order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: *spot only* whether the order is a trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.orderFilter]: *spot only* 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
requestExtended = self.cancel_order_request(id, symbol, params)
|
||
response = self.privatePostV5OrderCancel(requestExtended)
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "orderId": "c6f055d9-7f21-4079-913d-e6523a9cfffa",
|
||
# "orderLinkId": "linear-004"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672217377164
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
return self.parse_order(result, market)
|
||
|
||
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}) -> List[Order]:
|
||
"""
|
||
cancel multiple orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/batch-cancel
|
||
|
||
:param str[] ids: order ids
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str[] [params.clientOrderIds]: client order ids
|
||
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
types = self.is_unified_enabled()
|
||
enableUnifiedAccount = types[1]
|
||
if not enableUnifiedAccount:
|
||
raise NotSupported(self.id + ' cancelOrders() supports UTA accounts only')
|
||
category = None
|
||
category, params = self.get_bybit_type('cancelOrders', market, params)
|
||
if category == 'inverse':
|
||
raise NotSupported(self.id + ' cancelOrders does not allow inverse orders')
|
||
ordersRequests = []
|
||
clientOrderIds = self.safe_list_2(params, 'clientOrderIds', 'clientOids', [])
|
||
params = self.omit(params, ['clientOrderIds', 'clientOids'])
|
||
for i in range(0, len(clientOrderIds)):
|
||
ordersRequests.append({
|
||
'symbol': market['id'],
|
||
'orderLinkId': self.safe_string(clientOrderIds, i),
|
||
})
|
||
for i in range(0, len(ids)):
|
||
ordersRequests.append({
|
||
'symbol': market['id'],
|
||
'orderId': self.safe_string(ids, i),
|
||
})
|
||
request: dict = {
|
||
'category': category,
|
||
'request': ordersRequests,
|
||
}
|
||
response = self.privatePostV5OrderCancelBatch(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": "0",
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "category": "spot",
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": "1636282505818800896",
|
||
# "orderLinkId": "1636282505818800897"
|
||
# },
|
||
# {
|
||
# "category": "spot",
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": "1636282505818800898",
|
||
# "orderLinkId": "1636282505818800899"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {
|
||
# "list": [
|
||
# {
|
||
# "code": "0",
|
||
# "msg": "OK"
|
||
# },
|
||
# {
|
||
# "code": "0",
|
||
# "msg": "OK"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "time": "1709796158501"
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
row = self.safe_list(result, 'list', [])
|
||
return self.parse_orders(row, market)
|
||
|
||
def cancel_all_orders_after(self, timeout: Int, params={}):
|
||
"""
|
||
dead man's switch, cancel all orders after the given timeout
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/dcp
|
||
|
||
:param number timeout: time in milliseconds
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.product]: OPTIONS, DERIVATIVES, SPOT, default is 'DERIVATIVES'
|
||
:returns dict: the api result
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'timeWindow': self.parse_to_int(timeout / 1000),
|
||
}
|
||
type: Str = None
|
||
type, params = self.handle_market_type_and_params('cancelAllOrdersAfter', None, params, 'swap')
|
||
productMap = {
|
||
'spot': 'SPOT',
|
||
'swap': 'DERIVATIVES',
|
||
'option': 'OPTIONS',
|
||
}
|
||
product = self.safe_string(productMap, type, type)
|
||
request['product'] = product
|
||
response = self.privatePostV5OrderDisconnectedCancelAll(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success"
|
||
# }
|
||
#
|
||
return response
|
||
|
||
def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
|
||
"""
|
||
cancel multiple orders for multiple symbols
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/batch-cancel
|
||
|
||
:param CancellationRequest[] orders: list of order ids with symbol, example [{"id": "a", "symbol": "BTC/USDT"}, {"id": "b", "symbol": "ETH/USDT"}]
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
types = self.is_unified_enabled()
|
||
enableUnifiedAccount = types[1]
|
||
if not enableUnifiedAccount:
|
||
raise NotSupported(self.id + ' cancelOrdersForSymbols() supports UTA accounts only')
|
||
ordersRequests = []
|
||
category = None
|
||
for i in range(0, len(orders)):
|
||
order = orders[i]
|
||
symbol = self.safe_string(order, 'symbol')
|
||
market = self.market(symbol)
|
||
currentCategory = None
|
||
currentCategory, params = self.get_bybit_type('cancelOrders', market, params)
|
||
if currentCategory == 'inverse':
|
||
raise NotSupported(self.id + ' cancelOrdersForSymbols does not allow inverse orders')
|
||
if (category is not None) and (category != currentCategory):
|
||
raise ExchangeError(self.id + ' cancelOrdersForSymbols requires all orders to be of the same category(linear, spot or option))')
|
||
category = currentCategory
|
||
id = self.safe_string(order, 'id')
|
||
clientOrderId = self.safe_string(order, 'clientOrderId')
|
||
idKey = 'orderId'
|
||
if clientOrderId is not None:
|
||
idKey = 'orderLinkId'
|
||
orderItem: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
orderItem[idKey] = id if (idKey == 'orderId') else clientOrderId
|
||
ordersRequests.append(orderItem)
|
||
request: dict = {
|
||
'category': category,
|
||
'request': ordersRequests,
|
||
}
|
||
response = self.privatePostV5OrderCancelBatch(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": "0",
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "category": "spot",
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": "1636282505818800896",
|
||
# "orderLinkId": "1636282505818800897"
|
||
# },
|
||
# {
|
||
# "category": "spot",
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": "1636282505818800898",
|
||
# "orderLinkId": "1636282505818800899"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {
|
||
# "list": [
|
||
# {
|
||
# "code": "0",
|
||
# "msg": "OK"
|
||
# },
|
||
# {
|
||
# "code": "0",
|
||
# "msg": "OK"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "time": "1709796158501"
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
row = self.safe_list(result, 'list', [])
|
||
return self.parse_orders(row, None)
|
||
|
||
def cancel_all_orders(self, symbol: Str = None, params={}):
|
||
"""
|
||
cancel all open orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/cancel-all
|
||
|
||
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: True if trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.baseCoin]: Base coin. Supports linear, inverse & option
|
||
:param str [params.settleCoin]: Settle coin. Supports linear, inverse & option
|
||
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
market = None
|
||
request: dict = {}
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('cancelAllOrders', market, params)
|
||
request['category'] = type
|
||
if (type == 'option') and not isUnifiedAccount:
|
||
raise NotSupported(self.id + ' cancelAllOrders() Normal Account not support ' + type + ' market')
|
||
if (type == 'linear') or (type == 'inverse'):
|
||
baseCoin = self.safe_string(params, 'baseCoin')
|
||
if symbol is None and baseCoin is None:
|
||
defaultSettle = self.safe_string(self.options, 'defaultSettle', 'USDT')
|
||
request['settleCoin'] = self.safe_string(params, 'settleCoin', defaultSettle)
|
||
isTrigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
params = self.omit(params, ['stop', 'trigger'])
|
||
if isTrigger:
|
||
request['orderFilter'] = 'StopOrder'
|
||
response = self.privatePostV5OrderCancelAll(self.extend(request, params))
|
||
#
|
||
# linear / inverse / option
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "orderId": "f6a73e1f-39b5-4dee-af21-1460b2e3b27c",
|
||
# "orderLinkId": "a001"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672219780463
|
||
# }
|
||
#
|
||
# spot
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "success": "1"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1676962409398
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
orders = self.safe_list(result, 'list')
|
||
if not isinstance(orders, list):
|
||
return [self.safe_order({'info': response})]
|
||
return self.parse_orders(orders, market)
|
||
|
||
def fetch_order_classic(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
fetches information on an order made by the user *classic accounts only*
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str id: the order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['spot']:
|
||
raise NotSupported(self.id + ' fetchOrder() is not supported for spot markets')
|
||
request: dict = {
|
||
'orderId': id,
|
||
}
|
||
result = self.fetch_orders(symbol, None, None, self.extend(request, params))
|
||
length = len(result)
|
||
if length == 0:
|
||
isTrigger = self.safe_bool_n(params, ['trigger', 'stop'], False)
|
||
extra = '' if isTrigger else ' If you are trying to fetch SL/TP conditional order, you might try setting params["trigger"] = True'
|
||
raise OrderNotFound('Order ' + str(id) + ' was not found.' + extra)
|
||
if length > 1:
|
||
raise InvalidOrder(self.id + ' returned more than one order')
|
||
return self.safe_value(result, 0)
|
||
|
||
def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
classic accounts only/ spot not supported* fetches information on an order made by the user *classic accounts only*
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str id: the order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param dict [params.acknowledged]: to suppress the warning, set to True
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
if not isUnifiedAccount:
|
||
return self.fetch_order_classic(id, symbol, params)
|
||
acknowledge = False
|
||
acknowledge, params = self.handle_option_and_params(params, 'fetchOrder', 'acknowledged')
|
||
if not acknowledge:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() can only access an order if it is in last 500 orders(of any status) for your account. Set params["acknowledged"] = True to hide self warning. Alternatively, we suggest to use fetchOpenOrder or fetchClosedOrder')
|
||
market = self.market(symbol)
|
||
marketType = None
|
||
marketType, params = self.get_bybit_type('fetchOrder', market, params)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'orderId': id,
|
||
'category': marketType,
|
||
}
|
||
isTrigger = None
|
||
isTrigger, params = self.handle_param_bool_2(params, 'trigger', 'stop', False)
|
||
if isTrigger:
|
||
request['orderFilter'] = 'StopOrder'
|
||
response = self.privateGetV5OrderRealtime(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "1321052653536515584%3A1672217748287%2C1321052653536515584%3A1672217748287",
|
||
# "category": "spot",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "orderType": "Limit",
|
||
# "orderLinkId": "1672217748277652",
|
||
# "orderId": "1321052653536515584",
|
||
# "cancelType": "UNKNOWN",
|
||
# "avgPrice": "",
|
||
# "stopOrderType": "tpslOrder",
|
||
# "lastPriceOnCreated": "",
|
||
# "orderStatus": "Cancelled",
|
||
# "takeProfit": "",
|
||
# "cumExecValue": "0",
|
||
# "triggerDirection": 0,
|
||
# "isLeverage": "0",
|
||
# "rejectReason": "",
|
||
# "price": "1000",
|
||
# "orderIv": "",
|
||
# "createdTime": "1672217748287",
|
||
# "tpTriggerBy": "",
|
||
# "positionIdx": 0,
|
||
# "timeInForce": "GTC",
|
||
# "leavesValue": "500",
|
||
# "updatedTime": "1672217748287",
|
||
# "side": "Buy",
|
||
# "triggerPrice": "1500",
|
||
# "cumExecFee": "0",
|
||
# "leavesQty": "0",
|
||
# "slTriggerBy": "",
|
||
# "closeOnTrigger": False,
|
||
# "cumExecQty": "0",
|
||
# "reduceOnly": False,
|
||
# "qty": "0.5",
|
||
# "stopLoss": "",
|
||
# "triggerBy": "1192.5"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672219526294
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
innerList = self.safe_list(result, 'list', [])
|
||
if len(innerList) == 0:
|
||
extra = '' if isTrigger else ' If you are trying to fetch SL/TP conditional order, you might try setting params["trigger"] = True'
|
||
raise OrderNotFound('Order ' + str(id) + ' was not found.' + extra)
|
||
order = self.safe_dict(innerList, 0, {})
|
||
return self.parse_order(order, market)
|
||
|
||
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
res = self.is_unified_enabled()
|
||
"""
|
||
*classic accounts only/ spot not supported* fetches information on multiple orders made by the user *classic accounts only/ spot not supported*
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
:param str symbol: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: True if trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
enableUnifiedAccount = self.safe_bool(res, 1)
|
||
if enableUnifiedAccount:
|
||
raise NotSupported(self.id + ' fetchOrders() is not supported after the 5/02 update for UTA accounts, please use fetchOpenOrders, fetchClosedOrders or fetchCanceledOrders')
|
||
return self.fetch_orders_classic(symbol, since, limit, params)
|
||
|
||
def fetch_orders_classic(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple orders made by the user *classic accounts only*
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str symbol: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: True if trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchOrders', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchOrders', market, params)
|
||
if type == 'spot':
|
||
raise NotSupported(self.id + ' fetchOrders() is not supported for spot markets')
|
||
request['category'] = type
|
||
isTrigger = self.safe_bool_n(params, ['trigger', 'stop'], False)
|
||
params = self.omit(params, ['trigger', 'stop'])
|
||
if isTrigger:
|
||
request['orderFilter'] = 'StopOrder'
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
response = self.privateGetV5OrderHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "03234de9-1332-41eb-b805-4a9f42c136a3%3A1672220109387%2C03234de9-1332-41eb-b805-4a9f42c136a3%3A1672220109387",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderType": "Limit",
|
||
# "orderLinkId": "test-001",
|
||
# "orderId": "03234de9-1332-41eb-b805-4a9f42c136a3",
|
||
# "cancelType": "CancelByUser",
|
||
# "avgPrice": "0",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "lastPriceOnCreated": "16656.5",
|
||
# "orderStatus": "Cancelled",
|
||
# "takeProfit": "",
|
||
# "cumExecValue": "0",
|
||
# "triggerDirection": 0,
|
||
# "blockTradeId": "",
|
||
# "rejectReason": "EC_PerCancelRequest",
|
||
# "isLeverage": "",
|
||
# "price": "18000",
|
||
# "orderIv": "",
|
||
# "createdTime": "1672220109387",
|
||
# "tpTriggerBy": "UNKNOWN",
|
||
# "positionIdx": 0,
|
||
# "timeInForce": "GoodTillCancel",
|
||
# "leavesValue": "0",
|
||
# "updatedTime": "1672220114123",
|
||
# "side": "Sell",
|
||
# "triggerPrice": "",
|
||
# "cumExecFee": "0",
|
||
# "slTriggerBy": "UNKNOWN",
|
||
# "leavesQty": "0",
|
||
# "closeOnTrigger": False,
|
||
# "cumExecQty": "0",
|
||
# "reduceOnly": False,
|
||
# "qty": "0.1",
|
||
# "stopLoss": "",
|
||
# "triggerBy": "UNKNOWN"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672221263862
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_orders(data, market, since, limit)
|
||
|
||
def fetch_closed_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
fetches information on a closed order made by the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str id: order id
|
||
:param str [symbol]: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: set to True for fetching a closed trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'orderId': id,
|
||
}
|
||
result = self.fetch_closed_orders(symbol, None, None, self.extend(request, params))
|
||
length = len(result)
|
||
if length == 0:
|
||
isTrigger = self.safe_bool_n(params, ['trigger', 'stop'], False)
|
||
extra = '' if isTrigger else ' If you are trying to fetch SL/TP conditional order, you might try setting params["trigger"] = True'
|
||
raise OrderNotFound('Order ' + str(id) + ' was not found.' + extra)
|
||
if length > 1:
|
||
raise InvalidOrder(self.id + ' returned more than one order')
|
||
return self.safe_value(result, 0)
|
||
|
||
def fetch_open_order(self, id: str, symbol: Str = None, params={}) -> Order:
|
||
"""
|
||
fetches information on an open order made by the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/open-order
|
||
|
||
:param str id: order id
|
||
:param str [symbol]: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: set to True for fetching an open trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.baseCoin]: Base coin. Supports linear, inverse & option
|
||
:param str [params.settleCoin]: Settle coin. Supports linear, inverse & option
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'orderId': id,
|
||
}
|
||
result = self.fetch_open_orders(symbol, None, None, self.extend(request, params))
|
||
length = len(result)
|
||
if length == 0:
|
||
isTrigger = self.safe_bool_n(params, ['trigger', 'stop'], False)
|
||
extra = '' if isTrigger else ' If you are trying to fetch SL/TP conditional order, you might try setting params["trigger"] = True'
|
||
raise OrderNotFound('Order ' + str(id) + ' was not found.' + extra)
|
||
if length > 1:
|
||
raise InvalidOrder(self.id + ' returned more than one order')
|
||
return self.safe_value(result, 0)
|
||
|
||
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://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str [symbol]: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: set to True for fetching trigger orders
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchCanceledAndClosedOrders', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchCanceledAndClosedOrders', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchCanceledAndClosedOrders', market, params)
|
||
request['category'] = type
|
||
isTrigger = self.safe_bool_n(params, ['trigger', 'stop'], False)
|
||
params = self.omit(params, ['trigger', 'stop'])
|
||
if isTrigger:
|
||
request['orderFilter'] = 'StopOrder'
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
response = self.privateGetV5OrderHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "f5f2d355-9a11-4af3-9b83-aa1d6ab6ddfe%3A1757837618905%2Caee7453a-a100-465f-857a-3db780e9329a%3A1757837580469",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderType": "Market",
|
||
# "orderLinkId": "",
|
||
# "slLimitPrice": "0",
|
||
# "orderId": "f5f2d355-9a11-4af3-9b83-aa1d6ab6ddfe",
|
||
# "cancelType": "UNKNOWN",
|
||
# "avgPrice": "122529.9",
|
||
# "stopOrderType": "",
|
||
# "lastPriceOnCreated": "123747.9",
|
||
# "orderStatus": "Filled",
|
||
# "createType": "CreateByUser",
|
||
# "takeProfit": "",
|
||
# "cumExecValue": "122.5299",
|
||
# "tpslMode": "",
|
||
# "smpType": "None",
|
||
# "triggerDirection": 0,
|
||
# "blockTradeId": "",
|
||
# "cumFeeDetail": {
|
||
# "USDT": "0.06739145"
|
||
# },
|
||
# "rejectReason": "EC_NoError",
|
||
# "isLeverage": "",
|
||
# "price": "120518",
|
||
# "orderIv": "",
|
||
# "createdTime": "1757837618905",
|
||
# "tpTriggerBy": "",
|
||
# "positionIdx": 0,
|
||
# "timeInForce": "IOC",
|
||
# "leavesValue": "0",
|
||
# "updatedTime": "1757837618909",
|
||
# "side": "Sell",
|
||
# "smpGroup": 0,
|
||
# "triggerPrice": "",
|
||
# "tpLimitPrice": "0",
|
||
# "cumExecFee": "0.06739145",
|
||
# "slTriggerBy": "",
|
||
# "leavesQty": "0",
|
||
# "closeOnTrigger": False,
|
||
# "slippageToleranceType": "UNKNOWN",
|
||
# "placeType": "",
|
||
# "cumExecQty": "0.001",
|
||
# "reduceOnly": True,
|
||
# "qty": "0.001",
|
||
# "stopLoss": "",
|
||
# "smpOrderId": "",
|
||
# "slippageTolerance": "0",
|
||
# "triggerBy": "",
|
||
# "extraFees": ""
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1758187806376
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_orders(data, market, since, limit)
|
||
|
||
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple closed orders made by the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str [symbol]: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: set to True for fetching closed trigger orders
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'orderStatus': 'Filled',
|
||
}
|
||
return self.fetch_canceled_and_closed_orders(symbol, since, limit, self.extend(request, params))
|
||
|
||
def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple canceled orders made by the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/order-list
|
||
|
||
:param str [symbol]: unified market symbol of the market orders were made in
|
||
:param int [since]: timestamp in ms of the earliest order, default is None
|
||
:param int [limit]: max number of orders to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: True if trigger order
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'orderStatus': 'Cancelled',
|
||
}
|
||
return self.fetch_canceled_and_closed_orders(symbol, since, limit, self.extend(request, params))
|
||
|
||
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetch all unfilled currently open orders
|
||
|
||
https://bybit-exchange.github.io/docs/v5/order/open-order
|
||
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch open orders for
|
||
:param int [limit]: the maximum number of open orders structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.trigger]: set to True for fetching open trigger orders
|
||
:param boolean [params.stop]: alias for trigger
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.baseCoin]: Base coin. Supports linear, inverse & option
|
||
:param str [params.settleCoin]: Settle coin. Supports linear, inverse & option
|
||
:param str [params.orderFilter]: 'Order' or 'StopOrder' or 'tpslOrder'
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchOpenOrders', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchOpenOrders', market, params)
|
||
if type == 'linear' or type == 'inverse':
|
||
baseCoin = self.safe_string(params, 'baseCoin')
|
||
if symbol is None and baseCoin is None:
|
||
defaultSettle = self.safe_string(self.options, 'defaultSettle', 'USDT')
|
||
settleCoin = self.safe_string(params, 'settleCoin', defaultSettle)
|
||
request['settleCoin'] = settleCoin
|
||
request['category'] = type
|
||
isTrigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
params = self.omit(params, ['stop', 'trigger'])
|
||
if isTrigger:
|
||
request['orderFilter'] = 'StopOrder'
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.privateGetV5OrderRealtime(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "f5f2d355-9a11-4af3-9b83-aa1d6ab6ddfe%3A1757837618905%2Caee7453a-a100-465f-857a-3db780e9329a%3A1757837580469",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderType": "Market",
|
||
# "orderLinkId": "",
|
||
# "slLimitPrice": "0",
|
||
# "orderId": "f5f2d355-9a11-4af3-9b83-aa1d6ab6ddfe",
|
||
# "cancelType": "UNKNOWN",
|
||
# "avgPrice": "122529.9",
|
||
# "stopOrderType": "",
|
||
# "lastPriceOnCreated": "123747.9",
|
||
# "orderStatus": "Filled",
|
||
# "createType": "CreateByUser",
|
||
# "takeProfit": "",
|
||
# "cumExecValue": "122.5299",
|
||
# "tpslMode": "",
|
||
# "smpType": "None",
|
||
# "triggerDirection": 0,
|
||
# "blockTradeId": "",
|
||
# "cumFeeDetail": {
|
||
# "USDT": "0.06739145"
|
||
# },
|
||
# "rejectReason": "EC_NoError",
|
||
# "isLeverage": "",
|
||
# "price": "120518",
|
||
# "orderIv": "",
|
||
# "createdTime": "1757837618905",
|
||
# "tpTriggerBy": "",
|
||
# "positionIdx": 0,
|
||
# "timeInForce": "IOC",
|
||
# "leavesValue": "0",
|
||
# "updatedTime": "1757837618909",
|
||
# "side": "Sell",
|
||
# "smpGroup": 0,
|
||
# "triggerPrice": "",
|
||
# "tpLimitPrice": "0",
|
||
# "cumExecFee": "0.06739145",
|
||
# "slTriggerBy": "",
|
||
# "leavesQty": "0",
|
||
# "closeOnTrigger": False,
|
||
# "slippageToleranceType": "UNKNOWN",
|
||
# "placeType": "",
|
||
# "cumExecQty": "0.001",
|
||
# "reduceOnly": True,
|
||
# "qty": "0.001",
|
||
# "stopLoss": "",
|
||
# "smpOrderId": "",
|
||
# "slippageTolerance": "0",
|
||
# "triggerBy": "",
|
||
# "extraFees": ""
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1758187806376
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_orders(data, market, since, limit)
|
||
|
||
def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
fetch all the trades made from a single order
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position/execution
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
:param int [limit]: the maximum number of trades to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
request: dict = {}
|
||
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'orderLinkId')
|
||
if clientOrderId is not None:
|
||
request['orderLinkId'] = clientOrderId
|
||
else:
|
||
request['orderId'] = id
|
||
params = self.omit(params, ['clientOrderId', 'orderLinkId'])
|
||
return self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
|
||
|
||
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://bybit-exchange.github.io/docs/api-explorer/v5/position/execution
|
||
|
||
: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 str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchMyTrades', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 100)
|
||
request: dict = {
|
||
'execType': 'Trade',
|
||
}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchMyTrades', market, params)
|
||
request['category'] = type
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5ExecutionList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "132766%3A2%2C132766%3A2",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHPERP",
|
||
# "orderType": "Market",
|
||
# "underlyingPrice": "",
|
||
# "orderLinkId": "",
|
||
# "side": "Buy",
|
||
# "indexPrice": "",
|
||
# "orderId": "8c065341-7b52-4ca9-ac2c-37e31ac55c94",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "leavesQty": "0",
|
||
# "execTime": "1672282722429",
|
||
# "isMaker": False,
|
||
# "execFee": "0.071409",
|
||
# "feeRate": "0.0006",
|
||
# "execId": "e0cbe81d-0f18-5866-9415-cf319b5dab3b",
|
||
# "tradeIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "1183.54",
|
||
# "execPrice": "1190.15",
|
||
# "markIv": "",
|
||
# "orderQty": "0.1",
|
||
# "orderPrice": "1236.9",
|
||
# "execValue": "119.015",
|
||
# "execType": "Trade",
|
||
# "execQty": "0.1"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672283754510
|
||
# }
|
||
#
|
||
trades = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
||
#
|
||
# {
|
||
# "chainType": "ERC20",
|
||
# "addressDeposit": "0xf56297c6717c1d1c42c30324468ed50a9b7402ee",
|
||
# "tagDeposit": '',
|
||
# "chain": "ETH"
|
||
# }
|
||
#
|
||
address = self.safe_string(depositAddress, 'addressDeposit')
|
||
tag = self.safe_string(depositAddress, 'tagDeposit')
|
||
code = self.safe_string(currency, 'code')
|
||
self.check_address(address)
|
||
return {
|
||
'info': depositAddress,
|
||
'currency': code,
|
||
'network': self.network_id_to_code(self.safe_string(depositAddress, 'chain'), code),
|
||
'address': address,
|
||
'tag': tag,
|
||
}
|
||
|
||
def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]:
|
||
"""
|
||
fetch a dictionary of addresses for a currency, indexed by network
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/master-deposit-addr
|
||
|
||
:param str code: unified currency code of the currency for the deposit address
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a dictionary of `address structures <https://docs.ccxt.com/#/?id=address-structure>` indexed by the network
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
}
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chainType'] = self.network_code_to_id(networkCode, code)
|
||
response = self.privateGetV5AssetDepositQueryAddress(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "coin": "USDT",
|
||
# "chains": [
|
||
# {
|
||
# "chainType": "ERC20",
|
||
# "addressDeposit": "0xd9e1cd77afa0e50b452a62fbb68a3340602286c3",
|
||
# "tagDeposit": "",
|
||
# "chain": "ETH"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672192792860
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
chains = self.safe_list(result, 'chains', [])
|
||
coin = self.safe_string(result, 'coin')
|
||
currency = self.currency(coin)
|
||
parsed = self.parse_deposit_addresses(chains, [currency['code']], False, {
|
||
'currency': currency['code'],
|
||
})
|
||
return self.index_by(parsed, 'network')
|
||
|
||
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
fetch the deposit address for a currency associated with self account
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/master-deposit-addr
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
networkCode, paramsOmited = self.handle_network_code_and_params(params)
|
||
indexedAddresses = self.fetch_deposit_addresses_by_network(code, paramsOmited)
|
||
selectedNetworkCode = self.select_network_code_from_unified_networks(currency['code'], networkCode, indexedAddresses)
|
||
return indexedAddresses[selectedNetworkCode]
|
||
|
||
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all deposits made to an account
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/deposit-record
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: the earliest time in ms to fetch deposits for, default = 30 days before the current time
|
||
:param int [limit]: the maximum number of deposits structures to retrieve, default = 50, max = 50
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch deposits for, default = 30 days after since
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:param str [params.cursor]: used for pagination
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchDeposits', code, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {
|
||
# 'coin': currency['id'],
|
||
# 'limit': 20, # max 50
|
||
# 'cursor': '',
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5AssetDepositQueryRecord(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "rows": [
|
||
# {
|
||
# "coin": "USDT",
|
||
# "chain": "ETH",
|
||
# "amount": "10000",
|
||
# "txID": "skip-notification-scene-test-amount-202212270944-533285-USDT",
|
||
# "status": 3,
|
||
# "toAddress": "test-amount-address",
|
||
# "tag": "",
|
||
# "depositFee": "",
|
||
# "successAt": "1672134274000",
|
||
# "confirmations": "10000",
|
||
# "txIndex": "",
|
||
# "blockHash": ""
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": "eyJtaW5JRCI6MTA0NjA0MywibWF4SUQiOjEwNDYwNDN9"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672191992512
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_transactions(data, currency, since, limit)
|
||
|
||
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all withdrawals made from an account
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/withdraw-record
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: the earliest time in ms to fetch withdrawals for
|
||
:param int [limit]: the maximum number of withdrawals structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchWithdrawals', code, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {
|
||
# 'coin': currency['id'],
|
||
# 'limit': 20, # max 50
|
||
# 'cusor': '',
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5AssetWithdrawQueryRecord(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "rows": [
|
||
# {
|
||
# "coin": "USDT",
|
||
# "chain": "ETH",
|
||
# "amount": "77",
|
||
# "txID": "",
|
||
# "status": "SecurityCheck",
|
||
# "toAddress": "0x99ced129603abc771c0dabe935c326ff6c86645d",
|
||
# "tag": "",
|
||
# "withdrawFee": "10",
|
||
# "createTime": "1670922217000",
|
||
# "updateTime": "1670922217000",
|
||
# "withdrawId": "9976",
|
||
# "withdrawType": 0
|
||
# },
|
||
# {
|
||
# "coin": "USDT",
|
||
# "chain": "ETH",
|
||
# "amount": "26",
|
||
# "txID": "",
|
||
# "status": "success",
|
||
# "toAddress": "15638072681@163.com",
|
||
# "tag": "",
|
||
# "withdrawFee": "0",
|
||
# "createTime": "1669711121000",
|
||
# "updateTime": "1669711380000",
|
||
# "withdrawId": "9801",
|
||
# "withdrawType": 1
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": "eyJtaW5JRCI6OTgwMSwibWF4SUQiOjk5NzZ9"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672194949928
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_transactions(data, currency, since, limit)
|
||
|
||
def parse_transaction_status(self, status: Str):
|
||
statuses: dict = {
|
||
# v3 deposit status
|
||
'0': 'unknown',
|
||
'1': 'pending',
|
||
'2': 'processing',
|
||
'3': 'ok',
|
||
'4': 'fail',
|
||
# v3 withdrawal status
|
||
'SecurityCheck': 'pending',
|
||
'Pending': 'pending',
|
||
'success': 'ok',
|
||
'CancelByUser': 'canceled',
|
||
'Reject': 'rejected',
|
||
'Fail': 'failed',
|
||
'BlockchainConfirmed': 'ok',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchWithdrawals
|
||
#
|
||
# {
|
||
# "coin": "USDT",
|
||
# "chain": "TRX",
|
||
# "amount": "12.34",
|
||
# "txID": "de5ea0a2f2e59dc9a714837dd3ddc6d5e151b56ec5d786d351c4f52336f80d3c",
|
||
# "status": "success",
|
||
# "toAddress": "TQdmFKUoe1Lk2iwZuwRJEHJreTUBoN3BAw",
|
||
# "tag": "",
|
||
# "withdrawFee": "0.5",
|
||
# "createTime": "1665144183000",
|
||
# "updateTime": "1665144256000",
|
||
# "withdrawId": "8839035"
|
||
# }
|
||
#
|
||
# fetchDeposits
|
||
#
|
||
# {
|
||
# "coin": "USDT",
|
||
# "chain": "TRX",
|
||
# "amount": "44",
|
||
# "txID": "0b038ea12fa1575e2d66693db3c346b700d4b28347afc39f80321cf089acc960",
|
||
# "status": "3",
|
||
# "toAddress": "TC6NCAC5WSVCCiaD3kWZXyW91ZKKhLm53b",
|
||
# "tag": "",
|
||
# "depositFee": "",
|
||
# "successAt": "1665142507000",
|
||
# "confirmations": "100",
|
||
# "txIndex": "0",
|
||
# "blockHash": "0000000002ac3b1064aee94bca1bd0b58c4c09c65813b084b87a2063d961129e"
|
||
# }
|
||
#
|
||
# withdraw
|
||
#
|
||
# {
|
||
# "id": "9377266"
|
||
# }
|
||
#
|
||
currencyId = self.safe_string(transaction, 'coin')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
timestamp = self.safe_integer_2(transaction, 'createTime', 'successAt')
|
||
updated = self.safe_integer(transaction, 'updateTime')
|
||
status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
|
||
feeCost = self.safe_number_2(transaction, 'depositFee', 'withdrawFee')
|
||
type = 'deposit' if ('depositFee' in transaction) else 'withdrawal'
|
||
fee = None
|
||
if feeCost is not None:
|
||
fee = {
|
||
'cost': feeCost,
|
||
'currency': code,
|
||
}
|
||
toAddress = self.safe_string(transaction, 'toAddress')
|
||
return {
|
||
'info': transaction,
|
||
'id': self.safe_string_2(transaction, 'id', 'withdrawId'),
|
||
'txid': self.safe_string(transaction, 'txID'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'network': self.network_id_to_code(self.safe_string(transaction, 'chain')),
|
||
'address': None,
|
||
'addressTo': toAddress,
|
||
'addressFrom': None,
|
||
'tag': self.safe_string(transaction, 'tag'),
|
||
'tagTo': None,
|
||
'tagFrom': None,
|
||
'type': type,
|
||
'amount': self.safe_number(transaction, 'amount'),
|
||
'currency': code,
|
||
'status': status,
|
||
'updated': updated,
|
||
'fee': fee,
|
||
'internal': None,
|
||
'comment': None,
|
||
}
|
||
|
||
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
||
"""
|
||
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/account/transaction-log
|
||
https://bybit-exchange.github.io/docs/v5/account/contract-transaction-log
|
||
|
||
:param str [code]: unified currency code, default is None
|
||
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
||
:param int [limit]: max number of ledger entries to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:param str [params.subType]: if inverse will use v5/account/contract-transaction-log
|
||
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchLedger', code, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
request: dict = {
|
||
# 'coin': currency['id'],
|
||
# 'currency': currency['id'], # alias
|
||
# 'start_date': self.iso8601(since),
|
||
# 'end_date': self.iso8601(until),
|
||
# 'wallet_fund_type': 'Deposit', # Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit
|
||
# 'page': 1,
|
||
# 'limit': 20, # max 50
|
||
# v5 transaction log
|
||
# 'accountType': '', Account Type. UNIFIED
|
||
# 'category': '', Product type. spot,linear,option
|
||
# 'currency': '', Currency
|
||
# 'baseCoin': '', BaseCoin. e.g., BTC of BTCPERP
|
||
# 'type': '', Types of transaction logs
|
||
# 'startTime': 0, The start timestamp(ms)
|
||
# 'endTime': 0, The end timestamp(ms)
|
||
# 'limit': 0, Limit for data size per page. [1, 50]. Default: 20
|
||
# 'cursor': '', Cursor. Used for pagination
|
||
}
|
||
enableUnified = self.is_unified_enabled()
|
||
currency = None
|
||
currencyKey = 'coin'
|
||
if enableUnified[1]:
|
||
currencyKey = 'currency'
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
else:
|
||
if since is not None:
|
||
request['start_date'] = self.yyyymmdd(since)
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request[currencyKey] = currency['id']
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchLedger', None, params)
|
||
response = None
|
||
if enableUnified[1]:
|
||
unifiedMarginStatus = self.safe_integer(self.options, 'unifiedMarginStatus', 5) # 3/4 uta 1.0, 5/6 uta 2.0
|
||
if subType == 'inverse' and (unifiedMarginStatus < 5):
|
||
response = self.privateGetV5AccountContractTransactionLog(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetV5AccountTransactionLog(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetV5AccountContractTransactionLog(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "ret_code": 0,
|
||
# "ret_msg": "ok",
|
||
# "ext_code": "",
|
||
# "result": {
|
||
# "data": [
|
||
# {
|
||
# "id": 234467,
|
||
# "user_id": 1,
|
||
# "coin": "BTC",
|
||
# "wallet_id": 27913,
|
||
# "type": "Realized P&L",
|
||
# "amount": "-0.00000006",
|
||
# "tx_id": "",
|
||
# "address": "BTCUSD",
|
||
# "wallet_balance": "0.03000330",
|
||
# "exec_time": "2019-12-09T00:00:25.000Z",
|
||
# "cross_seq": 0
|
||
# }
|
||
# ]
|
||
# },
|
||
# "ext_info": null,
|
||
# "time_now": "1577481867.115552",
|
||
# "rate_limit_status": 119,
|
||
# "rate_limit_reset_ms": 1577481867122,
|
||
# "rate_limit": 120
|
||
# }
|
||
#
|
||
# v5 transaction log
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "21963%3A1%2C14954%3A1",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "side": "Buy",
|
||
# "funding": "-0.003676",
|
||
# "orderLinkId": "",
|
||
# "orderId": "1672128000-8-592324-1-2",
|
||
# "fee": "0.00000000",
|
||
# "change": "-0.003676",
|
||
# "cashFlow": "0",
|
||
# "transactionTime": "1672128000000",
|
||
# "type": "SETTLEMENT",
|
||
# "feeRate": "0.0001",
|
||
# "size": "100",
|
||
# "qty": "100",
|
||
# "cashBalance": "5086.55825002",
|
||
# "currency": "USDT",
|
||
# "category": "linear",
|
||
# "tradePrice": "0.3676",
|
||
# "tradeId": "534c0003-4bf7-486f-aa02-78cee36825e4"
|
||
# },
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "side": "Buy",
|
||
# "funding": "",
|
||
# "orderLinkId": "linear-order",
|
||
# "orderId": "592b7e41-78fd-42e2-9aa3-91e1835ef3e1",
|
||
# "fee": "0.01908720",
|
||
# "change": "-0.0190872",
|
||
# "cashFlow": "0",
|
||
# "transactionTime": "1672121182224",
|
||
# "type": "TRADE",
|
||
# "feeRate": "0.0006",
|
||
# "size": "100",
|
||
# "qty": "88",
|
||
# "cashBalance": "5086.56192602",
|
||
# "currency": "USDT",
|
||
# "category": "linear",
|
||
# "tradePrice": "0.3615",
|
||
# "tradeId": "5184f079-88ec-54c7-8774-5173cafd2b4e"
|
||
# },
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "side": "Buy",
|
||
# "funding": "",
|
||
# "orderLinkId": "linear-order",
|
||
# "orderId": "592b7e41-78fd-42e2-9aa3-91e1835ef3e1",
|
||
# "fee": "0.00260280",
|
||
# "change": "-0.0026028",
|
||
# "cashFlow": "0",
|
||
# "transactionTime": "1672121182224",
|
||
# "type": "TRADE",
|
||
# "feeRate": "0.0006",
|
||
# "size": "12",
|
||
# "qty": "12",
|
||
# "cashBalance": "5086.58101322",
|
||
# "currency": "USDT",
|
||
# "category": "linear",
|
||
# "tradePrice": "0.3615",
|
||
# "tradeId": "8569c10f-5061-5891-81c4-a54929847eb3"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672132481405
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_ledger(data, currency, since, limit)
|
||
|
||
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
||
#
|
||
# {
|
||
# "id": 234467,
|
||
# "user_id": 1,
|
||
# "coin": "BTC",
|
||
# "wallet_id": 27913,
|
||
# "type": "Realized P&L",
|
||
# "amount": "-0.00000006",
|
||
# "tx_id": "",
|
||
# "address": "BTCUSD",
|
||
# "wallet_balance": "0.03000330",
|
||
# "exec_time": "2019-12-09T00:00:25.000Z",
|
||
# "cross_seq": 0
|
||
# }
|
||
#
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "side": "Buy",
|
||
# "funding": "",
|
||
# "orderLinkId": "linear-order",
|
||
# "orderId": "592b7e41-78fd-42e2-9aa3-91e1835ef3e1",
|
||
# "fee": "0.00260280",
|
||
# "change": "-0.0026028",
|
||
# "cashFlow": "0",
|
||
# "transactionTime": "1672121182224",
|
||
# "type": "TRADE",
|
||
# "feeRate": "0.0006",
|
||
# "size": "12",
|
||
# "qty": "12",
|
||
# "cashBalance": "5086.58101322",
|
||
# "currency": "USDT",
|
||
# "category": "linear",
|
||
# "tradePrice": "0.3615",
|
||
# "tradeId": "8569c10f-5061-5891-81c4-a54929847eb3"
|
||
# }
|
||
#
|
||
currencyId = self.safe_string_2(item, 'coin', 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
currency = self.safe_currency(currencyId, currency)
|
||
amountString = self.safe_string_2(item, 'amount', 'change')
|
||
afterString = self.safe_string_2(item, 'wallet_balance', 'cashBalance')
|
||
direction = 'out' if Precise.string_lt(amountString, '0') else 'in'
|
||
before = None
|
||
after = None
|
||
amount = None
|
||
if afterString is not None and amountString is not None:
|
||
difference = amountString if (direction == 'out') else Precise.string_neg(amountString)
|
||
before = self.parse_to_numeric(Precise.string_add(afterString, difference))
|
||
after = self.parse_to_numeric(afterString)
|
||
amount = self.parse_to_numeric(Precise.string_abs(amountString))
|
||
timestamp = self.parse8601(self.safe_string(item, 'exec_time'))
|
||
if timestamp is None:
|
||
timestamp = self.safe_integer(item, 'transactionTime')
|
||
return self.safe_ledger_entry({
|
||
'info': item,
|
||
'id': self.safe_string(item, 'id'),
|
||
'direction': direction,
|
||
'account': self.safe_string(item, 'wallet_id'),
|
||
'referenceId': self.safe_string(item, 'tx_id'),
|
||
'referenceAccount': None,
|
||
'type': self.parse_ledger_entry_type(self.safe_string(item, 'type')),
|
||
'currency': code,
|
||
'amount': amount,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'before': before,
|
||
'after': after,
|
||
'status': 'ok',
|
||
'fee': {
|
||
'currency': code,
|
||
'cost': self.safe_number(item, 'fee'),
|
||
},
|
||
}, currency)
|
||
|
||
def parse_ledger_entry_type(self, type):
|
||
types: dict = {
|
||
'Deposit': 'transaction',
|
||
'Withdraw': 'transaction',
|
||
'RealisedPNL': 'trade',
|
||
'Commission': 'fee',
|
||
'Refund': 'cashback',
|
||
'Prize': 'prize', # ?
|
||
'ExchangeOrderWithdraw': 'transaction',
|
||
'ExchangeOrderDeposit': 'transaction',
|
||
# v5
|
||
'TRANSFER_IN': 'transaction',
|
||
'TRANSFER_OUT': 'transaction',
|
||
'TRADE': 'trade',
|
||
'SETTLEMENT': 'trade',
|
||
'DELIVERY': 'trade',
|
||
'LIQUIDATION': 'trade',
|
||
'BONUS': 'Prize',
|
||
'FEE_REFUND': 'cashback',
|
||
'INTEREST': 'transaction',
|
||
'CURRENCY_BUY': 'trade',
|
||
'CURRENCY_SELL': 'trade',
|
||
}
|
||
return self.safe_string(types, type, type)
|
||
|
||
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
make a withdrawal
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/withdraw
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: the amount to withdraw
|
||
:param str address: the address to withdraw to
|
||
:param str tag:
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.accountType]: 'UTA', 'FUND', 'FUND,UTA', and 'SPOT(for classic accounts only)
|
||
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
||
accountType = None
|
||
accounts = self.is_unified_enabled()
|
||
isUta = accounts[1]
|
||
accountType, params = self.handle_option_and_params(params, 'withdraw', 'accountType')
|
||
if accountType is None:
|
||
accountType = 'UTA' if isUta else 'SPOT'
|
||
self.load_markets()
|
||
self.check_address(address)
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'amount': self.number_to_string(amount),
|
||
'address': address,
|
||
'timestamp': self.milliseconds(),
|
||
'accountType': accountType,
|
||
}
|
||
if tag is not None:
|
||
request['tag'] = tag
|
||
networkCode, query = self.handle_network_code_and_params(params)
|
||
networkId = self.network_code_to_id(networkCode)
|
||
if networkId is not None:
|
||
request['chain'] = networkId.upper()
|
||
response = self.privatePostV5AssetWithdrawCreate(self.extend(request, query))
|
||
#
|
||
# {
|
||
# "retCode": "0",
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "id": "9377266"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": "1666892894902"
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
return self.parse_transaction(result, currency)
|
||
|
||
def fetch_position(self, symbol: str, params={}) -> Position:
|
||
"""
|
||
fetch data on a single open contract trade position
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position
|
||
|
||
:param str symbol: unified market symbol of the market the position is held in, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchPosition() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = None
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchPosition', market, params)
|
||
request['category'] = type
|
||
response = self.privateGetV5PositionList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "updateAt%3D1672279322668",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "leverage": "10",
|
||
# "avgPrice": "0.3615",
|
||
# "liqPrice": "0.0001",
|
||
# "riskLimitValue": "200000",
|
||
# "takeProfit": "",
|
||
# "positionValue": "36.15",
|
||
# "tpslMode": "Full",
|
||
# "riskId": 41,
|
||
# "trailingStop": "0",
|
||
# "unrealisedPnl": "-1.83",
|
||
# "markPrice": "0.3432",
|
||
# "cumRealisedPnl": "0.48805876",
|
||
# "positionMM": "0.381021",
|
||
# "createdTime": "1672121182216",
|
||
# "positionIdx": 0,
|
||
# "positionIM": "3.634521",
|
||
# "updatedTime": "1672279322668",
|
||
# "side": "Buy",
|
||
# "bustPrice": "",
|
||
# "size": "100",
|
||
# "positionStatus": "Normal",
|
||
# "stopLoss": "",
|
||
# "tradeMode": 0
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672280219169
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
positions = self.safe_list_2(result, 'list', 'dataList', [])
|
||
timestamp = self.safe_integer(response, 'time')
|
||
first = self.safe_dict(positions, 0, {})
|
||
position = self.parse_position(first, market)
|
||
position['timestamp'] = timestamp
|
||
position['datetime'] = self.iso8601(timestamp)
|
||
return position
|
||
|
||
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
||
"""
|
||
fetch all open positions
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position
|
||
|
||
:param str[] symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param str [params.baseCoin]: Base coin. Supports linear, inverse & option
|
||
:param str [params.settleCoin]: Settle coin. Supports linear, inverse & option
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times
|
||
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchPositions', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchPositions', symbols, None, None, params, 'nextPageCursor', 'cursor', None, 200)
|
||
symbol = None
|
||
if (symbols is not None) and isinstance(symbols, list):
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength > 1:
|
||
raise ArgumentsRequired(self.id + ' fetchPositions() does not accept an array with more than one symbol')
|
||
elif symbolsLength == 1:
|
||
symbol = symbols[0]
|
||
symbols = self.market_symbols(symbols)
|
||
elif symbols is not None:
|
||
symbol = symbols
|
||
symbols = [self.symbol(symbol)]
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
symbol = market['symbol']
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchPositions', market, params)
|
||
if type == 'linear' or type == 'inverse':
|
||
baseCoin = self.safe_string(params, 'baseCoin')
|
||
if type == 'linear':
|
||
if symbol is None and baseCoin is None:
|
||
defaultSettle = self.safe_string(self.options, 'defaultSettle', 'USDT')
|
||
settleCoin = self.safe_string(params, 'settleCoin', defaultSettle)
|
||
request['settleCoin'] = settleCoin
|
||
else:
|
||
# inverse
|
||
if symbol is None and baseCoin is None:
|
||
request['category'] = 'inverse'
|
||
if self.safe_integer(params, 'limit') is None:
|
||
request['limit'] = 200 # max limit
|
||
params = self.omit(params, ['type'])
|
||
request['category'] = type
|
||
response = self.privateGetV5PositionList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "Success",
|
||
# "result": {
|
||
# "nextPageCursor": "0%3A1657711949945%2C0%3A1657711949945",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "leverage": "10",
|
||
# "updatedTime": 1657711949945,
|
||
# "side": "Buy",
|
||
# "positionValue": "536.92500000",
|
||
# "takeProfit": "",
|
||
# "tpslMode": "Full",
|
||
# "riskId": 11,
|
||
# "trailingStop": "",
|
||
# "entryPrice": "1073.85000000",
|
||
# "unrealisedPnl": "",
|
||
# "markPrice": "1080.65000000",
|
||
# "size": "0.5000",
|
||
# "positionStatus": "normal",
|
||
# "stopLoss": "",
|
||
# "cumRealisedPnl": "-0.32215500",
|
||
# "positionMM": "2.97456450",
|
||
# "createdTime": 1657711949928,
|
||
# "positionIdx": 0,
|
||
# "positionIM": "53.98243950"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "time": 1657713693182
|
||
# }
|
||
#
|
||
positions = self.add_pagination_cursor_to_result(response)
|
||
results = []
|
||
for i in range(0, len(positions)):
|
||
rawPosition = positions[i]
|
||
if ('data' in rawPosition) and ('is_valid' in rawPosition):
|
||
# futures only
|
||
rawPosition = self.safe_dict(rawPosition, 'data')
|
||
results.append(self.parse_position(rawPosition))
|
||
return self.filter_by_array_positions(results, 'symbol', symbols, False)
|
||
|
||
def parse_position(self, position: dict, market: Market = None) -> Position:
|
||
#
|
||
# linear swap
|
||
#
|
||
# {
|
||
# "positionIdx": 0,
|
||
# "riskId": "11",
|
||
# "symbol": "ETHUSDT",
|
||
# "side": "Buy",
|
||
# "size": "0.10",
|
||
# "positionValue": "119.845",
|
||
# "entryPrice": "1198.45",
|
||
# "tradeMode": 1,
|
||
# "autoAddMargin": 0,
|
||
# "leverage": "4.2",
|
||
# "positionBalance": "28.58931118",
|
||
# "liqPrice": "919.10",
|
||
# "bustPrice": "913.15",
|
||
# "takeProfit": "0.00",
|
||
# "stopLoss": "0.00",
|
||
# "trailingStop": "0.00",
|
||
# "unrealisedPnl": "0.083",
|
||
# "createdTime": "1669097244192",
|
||
# "updatedTime": "1669413126190",
|
||
# "tpSlMode": "Full",
|
||
# "riskLimitValue": "900000",
|
||
# "activePrice": "0.00"
|
||
# }
|
||
#
|
||
# usdc
|
||
# {
|
||
# "symbol":"BTCPERP",
|
||
# "leverage":"1.00",
|
||
# "occClosingFee":"0.0000",
|
||
# "liqPrice":"",
|
||
# "positionValue":"30.8100",
|
||
# "takeProfit":"0.0",
|
||
# "riskId":"10001",
|
||
# "trailingStop":"0.0000",
|
||
# "unrealisedPnl":"0.0000",
|
||
# "createdAt":"1652451795305",
|
||
# "markPrice":"30809.41",
|
||
# "cumRealisedPnl":"0.0000",
|
||
# "positionMM":"0.1541",
|
||
# "positionIM":"30.8100",
|
||
# "updatedAt":"1652451795305",
|
||
# "tpSLMode":"UNKNOWN",
|
||
# "side":"Buy",
|
||
# "bustPrice":"",
|
||
# "deleverageIndicator":"0",
|
||
# "entryPrice":"30810.0",
|
||
# "size":"0.001",
|
||
# "sessionRPL":"0.0000",
|
||
# "positionStatus":"NORMAL",
|
||
# "sessionUPL":"-0.0006",
|
||
# "stopLoss":"0.0",
|
||
# "orderMargin":"0.0000",
|
||
# "sessionAvgPrice":"30810.0"
|
||
# }
|
||
#
|
||
# unified margin
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "leverage": "10",
|
||
# "updatedTime": 1657711949945,
|
||
# "side": "Buy",
|
||
# "positionValue": "536.92500000",
|
||
# "takeProfit": "",
|
||
# "tpslMode": "Full",
|
||
# "riskId": 11,
|
||
# "trailingStop": "",
|
||
# "entryPrice": "1073.85000000",
|
||
# "unrealisedPnl": "",
|
||
# "markPrice": "1080.65000000",
|
||
# "size": "0.5000",
|
||
# "positionStatus": "normal",
|
||
# "stopLoss": "",
|
||
# "cumRealisedPnl": "-0.32215500",
|
||
# "positionMM": "2.97456450",
|
||
# "createdTime": 1657711949928,
|
||
# "positionIdx": 0,
|
||
# "positionIM": "53.98243950"
|
||
# }
|
||
#
|
||
# unified account
|
||
#
|
||
# {
|
||
# "symbol": "XRPUSDT",
|
||
# "leverage": "10",
|
||
# "avgPrice": "0.3615",
|
||
# "liqPrice": "0.0001",
|
||
# "riskLimitValue": "200000",
|
||
# "takeProfit": "",
|
||
# "positionValue": "36.15",
|
||
# "tpslMode": "Full",
|
||
# "riskId": 41,
|
||
# "trailingStop": "0",
|
||
# "unrealisedPnl": "-1.83",
|
||
# "markPrice": "0.3432",
|
||
# "cumRealisedPnl": "0.48805876",
|
||
# "positionMM": "0.381021",
|
||
# "createdTime": "1672121182216",
|
||
# "positionIdx": 0,
|
||
# "positionIM": "3.634521",
|
||
# "updatedTime": "1672279322668",
|
||
# "side": "Buy",
|
||
# "bustPrice": "",
|
||
# "size": "100",
|
||
# "positionStatus": "Normal",
|
||
# "stopLoss": "",
|
||
# "tradeMode": 0
|
||
# }
|
||
#
|
||
# fetchPositionsHistory
|
||
#
|
||
# {
|
||
# symbol: 'XRPUSDT',
|
||
# orderType: 'Market',
|
||
# leverage: '10',
|
||
# updatedTime: '1712717265572',
|
||
# side: 'Sell',
|
||
# orderId: '071749f3-a9fa-427b-b5ca-27b2f52b81de',
|
||
# closedPnl: '-0.00049568',
|
||
# avgEntryPrice: '0.6045',
|
||
# qty: '3',
|
||
# cumEntryValue: '1.8135',
|
||
# createdTime: '1712717265566',
|
||
# orderPrice: '0.5744',
|
||
# closedSize: '3',
|
||
# avgExitPrice: '0.605',
|
||
# execType: 'Trade',
|
||
# fillCount: '1',
|
||
# cumExitValue: '1.815'
|
||
# }
|
||
#
|
||
closedSize = self.safe_string(position, 'closedSize')
|
||
isHistory = (closedSize is not None)
|
||
contract = self.safe_string(position, 'symbol')
|
||
market = self.safe_market(contract, market, None, 'contract')
|
||
size = Precise.string_abs(self.safe_string_2(position, 'size', 'qty'))
|
||
side = self.safe_string(position, 'side')
|
||
if side is not None:
|
||
if side == 'Buy':
|
||
side = 'short' if isHistory else 'long'
|
||
elif side == 'Sell':
|
||
side = 'long' if isHistory else 'short'
|
||
else:
|
||
side = None
|
||
notional = self.safe_string_2(position, 'positionValue', 'cumExitValue')
|
||
unrealisedPnl = self.omit_zero(self.safe_string(position, 'unrealisedPnl'))
|
||
initialMarginString = self.safe_string_2(position, 'positionIM', 'cumEntryValue')
|
||
maintenanceMarginString = self.safe_string(position, 'positionMM')
|
||
timestamp = self.safe_integer_n(position, ['createdTime', 'createdAt'])
|
||
lastUpdateTimestamp = self.parse8601(self.safe_string(position, 'updated_at'))
|
||
if lastUpdateTimestamp is None:
|
||
lastUpdateTimestamp = self.safe_integer_n(position, ['updatedTime', 'updatedAt', 'updatedTime'])
|
||
tradeMode = self.safe_integer(position, 'tradeMode', 0)
|
||
marginMode = None
|
||
if (not self.options['enableUnifiedAccount']) or (self.options['enableUnifiedAccount'] and market['inverse']):
|
||
# tradeMode would work for classic and UTA(inverse)
|
||
if not isHistory: # cannot tell marginMode for fetchPositionsHistory, and closedSize will only be defined for fetchPositionsHistory response
|
||
marginMode = 'isolated' if (tradeMode == 1) else 'cross'
|
||
collateralString = self.safe_string(position, 'positionBalance')
|
||
entryPrice = self.omit_zero(self.safe_string_n(position, ['entryPrice', 'avgPrice', 'avgEntryPrice']))
|
||
markPrice = self.safe_string(position, 'markPrice')
|
||
liquidationPrice = self.omit_zero(self.safe_string(position, 'liqPrice'))
|
||
leverage = self.safe_string(position, 'leverage')
|
||
if liquidationPrice is not None:
|
||
if market['settle'] == 'USDC':
|
||
# (Entry price - Liq price) * Contracts + Maintenance Margin + (unrealised pnl) = Collateral
|
||
price = markPrice if self.safe_bool(self.options, 'useMarkPriceForPositionCollateral', False) else entryPrice
|
||
difference = Precise.string_abs(Precise.string_sub(price, liquidationPrice))
|
||
collateralString = Precise.string_add(Precise.string_add(Precise.string_mul(difference, size), maintenanceMarginString), unrealisedPnl)
|
||
else:
|
||
bustPrice = self.safe_string(position, 'bustPrice')
|
||
if market['linear']:
|
||
# derived from the following formulas
|
||
# (Entry price - Bust price) * Contracts = Collateral
|
||
# (Entry price - Liq price) * Contracts = Collateral - Maintenance Margin
|
||
# Maintenance Margin = (Bust price - Liq price) x Contracts
|
||
maintenanceMarginPriceDifference = Precise.string_abs(Precise.string_sub(liquidationPrice, bustPrice))
|
||
maintenanceMarginString = Precise.string_mul(maintenanceMarginPriceDifference, size)
|
||
# Initial Margin = Contracts x Entry Price / Leverage
|
||
if (entryPrice is not None) and (initialMarginString is None):
|
||
initialMarginString = Precise.string_div(Precise.string_mul(size, entryPrice), leverage)
|
||
else:
|
||
# Contracts * (1 / Entry price - 1 / Bust price) = Collateral
|
||
# Contracts * (1 / Entry price - 1 / Liq price) = Collateral - Maintenance Margin
|
||
# Maintenance Margin = Contracts * (1 / Liq price - 1 / Bust price)
|
||
# Maintenance Margin = Contracts * (Bust price - Liq price) / (Liq price x Bust price)
|
||
difference = Precise.string_abs(Precise.string_sub(bustPrice, liquidationPrice))
|
||
multiply = Precise.string_mul(bustPrice, liquidationPrice)
|
||
maintenanceMarginString = Precise.string_div(Precise.string_mul(size, difference), multiply)
|
||
# Initial Margin = Leverage x Contracts / EntryPrice
|
||
if (entryPrice is not None) and (initialMarginString is None):
|
||
initialMarginString = Precise.string_div(size, Precise.string_mul(entryPrice, leverage))
|
||
maintenanceMarginPercentage = Precise.string_div(maintenanceMarginString, notional)
|
||
marginRatio = Precise.string_div(maintenanceMarginString, collateralString, 4)
|
||
positionIdx = self.safe_string(position, 'positionIdx')
|
||
hedged = (positionIdx is not None) and (positionIdx != '0')
|
||
return self.safe_position({
|
||
'info': position,
|
||
'id': None,
|
||
'symbol': market['symbol'],
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'lastUpdateTimestamp': lastUpdateTimestamp,
|
||
'initialMargin': self.parse_number(initialMarginString),
|
||
'initialMarginPercentage': self.parse_number(Precise.string_div(initialMarginString, notional)),
|
||
'maintenanceMargin': self.parse_number(maintenanceMarginString),
|
||
'maintenanceMarginPercentage': self.parse_number(maintenanceMarginPercentage),
|
||
'entryPrice': self.parse_number(entryPrice),
|
||
'notional': self.parse_number(notional),
|
||
'leverage': self.parse_number(leverage),
|
||
'unrealizedPnl': self.parse_number(unrealisedPnl),
|
||
'realizedPnl': self.safe_number(position, 'closedPnl'),
|
||
'contracts': self.parse_number(size), # in USD for inverse swaps
|
||
'contractSize': self.safe_number(market, 'contractSize'),
|
||
'marginRatio': self.parse_number(marginRatio),
|
||
'liquidationPrice': self.parse_number(liquidationPrice),
|
||
'markPrice': self.parse_number(markPrice),
|
||
'lastPrice': self.safe_number(position, 'avgExitPrice'),
|
||
'collateral': self.parse_number(collateralString),
|
||
'marginMode': marginMode,
|
||
'side': side,
|
||
'percentage': None,
|
||
'stopLossPrice': self.safe_number_2(position, 'stop_loss', 'stopLoss'),
|
||
'takeProfitPrice': self.safe_number_2(position, 'take_profit', 'takeProfit'),
|
||
'hedged': hedged,
|
||
})
|
||
|
||
def fetch_leverage(self, symbol: str, params={}) -> Leverage:
|
||
"""
|
||
fetch the set leverage for a market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
position = self.fetch_position(symbol, params)
|
||
return self.parse_leverage(position, market)
|
||
|
||
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
||
marketId = self.safe_string(leverage, 'symbol')
|
||
leverageValue = self.safe_integer(leverage, 'leverage')
|
||
return {
|
||
'info': leverage,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'marginMode': self.safe_string_lower(leverage, 'marginMode'),
|
||
'longLeverage': leverageValue,
|
||
'shortLeverage': leverageValue,
|
||
}
|
||
|
||
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
||
"""
|
||
set margin mode(account) or trade mode(symbol)
|
||
|
||
https://bybit-exchange.github.io/docs/v5/account/set-margin-mode
|
||
https://bybit-exchange.github.io/docs/v5/position/cross-isolate
|
||
|
||
:param str marginMode: account mode must be either [isolated, cross, portfolio], trade mode must be either [isolated, cross]
|
||
:param str symbol: unified market symbol of the market the position is held in, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.leverage]: the rate of leverage, is required if setting trade mode(symbol)
|
||
:returns dict: response from the exchange
|
||
"""
|
||
self.load_markets()
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
market = None
|
||
response = None
|
||
if isUnifiedAccount:
|
||
if marginMode == 'isolated':
|
||
marginMode = 'ISOLATED_MARGIN'
|
||
elif marginMode == 'cross':
|
||
marginMode = 'REGULAR_MARGIN'
|
||
elif marginMode == 'portfolio':
|
||
marginMode = 'PORTFOLIO_MARGIN'
|
||
else:
|
||
raise NotSupported(self.id + ' setMarginMode() marginMode must be either [isolated, cross, portfolio]')
|
||
request: dict = {
|
||
'setMarginMode': marginMode,
|
||
}
|
||
response = self.privatePostV5AccountSetMarginMode(self.extend(request, params))
|
||
else:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol parameter for non unified account')
|
||
market = self.market(symbol)
|
||
isUsdcSettled = market['settle'] == 'USDC'
|
||
if isUsdcSettled:
|
||
if marginMode == 'cross':
|
||
marginMode = 'REGULAR_MARGIN'
|
||
elif marginMode == 'portfolio':
|
||
marginMode = 'PORTFOLIO_MARGIN'
|
||
else:
|
||
raise NotSupported(self.id + ' setMarginMode() for usdc market marginMode must be either [cross, portfolio]')
|
||
request: dict = {
|
||
'setMarginMode': marginMode,
|
||
}
|
||
response = self.privatePostV5AccountSetMarginMode(self.extend(request, params))
|
||
else:
|
||
type = None
|
||
type, params = self.get_bybit_type('setPositionMode', market, params)
|
||
tradeMode = None
|
||
if marginMode == 'cross':
|
||
tradeMode = 0
|
||
elif marginMode == 'isolated':
|
||
tradeMode = 1
|
||
else:
|
||
raise NotSupported(self.id + ' setMarginMode() with symbol marginMode must be either [isolated, cross]')
|
||
sellLeverage = None
|
||
buyLeverage = None
|
||
leverage = self.safe_string(params, 'leverage')
|
||
if leverage is None:
|
||
sellLeverage = self.safe_string_2(params, 'sell_leverage', 'sellLeverage')
|
||
buyLeverage = self.safe_string_2(params, 'buy_leverage', 'buyLeverage')
|
||
if sellLeverage is None and buyLeverage is None:
|
||
raise ArgumentsRequired(self.id + ' setMarginMode() requires a leverage parameter or sell_leverage and buy_leverage parameters')
|
||
if buyLeverage is None:
|
||
buyLeverage = sellLeverage
|
||
if sellLeverage is None:
|
||
sellLeverage = buyLeverage
|
||
params = self.omit(params, ['buy_leverage', 'sell_leverage', 'sellLeverage', 'buyLeverage'])
|
||
else:
|
||
sellLeverage = leverage
|
||
buyLeverage = leverage
|
||
params = self.omit(params, 'leverage')
|
||
request: dict = {
|
||
'category': type,
|
||
'symbol': market['id'],
|
||
'tradeMode': tradeMode,
|
||
'buyLeverage': buyLeverage,
|
||
'sellLeverage': sellLeverage,
|
||
}
|
||
response = self.privatePostV5PositionSwitchIsolated(self.extend(request, params))
|
||
return response
|
||
|
||
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
||
"""
|
||
set the level of leverage for a market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position/leverage
|
||
|
||
: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.buyLeverage]: leverage for buy side
|
||
:param str [params.sellLeverage]: leverage for sell side
|
||
: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)
|
||
# WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
|
||
# AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
|
||
# engage in leverage setting
|
||
# we reuse the code here instead of having two methods
|
||
leverageString = self.number_to_string(leverage)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'buyLeverage': leverageString,
|
||
'sellLeverage': leverageString,
|
||
}
|
||
request['buyLeverage'] = leverageString
|
||
request['sellLeverage'] = leverageString
|
||
if market['linear']:
|
||
request['category'] = 'linear'
|
||
elif market['inverse']:
|
||
request['category'] = 'inverse'
|
||
else:
|
||
raise NotSupported(self.id + ' setLeverage() only support linear and inverse market')
|
||
response = self.privatePostV5PositionSetLeverage(self.extend(request, params))
|
||
return response
|
||
|
||
def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
|
||
"""
|
||
set hedged to True or False for a market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position/position-mode
|
||
|
||
:param bool hedged:
|
||
:param str symbol: used for unified account with inverse market
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: response from the exchange
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
mode = None
|
||
if hedged:
|
||
mode = 3
|
||
else:
|
||
mode = 0
|
||
request: dict = {
|
||
'mode': mode,
|
||
}
|
||
if symbol is None:
|
||
request['coin'] = 'USDT'
|
||
else:
|
||
request['symbol'] = market['id']
|
||
if symbol is not None:
|
||
request['category'] = 'linear' if market['linear'] else 'inverse'
|
||
else:
|
||
type = None
|
||
type, params = self.get_bybit_type('setPositionMode', market, params)
|
||
request['category'] = type
|
||
params = self.omit(params, 'type')
|
||
response = self.privatePostV5PositionSwitchMode(self.extend(request, params))
|
||
#
|
||
# v5
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {},
|
||
# "retExtInfo": {},
|
||
# "time": 1675249072814
|
||
# }
|
||
return response
|
||
|
||
def fetch_derivatives_open_interest_history(self, symbol: str, timeframe='1h', since: Int = None, limit: Int = None, params={}):
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
subType = 'linear' if market['linear'] else 'inverse'
|
||
category = self.safe_string(params, 'category', subType)
|
||
intervals = self.safe_dict(self.options, 'intervals')
|
||
interval = self.safe_string(intervals, timeframe) # 5min,15min,30min,1h,4h,1d
|
||
if interval is None:
|
||
raise BadRequest(self.id + ' fetchOpenInterestHistory() cannot use the ' + timeframe + ' timeframe')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'intervalTime': interval,
|
||
'category': category,
|
||
}
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
params = self.omit(params, ['until'])
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.publicGetV5MarketOpenInterest(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "symbol": "BTCUSD",
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# {
|
||
# "openInterest": "461134384.00000000",
|
||
# "timestamp": "1669571400000"
|
||
# },
|
||
# {
|
||
# "openInterest": "461134292.00000000",
|
||
# "timestamp": "1669571100000"
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": ""
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672053548579
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
id = self.safe_string(result, 'symbol')
|
||
market = self.safe_market(id, market, None, 'contract')
|
||
return self.parse_open_interests_history(data, market, since, limit)
|
||
|
||
def fetch_open_interest(self, symbol: str, params={}):
|
||
"""
|
||
Retrieves the open interest of a derivative trading pair
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/open-interest
|
||
|
||
:param str symbol: Unified CCXT market symbol
|
||
:param dict [params]: exchange specific parameters
|
||
:param str [params.interval]: 5m, 15m, 30m, 1h, 4h, 1d
|
||
:param str [params.category]: "linear" or "inverse"
|
||
:returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['contract']:
|
||
raise BadRequest(self.id + ' fetchOpenInterest() supports contract markets only')
|
||
timeframe = self.safe_string(params, 'interval', '1h')
|
||
intervals = self.safe_dict(self.options, 'intervals')
|
||
interval = self.safe_string(intervals, timeframe) # 5min,15min,30min,1h,4h,1d
|
||
if interval is None:
|
||
raise BadRequest(self.id + ' fetchOpenInterest() cannot use the ' + timeframe + ' timeframe')
|
||
subType = 'linear' if market['linear'] else 'inverse'
|
||
category = self.safe_string(params, 'category', subType)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'intervalTime': interval,
|
||
'category': category,
|
||
}
|
||
response = self.publicGetV5MarketOpenInterest(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "symbol": "BTCUSD",
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# {
|
||
# "openInterest": "461134384.00000000",
|
||
# "timestamp": "1669571400000"
|
||
# },
|
||
# {
|
||
# "openInterest": "461134292.00000000",
|
||
# "timestamp": "1669571100000"
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": ""
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672053548579
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
id = self.safe_string(result, 'symbol')
|
||
market = self.safe_market(id, market, None, 'contract')
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_open_interest(data[0], market)
|
||
|
||
def fetch_open_interest_history(self, symbol: str, timeframe='1h', since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
Gets the total amount of unsettled contracts. In other words, the total number of contracts held in open positions
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/open-interest
|
||
|
||
:param str symbol: Unified market symbol
|
||
:param str timeframe: "5m", 15m, 30m, 1h, 4h, 1d
|
||
:param int [since]: Not used by Bybit
|
||
:param int [limit]: The number of open interest structures to return. Max 200, default 50
|
||
:param dict [params]: Exchange specific parameters
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns: An array of open interest structures
|
||
"""
|
||
if timeframe == '1m':
|
||
raise BadRequest(self.id + ' fetchOpenInterestHistory cannot use the 1m timeframe')
|
||
self.load_markets()
|
||
paginate = self.safe_bool(params, 'paginate')
|
||
if paginate:
|
||
params = self.omit(params, 'paginate')
|
||
params['timeframe'] = timeframe
|
||
return self.fetch_paginated_call_cursor('fetchOpenInterestHistory', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 200)
|
||
market = self.market(symbol)
|
||
if market['spot'] or market['option']:
|
||
raise BadRequest(self.id + ' fetchOpenInterestHistory() symbol does not support market ' + symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
return self.fetch_derivatives_open_interest_history(symbol, timeframe, since, limit, params)
|
||
|
||
def parse_open_interest(self, interest, market: Market = None):
|
||
#
|
||
# {
|
||
# "openInterest": 64757.62400000,
|
||
# "timestamp": 1665784800000,
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(interest, 'timestamp')
|
||
openInterest = self.safe_number_2(interest, 'open_interest', 'openInterest')
|
||
# the openInterest is in the base asset for linear and quote asset for inverse
|
||
amount = openInterest if market['linear'] else None
|
||
value = openInterest if market['inverse'] else None
|
||
return self.safe_open_interest({
|
||
'symbol': market['symbol'],
|
||
'openInterestAmount': amount,
|
||
'openInterestValue': value,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': interest,
|
||
}, market)
|
||
|
||
def fetch_cross_borrow_rate(self, code: str, params={}) -> CrossBorrowRate:
|
||
"""
|
||
fetch the rate of interest to borrow a currency for margin trading
|
||
|
||
https://bybit-exchange.github.io/docs/zh-TW/v5/spot-margin-normal/interest-quota
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `borrow rate structure <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
}
|
||
response = self.privateGetV5SpotCrossMarginTradeLoanInfo(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": "0",
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "coin": "USDT",
|
||
# "interestRate": "0.000107000000",
|
||
# "loanAbleAmount": "",
|
||
# "maxLoanAmount": "79999.999"
|
||
# },
|
||
# "retExtInfo": null,
|
||
# "time": "1666734490778"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(response, 'time')
|
||
data = self.safe_dict(response, 'result', {})
|
||
data['timestamp'] = timestamp
|
||
return self.parse_borrow_rate(data, currency)
|
||
|
||
def parse_borrow_rate(self, info, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "coin": "USDT",
|
||
# "interestRate": "0.000107000000",
|
||
# "loanAbleAmount": "",
|
||
# "maxLoanAmount": "79999.999",
|
||
# "timestamp": 1666734490778
|
||
# }
|
||
#
|
||
# fetchBorrowRateHistory
|
||
# {
|
||
# "timestamp": 1721469600000,
|
||
# "currency": "USDC",
|
||
# "hourlyBorrowRate": "0.000014621596",
|
||
# "vipLevel": "No VIP"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(info, 'timestamp')
|
||
currencyId = self.safe_string_2(info, 'coin', 'currency')
|
||
hourlyBorrowRate = self.safe_number(info, 'hourlyBorrowRate')
|
||
period = 3600000 if (hourlyBorrowRate is not None) else 86400000 # 1h or 1d
|
||
return {
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'rate': self.safe_number(info, 'interestRate', hourlyBorrowRate),
|
||
'period': period, # Daily
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': info,
|
||
}
|
||
|
||
def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]:
|
||
"""
|
||
fetch the interest owed by the user for borrowing currency for margin trading
|
||
|
||
https://bybit-exchange.github.io/docs/zh-TW/v5/spot-margin-normal/account-info
|
||
|
||
:param str code: unified currency code
|
||
:param str symbol: unified market symbol when fetch interest in isolated markets
|
||
:param number [since]: the earliest time in ms to fetch borrrow interest for
|
||
:param number [limit]: the maximum number of structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `borrow interest structures <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
response = self.privateGetV5SpotCrossMarginTradeAccount(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "ret_code": 0,
|
||
# "ret_msg": "",
|
||
# "ext_code": null,
|
||
# "ext_info": null,
|
||
# "result": {
|
||
# "status": "1",
|
||
# "riskRate": "0",
|
||
# "acctBalanceSum": "0.000486213817680857",
|
||
# "debtBalanceSum": "0",
|
||
# "loanAccountList": [
|
||
# {
|
||
# "tokenId": "BTC",
|
||
# "total": "0.00048621",
|
||
# "locked": "0",
|
||
# "loan": "0",
|
||
# "interest": "0",
|
||
# "free": "0.00048621"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
rows = self.safe_list(data, 'loanAccountList', [])
|
||
interest = self.parse_borrow_interests(rows, None)
|
||
return self.filter_by_currency_since_limit(interest, code, since, limit)
|
||
|
||
def fetch_borrow_rate_history(self, code: str, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
retrieves a history of a currencies borrow interest rate at specific time slots
|
||
|
||
https://bybit-exchange.github.io/docs/v5/spot-margin-uta/historical-interest
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: timestamp for the earliest borrow rate
|
||
:param int [limit]: the maximum number of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>` to retrieve
|
||
: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[]: an array of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
if since is None:
|
||
since = self.milliseconds() - 86400000 * 30 # last 30 days
|
||
request['startTime'] = since
|
||
endTime = self.safe_integer_2(params, 'until', 'endTime')
|
||
params = self.omit(params, ['until'])
|
||
if endTime is None:
|
||
endTime = since + 86400000 * 30 # since + 30 days
|
||
request['endTime'] = endTime
|
||
response = self.privateGetV5SpotMarginTradeInterestRateHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "timestamp": 1721469600000,
|
||
# "currency": "USDC",
|
||
# "hourlyBorrowRate": "0.000014621596",
|
||
# "vipLevel": "No VIP"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": "{}",
|
||
# "time": 1721899048991
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result')
|
||
rows = self.safe_list(data, 'list', [])
|
||
return self.parse_borrow_rate_history(rows, code, since, limit)
|
||
|
||
def parse_borrow_interest(self, info: dict, market: Market = None) -> BorrowInterest:
|
||
#
|
||
# {
|
||
# "tokenId": "BTC",
|
||
# "total": "0.00048621",
|
||
# "locked": "0",
|
||
# "loan": "0",
|
||
# "interest": "0",
|
||
# "free": "0.00048621"
|
||
# },
|
||
#
|
||
return {
|
||
'info': info,
|
||
'symbol': None,
|
||
'currency': self.safe_currency_code(self.safe_string(info, 'tokenId')),
|
||
'interest': self.safe_number(info, 'interest'),
|
||
'interestRate': None,
|
||
'amountBorrowed': self.safe_number(info, 'loan'),
|
||
'marginMode': 'cross',
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
}
|
||
|
||
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
||
"""
|
||
transfer currency internally between wallets on the same account
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/create-inter-transfer
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: amount to transfer
|
||
:param str fromAccount: account to transfer from
|
||
:param str toAccount: account to transfer to
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.transferId]: UUID, which is unique across the platform
|
||
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
self.load_markets()
|
||
transferId = self.safe_string(params, 'transferId', self.uuid())
|
||
accountTypes = self.safe_dict(self.options, 'accountsByType', {})
|
||
fromId = self.safe_string(accountTypes, fromAccount, fromAccount)
|
||
toId = self.safe_string(accountTypes, toAccount, toAccount)
|
||
currency = self.currency(code)
|
||
amountToPrecision = self.currency_to_precision(code, amount)
|
||
request: dict = {
|
||
'transferId': transferId,
|
||
'fromAccountType': fromId,
|
||
'toAccountType': toId,
|
||
'coin': currency['id'],
|
||
'amount': amountToPrecision,
|
||
}
|
||
response = self.privatePostV5AssetTransferInterTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "transferId": "4244af44-f3b0-4cf6-a743-b56560e987bc"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1666875857205
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(response, 'time')
|
||
transfer = self.safe_dict(response, 'result', {})
|
||
statusRaw = self.safe_string_n(response, ['retCode', 'retMsg'])
|
||
status = self.parse_transfer_status(statusRaw)
|
||
return self.extend(self.parse_transfer(transfer, currency), {
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'amount': self.parse_number(amountToPrecision),
|
||
'fromAccount': fromAccount,
|
||
'toAccount': toAccount,
|
||
'status': status,
|
||
})
|
||
|
||
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://bybit-exchange.github.io/docs/v5/asset/inter-transfer-list
|
||
|
||
: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 transfer 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 entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchTransfers', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchTransfers', code, since, limit, params, 'nextPageCursor', 'cursor', None, 50)
|
||
currency = None
|
||
request: dict = {}
|
||
if code is not None:
|
||
currency = self.safe_currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5AssetTransferQueryInterTransferList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "transferId": "selfTransfer_a1091cc7-9364-4b74-8de1-18f02c6f2d5c",
|
||
# "coin": "USDT",
|
||
# "amount": "5000",
|
||
# "fromAccountType": "SPOT",
|
||
# "toAccountType": "UNIFIED",
|
||
# "timestamp": "1667283263000",
|
||
# "status": "SUCCESS"
|
||
# }
|
||
# ],
|
||
# "nextPageCursor": "eyJtaW5JRCI6MTM1ODQ2OCwibWF4SUQiOjEzNTg0Njh9"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1670988271677
|
||
# }
|
||
#
|
||
data = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_transfers(data, currency, since, limit)
|
||
|
||
def borrow_cross_margin(self, code: str, amount: float, params={}):
|
||
"""
|
||
create a loan to borrow margin
|
||
|
||
https://bybit-exchange.github.io/docs/v5/spot-margin-normal/borrow
|
||
|
||
:param str code: unified currency code of the currency to borrow
|
||
:param float amount: the amount to borrow
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'qty': self.currency_to_precision(code, amount),
|
||
}
|
||
response = self.privatePostV5SpotCrossMarginTradeLoan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "transactId": "14143"
|
||
# },
|
||
# "retExtInfo": null,
|
||
# "time": 1662617848970
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
transaction = self.parse_margin_loan(result, currency)
|
||
return self.extend(transaction, {
|
||
'symbol': None,
|
||
'amount': amount,
|
||
})
|
||
|
||
def repay_cross_margin(self, code: str, amount, params={}):
|
||
"""
|
||
repay borrowed margin and interest
|
||
|
||
https://bybit-exchange.github.io/docs/v5/spot-margin-normal/repay
|
||
|
||
:param str code: unified currency code of the currency to repay
|
||
:param float amount: the amount to repay
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'qty': self.number_to_string(amount),
|
||
}
|
||
response = self.privatePostV5SpotCrossMarginTradeRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "repayId": "12128"
|
||
# },
|
||
# "retExtInfo": null,
|
||
# "time": 1662618298452
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
transaction = self.parse_margin_loan(result, currency)
|
||
return self.extend(transaction, {
|
||
'symbol': None,
|
||
'amount': amount,
|
||
})
|
||
|
||
def parse_margin_loan(self, info, currency: Currency = None) -> dict:
|
||
#
|
||
# borrowCrossMargin
|
||
#
|
||
# {
|
||
# "transactId": "14143"
|
||
# }
|
||
#
|
||
# repayCrossMargin
|
||
#
|
||
# {
|
||
# "repayId": "12128"
|
||
# }
|
||
#
|
||
return {
|
||
'id': self.safe_string_2(info, 'transactId', 'repayId'),
|
||
'currency': self.safe_string(currency, 'code'),
|
||
'amount': None,
|
||
'symbol': None,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'info': info,
|
||
}
|
||
|
||
def parse_transfer_status(self, status: Str) -> Str:
|
||
statuses: dict = {
|
||
'0': 'ok',
|
||
'OK': 'ok',
|
||
'SUCCESS': 'ok',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
||
#
|
||
# transfer
|
||
#
|
||
# {
|
||
# "transferId": "22c2bc11-ed5b-49a4-8647-c4e0f5f6f2b2"
|
||
# }
|
||
#
|
||
# fetchTransfers
|
||
#
|
||
# {
|
||
# "transferId": "e9c421c4-b010-4b16-abd6-106179f27702",
|
||
# "coin": "USDT",
|
||
# "amount": "8",
|
||
# "fromAccountType": "FUND",
|
||
# "toAccountType": "SPOT",
|
||
# "timestamp": "1666879426000",
|
||
# "status": "SUCCESS"
|
||
# }
|
||
#
|
||
currencyId = self.safe_string(transfer, 'coin')
|
||
timestamp = self.safe_integer(transfer, 'timestamp')
|
||
fromAccountId = self.safe_string(transfer, 'fromAccountType')
|
||
toAccountId = self.safe_string(transfer, 'toAccountType')
|
||
accountIds = self.safe_dict(self.options, 'accountsById', {})
|
||
fromAccount = self.safe_string(accountIds, fromAccountId, fromAccountId)
|
||
toAccount = self.safe_string(accountIds, toAccountId, toAccountId)
|
||
return {
|
||
'info': transfer,
|
||
'id': self.safe_string(transfer, 'transferId'),
|
||
'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.parse_transfer_status(self.safe_string(transfer, 'status')),
|
||
}
|
||
|
||
def fetch_derivatives_market_leverage_tiers(self, symbol: str, params={}) -> List[LeverageTier]:
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if market['linear']:
|
||
request['category'] = 'linear'
|
||
elif market['inverse']:
|
||
request['category'] = 'inverse'
|
||
response = self.publicGetV5MarketRiskLimit(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "category": "inverse",
|
||
# "list": [
|
||
# {
|
||
# "id": 1,
|
||
# "symbol": "BTCUSD",
|
||
# "riskLimitValue": "150",
|
||
# "maintenanceMargin": "0.5",
|
||
# "initialMargin": "1",
|
||
# "isLowestRisk": 1,
|
||
# "maxLeverage": "100.00"
|
||
# },
|
||
# ....
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672054488010
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result')
|
||
tiers = self.safe_list(result, 'list')
|
||
return self.parse_market_leverage_tiers(tiers, market)
|
||
|
||
def fetch_market_leverage_tiers(self, symbol: str, params={}) -> List[LeverageTier]:
|
||
"""
|
||
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes for a single market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/risk-limit
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `leverage tiers structure <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
market = None
|
||
market = self.market(symbol)
|
||
if market['spot'] or market['option']:
|
||
raise BadRequest(self.id + ' fetchMarketLeverageTiers() symbol does not support market ' + symbol)
|
||
request['symbol'] = market['id']
|
||
return self.fetch_derivatives_market_leverage_tiers(symbol, params)
|
||
|
||
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "makerFeeRate": 0.001,
|
||
# "takerFeeRate": 0.001
|
||
# }
|
||
#
|
||
marketId = self.safe_string(fee, 'symbol')
|
||
defaultType = market['type'] if (market is not None) else 'contract'
|
||
symbol = self.safe_symbol(marketId, market, None, defaultType)
|
||
return {
|
||
'info': fee,
|
||
'symbol': symbol,
|
||
'maker': self.safe_number(fee, 'makerFeeRate'),
|
||
'taker': self.safe_number(fee, 'takerFeeRate'),
|
||
'percentage': None,
|
||
'tierBased': None,
|
||
}
|
||
|
||
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
||
"""
|
||
fetch the trading fees for a market
|
||
|
||
https://bybit-exchange.github.io/docs/v5/account/fee-rate
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
category = None
|
||
category, params = self.get_bybit_type('fetchTradingFee', market, params)
|
||
request['category'] = category
|
||
response = self.privateGetV5AccountFeeRate(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "takerFeeRate": "0.0006",
|
||
# "makerFeeRate": "0.0001"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1676360412576
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
fees = self.safe_list(result, 'list', [])
|
||
first = self.safe_dict(fees, 0, {})
|
||
return self.parse_trading_fee(first, market)
|
||
|
||
def fetch_trading_fees(self, params={}) -> TradingFees:
|
||
"""
|
||
fetch the trading fees for multiple markets
|
||
|
||
https://bybit-exchange.github.io/docs/v5/account/fee-rate
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
type = None
|
||
type, params = self.handle_option_and_params(params, 'fetchTradingFees', 'type', 'future')
|
||
if type == 'spot':
|
||
raise NotSupported(self.id + ' fetchTradingFees() is not supported for spot market')
|
||
response = self.privateGetV5AccountFeeRate(params)
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "takerFeeRate": "0.0006",
|
||
# "makerFeeRate": "0.0001"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1676360412576
|
||
# }
|
||
#
|
||
fees = self.safe_dict(response, 'result', {})
|
||
fees = self.safe_list(fees, 'list', [])
|
||
result: dict = {}
|
||
for i in range(0, len(fees)):
|
||
fee = self.parse_trading_fee(fees[i])
|
||
symbol = fee['symbol']
|
||
result[symbol] = fee
|
||
return result
|
||
|
||
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None) -> Any:
|
||
#
|
||
# {
|
||
# "name": "BTC",
|
||
# "coin": "BTC",
|
||
# "remainAmount": "150",
|
||
# "chains": [
|
||
# {
|
||
# "chainType": "BTC",
|
||
# "confirmation": "10000",
|
||
# "withdrawFee": "0.0005",
|
||
# "depositMin": "0.0005",
|
||
# "withdrawMin": "0.001",
|
||
# "chain": "BTC",
|
||
# "chainDeposit": "1",
|
||
# "chainWithdraw": "1",
|
||
# "minAccuracy": "8"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
chains = self.safe_list(fee, 'chains', [])
|
||
chainsLength = len(chains)
|
||
result: dict = {
|
||
'info': fee,
|
||
'withdraw': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'networks': {},
|
||
}
|
||
if chainsLength != 0:
|
||
for i in range(0, chainsLength):
|
||
chain = chains[i]
|
||
networkId = self.safe_string(chain, 'chain')
|
||
currencyCode = self.safe_string(currency, 'code')
|
||
networkCode = self.network_id_to_code(networkId, currencyCode)
|
||
result['networks'][networkCode] = {
|
||
'deposit': {'fee': None, 'percentage': None},
|
||
'withdraw': {'fee': self.safe_number(chain, 'withdrawFee'), 'percentage': False},
|
||
}
|
||
if chainsLength == 1:
|
||
result['withdraw']['fee'] = self.safe_number(chain, 'withdrawFee')
|
||
result['withdraw']['percentage'] = False
|
||
return result
|
||
|
||
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
fetch deposit and withdraw fees
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/coin-info
|
||
|
||
:param str[] codes: list of unified currency codes
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
self.check_required_credentials()
|
||
self.load_markets()
|
||
response = self.privateGetV5AssetCoinQueryInfo(params)
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "",
|
||
# "result": {
|
||
# "rows": [
|
||
# {
|
||
# "name": "BTC",
|
||
# "coin": "BTC",
|
||
# "remainAmount": "150",
|
||
# "chains": [
|
||
# {
|
||
# "chainType": "BTC",
|
||
# "confirmation": "10000",
|
||
# "withdrawFee": "0.0005",
|
||
# "depositMin": "0.0005",
|
||
# "withdrawMin": "0.001",
|
||
# "chain": "BTC",
|
||
# "chainDeposit": "1",
|
||
# "chainWithdraw": "1",
|
||
# "minAccuracy": "8"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672194582264
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
rows = self.safe_list(data, 'rows', [])
|
||
return self.parse_deposit_withdraw_fees(rows, codes, 'coin')
|
||
|
||
def fetch_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical settlement records
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/delivery-price
|
||
|
||
:param str symbol: unified market symbol of the settlement history
|
||
:param int [since]: timestamp in ms
|
||
:param int [limit]: number of records
|
||
:param dict [params]: exchange specific params
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:returns dict[]: a list of [settlement history objects]
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchSettlementHistory', market, params)
|
||
if type == 'spot':
|
||
raise NotSupported(self.id + ' fetchSettlementHistory() is not supported for spot market')
|
||
request['category'] = type
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.publicGetV5MarketDeliveryPrice(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "nextPageCursor": "0%2C3",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000"
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1689043527231
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
settlements = self.parse_settlements(data, market)
|
||
sorted = self.sort_by(settlements, 'timestamp')
|
||
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
|
||
|
||
def fetch_my_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical settlement records of the user
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/delivery
|
||
|
||
:param str symbol: unified market symbol of the settlement history
|
||
:param int [since]: timestamp in ms
|
||
:param int [limit]: number of records
|
||
:param dict [params]: exchange specific params
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:returns dict[]: a list of [settlement history objects]
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchMySettlementHistory', market, params)
|
||
if type == 'spot':
|
||
raise NotSupported(self.id + ' fetchMySettlementHistory() is not supported for spot market')
|
||
request['category'] = type
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.privateGetV5AssetDeliveryRecord(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "success",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "nextPageCursor": "0%2C3",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000",
|
||
# "side": "Buy",
|
||
# "strike": "20",
|
||
# "fee": "0.00000000",
|
||
# "position": "0.01",
|
||
# "deliveryRpl": "3.5"
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1689043527231
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
settlements = self.parse_settlements(data, market)
|
||
sorted = self.sort_by(settlements, 'timestamp')
|
||
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
|
||
|
||
def parse_settlement(self, settlement, market):
|
||
#
|
||
# fetchSettlementHistory
|
||
#
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000"
|
||
# }
|
||
#
|
||
# fetchMySettlementHistory
|
||
#
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000",
|
||
# "side": "Buy",
|
||
# "strike": "20",
|
||
# "fee": "0.00000000",
|
||
# "position": "0.01",
|
||
# "deliveryRpl": "3.5"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(settlement, 'deliveryTime')
|
||
marketId = self.safe_string(settlement, 'symbol')
|
||
return {
|
||
'info': settlement,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'price': self.safe_number(settlement, 'deliveryPrice'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def parse_settlements(self, settlements, market):
|
||
#
|
||
# fetchSettlementHistory
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000"
|
||
# }
|
||
# ]
|
||
#
|
||
# fetchMySettlementHistory
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "SOL-27JUN23-20-C",
|
||
# "deliveryPrice": "16.62258889",
|
||
# "deliveryTime": "1687852800000",
|
||
# "side": "Buy",
|
||
# "strike": "20",
|
||
# "fee": "0.00000000",
|
||
# "position": "0.01",
|
||
# "deliveryRpl": "3.5"
|
||
# }
|
||
# ]
|
||
#
|
||
result = []
|
||
for i in range(0, len(settlements)):
|
||
result.append(self.parse_settlement(settlements[i], market))
|
||
return result
|
||
|
||
def fetch_volatility_history(self, code: str, params={}):
|
||
"""
|
||
fetch the historical volatility of an option market based on an underlying asset
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/iv
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.period]: the period in days to fetch the volatility for: 7,14,21,30,60,90,180,270
|
||
:returns dict[]: a list of `volatility history objects <https://docs.ccxt.com/#/?id=volatility-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'category': 'option',
|
||
'baseCoin': currency['id'],
|
||
}
|
||
response = self.publicGetV5MarketHistoricalVolatility(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "SUCCESS",
|
||
# "category": "option",
|
||
# "result": [
|
||
# {
|
||
# "period": 7,
|
||
# "value": "0.23854072",
|
||
# "time": "1690574400000"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
volatility = self.safe_list(response, 'result', [])
|
||
return self.parse_volatility_history(volatility)
|
||
|
||
def parse_volatility_history(self, volatility):
|
||
#
|
||
# {
|
||
# "period": 7,
|
||
# "value": "0.23854072",
|
||
# "time": "1690574400000"
|
||
# }
|
||
#
|
||
result = []
|
||
for i in range(0, len(volatility)):
|
||
entry = volatility[i]
|
||
timestamp = self.safe_integer(entry, 'time')
|
||
result.append({
|
||
'info': volatility,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'volatility': self.safe_number(entry, 'value'),
|
||
})
|
||
return result
|
||
|
||
def fetch_greeks(self, symbol: str, params={}) -> Greeks:
|
||
"""
|
||
fetches an option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
||
|
||
https://bybit-exchange.github.io/docs/api-explorer/v5/market/tickers
|
||
|
||
:param str symbol: unified symbol of the market to fetch greeks for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'category': 'option',
|
||
}
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "SUCCESS",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-26JAN24-39000-C",
|
||
# "bid1Price": "3205",
|
||
# "bid1Size": "7.1",
|
||
# "bid1Iv": "0.5478",
|
||
# "ask1Price": "3315",
|
||
# "ask1Size": "1.98",
|
||
# "ask1Iv": "0.5638",
|
||
# "lastPrice": "3230",
|
||
# "highPrice24h": "3255",
|
||
# "lowPrice24h": "3200",
|
||
# "markPrice": "3273.02263032",
|
||
# "indexPrice": "36790.96",
|
||
# "markIv": "0.5577",
|
||
# "underlyingPrice": "37649.67254894",
|
||
# "openInterest": "19.67",
|
||
# "turnover24h": "170140.33875912",
|
||
# "volume24h": "4.56",
|
||
# "totalVolume": "22",
|
||
# "totalTurnover": "789305",
|
||
# "delta": "0.49640971",
|
||
# "gamma": "0.00004131",
|
||
# "vega": "69.08651675",
|
||
# "theta": "-24.9443226",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0.18532111"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1699584008326
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(response, 'time')
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
greeks = self.parse_greeks(data[0], market)
|
||
return self.extend(greeks, {
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
})
|
||
|
||
def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
|
||
"""
|
||
fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
||
|
||
https://bybit-exchange.github.io/docs/api-explorer/v5/market/tickers
|
||
|
||
:param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.baseCoin]: the baseCoin of the symbol, default is BTC
|
||
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
baseCoin = self.safe_string(params, 'baseCoin', 'BTC')
|
||
request: dict = {
|
||
'category': 'option',
|
||
'baseCoin': baseCoin,
|
||
}
|
||
market = None
|
||
if symbols is not None:
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength == 1:
|
||
market = self.market(symbols[0])
|
||
request['symbol'] = market['id']
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "SUCCESS",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-26JAN24-39000-C",
|
||
# "bid1Price": "3205",
|
||
# "bid1Size": "7.1",
|
||
# "bid1Iv": "0.5478",
|
||
# "ask1Price": "3315",
|
||
# "ask1Size": "1.98",
|
||
# "ask1Iv": "0.5638",
|
||
# "lastPrice": "3230",
|
||
# "highPrice24h": "3255",
|
||
# "lowPrice24h": "3200",
|
||
# "markPrice": "3273.02263032",
|
||
# "indexPrice": "36790.96",
|
||
# "markIv": "0.5577",
|
||
# "underlyingPrice": "37649.67254894",
|
||
# "openInterest": "19.67",
|
||
# "turnover24h": "170140.33875912",
|
||
# "volume24h": "4.56",
|
||
# "totalVolume": "22",
|
||
# "totalTurnover": "789305",
|
||
# "delta": "0.49640971",
|
||
# "gamma": "0.00004131",
|
||
# "vega": "69.08651675",
|
||
# "theta": "-24.9443226",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0.18532111"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1699584008326
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
return self.parse_all_greeks(data, symbols)
|
||
|
||
def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
|
||
#
|
||
# {
|
||
# "symbol": "BTC-26JAN24-39000-C",
|
||
# "bid1Price": "3205",
|
||
# "bid1Size": "7.1",
|
||
# "bid1Iv": "0.5478",
|
||
# "ask1Price": "3315",
|
||
# "ask1Size": "1.98",
|
||
# "ask1Iv": "0.5638",
|
||
# "lastPrice": "3230",
|
||
# "highPrice24h": "3255",
|
||
# "lowPrice24h": "3200",
|
||
# "markPrice": "3273.02263032",
|
||
# "indexPrice": "36790.96",
|
||
# "markIv": "0.5577",
|
||
# "underlyingPrice": "37649.67254894",
|
||
# "openInterest": "19.67",
|
||
# "turnover24h": "170140.33875912",
|
||
# "volume24h": "4.56",
|
||
# "totalVolume": "22",
|
||
# "totalTurnover": "789305",
|
||
# "delta": "0.49640971",
|
||
# "gamma": "0.00004131",
|
||
# "vega": "69.08651675",
|
||
# "theta": "-24.9443226",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0.18532111"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(greeks, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market)
|
||
return {
|
||
'symbol': symbol,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'delta': self.safe_number(greeks, 'delta'),
|
||
'gamma': self.safe_number(greeks, 'gamma'),
|
||
'theta': self.safe_number(greeks, 'theta'),
|
||
'vega': self.safe_number(greeks, 'vega'),
|
||
'rho': None,
|
||
'bidSize': self.safe_number(greeks, 'bid1Size'),
|
||
'askSize': self.safe_number(greeks, 'ask1Size'),
|
||
'bidImpliedVolatility': self.safe_number(greeks, 'bid1Iv'),
|
||
'askImpliedVolatility': self.safe_number(greeks, 'ask1Iv'),
|
||
'markImpliedVolatility': self.safe_number(greeks, 'markIv'),
|
||
'bidPrice': self.safe_number(greeks, 'bid1Price'),
|
||
'askPrice': self.safe_number(greeks, 'ask1Price'),
|
||
'markPrice': self.safe_number(greeks, 'markPrice'),
|
||
'lastPrice': self.safe_number(greeks, 'lastPrice'),
|
||
'underlyingPrice': self.safe_number(greeks, 'underlyingPrice'),
|
||
'info': greeks,
|
||
}
|
||
|
||
def fetch_my_liquidations(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
|
||
"""
|
||
retrieves the users liquidated positions
|
||
|
||
https://bybit-exchange.github.io/docs/api-explorer/v5/position/execution
|
||
|
||
:param str [symbol]: unified CCXT market symbol
|
||
:param int [since]: the earliest time in ms to fetch liquidations for
|
||
:param int [limit]: the maximum number of liquidation structures to retrieve
|
||
:param dict [params]: exchange specific parameters for the exchange API endpoint
|
||
:param str [params.type]: market type, ['swap', 'option', 'spot']
|
||
:param str [params.subType]: market subType, ['linear', 'inverse']
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchMyLiquidations', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchMyLiquidations', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 100)
|
||
request: dict = {
|
||
'execType': 'BustTrade',
|
||
}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchMyLiquidations', market, params)
|
||
request['category'] = type
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5ExecutionList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "nextPageCursor": "132766%3A2%2C132766%3A2",
|
||
# "category": "linear",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "ETHPERP",
|
||
# "orderType": "Market",
|
||
# "underlyingPrice": "",
|
||
# "orderLinkId": "",
|
||
# "side": "Buy",
|
||
# "indexPrice": "",
|
||
# "orderId": "8c065341-7b52-4ca9-ac2c-37e31ac55c94",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "leavesQty": "0",
|
||
# "execTime": "1672282722429",
|
||
# "isMaker": False,
|
||
# "execFee": "0.071409",
|
||
# "feeRate": "0.0006",
|
||
# "execId": "e0cbe81d-0f18-5866-9415-cf319b5dab3b",
|
||
# "tradeIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "1183.54",
|
||
# "execPrice": "1190.15",
|
||
# "markIv": "",
|
||
# "orderQty": "0.1",
|
||
# "orderPrice": "1236.9",
|
||
# "execValue": "119.015",
|
||
# "execType": "Trade",
|
||
# "execQty": "0.1"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1672283754510
|
||
# }
|
||
#
|
||
liquidations = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_liquidations(liquidations, market, since, limit)
|
||
|
||
def parse_liquidation(self, liquidation, market: Market = None) -> Liquidation:
|
||
#
|
||
# {
|
||
# "symbol": "ETHPERP",
|
||
# "orderType": "Market",
|
||
# "underlyingPrice": "",
|
||
# "orderLinkId": "",
|
||
# "side": "Buy",
|
||
# "indexPrice": "",
|
||
# "orderId": "8c065341-7b52-4ca9-ac2c-37e31ac55c94",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "leavesQty": "0",
|
||
# "execTime": "1672282722429",
|
||
# "isMaker": False,
|
||
# "execFee": "0.071409",
|
||
# "feeRate": "0.0006",
|
||
# "execId": "e0cbe81d-0f18-5866-9415-cf319b5dab3b",
|
||
# "tradeIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "1183.54",
|
||
# "execPrice": "1190.15",
|
||
# "markIv": "",
|
||
# "orderQty": "0.1",
|
||
# "orderPrice": "1236.9",
|
||
# "execValue": "119.015",
|
||
# "execType": "Trade",
|
||
# "execQty": "0.1"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(liquidation, 'symbol')
|
||
timestamp = self.safe_integer(liquidation, 'execTime')
|
||
contractsString = self.safe_string(liquidation, 'execQty')
|
||
contractSizeString = self.safe_string(market, 'contractSize')
|
||
priceString = self.safe_string(liquidation, 'execPrice')
|
||
baseValueString = Precise.string_mul(contractsString, contractSizeString)
|
||
quoteValueString = Precise.string_mul(baseValueString, priceString)
|
||
return self.safe_liquidation({
|
||
'info': liquidation,
|
||
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
|
||
'contracts': self.parse_number(contractsString),
|
||
'contractSize': self.parse_number(contractSizeString),
|
||
'price': self.parse_number(priceString),
|
||
'baseValue': self.parse_number(baseValueString),
|
||
'quoteValue': self.parse_number(quoteValueString),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
})
|
||
|
||
def get_leverage_tiers_paginated(self, symbol: Str = None, params={}):
|
||
self.load_markets()
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'getLeverageTiersPaginated', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('getLeverageTiersPaginated', symbol, None, None, params, 'nextPageCursor', 'cursor', None, 100)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('getLeverageTiersPaginated', market, params, 'linear')
|
||
request: dict = {
|
||
'category': subType,
|
||
}
|
||
response = self.publicGetV5MarketRiskLimit(self.extend(request, params))
|
||
result = self.add_pagination_cursor_to_result(response)
|
||
first = self.safe_dict(result, 0)
|
||
total = len(result)
|
||
lastIndex = total - 1
|
||
last = self.safe_dict(result, lastIndex)
|
||
cursorValue = self.safe_string(first, 'nextPageCursor')
|
||
last['info'] = {
|
||
'nextPageCursor': cursorValue,
|
||
}
|
||
result[lastIndex] = last
|
||
return result
|
||
|
||
def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
|
||
"""
|
||
retrieve information on the maximum leverage, for different trade sizes
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/risk-limit
|
||
|
||
:param str[] [symbols]: a list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: market subType, ['linear', 'inverse'], default is 'linear'
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
symbol = None
|
||
if symbols is not None:
|
||
market = self.market(symbols[0])
|
||
if market['spot']:
|
||
raise NotSupported(self.id + ' fetchLeverageTiers() is not supported for spot market')
|
||
symbol = market['symbol']
|
||
data = self.get_leverage_tiers_paginated(symbol, self.extend({'paginate': True, 'paginationCalls': 50}, params))
|
||
symbols = self.market_symbols(symbols)
|
||
return self.parse_leverage_tiers(data, symbols, 'symbol')
|
||
|
||
def parse_leverage_tiers(self, response, symbols: Strings = None, marketIdKey=None) -> LeverageTiers:
|
||
#
|
||
# [
|
||
# {
|
||
# "id": 1,
|
||
# "symbol": "BTCUSD",
|
||
# "riskLimitValue": "150",
|
||
# "maintenanceMargin": "0.5",
|
||
# "initialMargin": "1",
|
||
# "isLowestRisk": 1,
|
||
# "maxLeverage": "100.00"
|
||
# }
|
||
# ]
|
||
#
|
||
tiers: dict = {}
|
||
marketIds = self.market_ids(symbols)
|
||
filteredResults = self.filter_by_array(response, marketIdKey, marketIds, False)
|
||
grouped = self.group_by(filteredResults, marketIdKey)
|
||
keys = list(grouped.keys())
|
||
for i in range(0, len(keys)):
|
||
marketId = keys[i]
|
||
entry = grouped[marketId]
|
||
for j in range(0, len(entry)):
|
||
id = self.safe_integer(entry[j], 'id')
|
||
entry[j]['id'] = id
|
||
market = self.safe_market(marketId, None, None, 'contract')
|
||
symbol = market['symbol']
|
||
tiers[symbol] = self.parse_market_leverage_tiers(self.sort_by(entry, 'id'), market)
|
||
return tiers
|
||
|
||
def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
|
||
#
|
||
# [
|
||
# {
|
||
# "id": 1,
|
||
# "symbol": "BTCUSD",
|
||
# "riskLimitValue": "150",
|
||
# "maintenanceMargin": "0.5",
|
||
# "initialMargin": "1",
|
||
# "isLowestRisk": 1,
|
||
# "maxLeverage": "100.00"
|
||
# }
|
||
# ]
|
||
#
|
||
tiers = []
|
||
for i in range(0, len(info)):
|
||
tier = info[i]
|
||
marketId = self.safe_string(info, 'symbol')
|
||
market = self.safe_market(marketId)
|
||
minNotional = self.parse_number('0')
|
||
if i != 0:
|
||
minNotional = self.safe_number(info[i - 1], 'riskLimitValue')
|
||
tiers.append({
|
||
'tier': self.safe_integer(tier, 'id'),
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'currency': market['settle'],
|
||
'minNotional': minNotional,
|
||
'maxNotional': self.safe_number(tier, 'riskLimitValue'),
|
||
'maintenanceMarginRate': self.safe_number(tier, 'maintenanceMargin'),
|
||
'maxLeverage': self.safe_number(tier, 'maxLeverage'),
|
||
'info': tier,
|
||
})
|
||
return tiers
|
||
|
||
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[FundingHistory]:
|
||
"""
|
||
fetch the history of funding payments paid and received on self account
|
||
|
||
https://bybit-exchange.github.io/docs/api-explorer/v5/position/execution
|
||
|
||
:param str [symbol]: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch funding history for
|
||
:param int [limit]: the maximum number of funding history structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_cursor('fetchFundingHistory', symbol, since, limit, params, 'nextPageCursor', 'cursor', None, 100)
|
||
request: dict = {
|
||
'execType': 'Funding',
|
||
}
|
||
market: Market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchFundingHistory', market, params)
|
||
request['category'] = type
|
||
if symbol is not None:
|
||
request['symbol'] = market['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['size'] = limit
|
||
else:
|
||
request['size'] = 100
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = self.privateGetV5ExecutionList(self.extend(request, params))
|
||
fundings = self.add_pagination_cursor_to_result(response)
|
||
return self.parse_incomes(fundings, market, since, limit)
|
||
|
||
def parse_income(self, income, market: Market = None) -> object:
|
||
#
|
||
# {
|
||
# "symbol": "XMRUSDT",
|
||
# "orderType": "UNKNOWN",
|
||
# "underlyingPrice": "",
|
||
# "orderLinkId": "",
|
||
# "orderId": "a11e5fe2-1dbf-4bab-a9b2-af80a14efc5d",
|
||
# "stopOrderType": "UNKNOWN",
|
||
# "execTime": "1710950400000",
|
||
# "feeCurrency": "",
|
||
# "createType": "",
|
||
# "feeRate": "-0.000761",
|
||
# "tradeIv": "",
|
||
# "blockTradeId": "",
|
||
# "markPrice": "136.79",
|
||
# "execPrice": "137.11",
|
||
# "markIv": "",
|
||
# "orderQty": "0",
|
||
# "orderPrice": "0",
|
||
# "execValue": "134.3678",
|
||
# "closedSize": "0",
|
||
# "execType": "Funding",
|
||
# "seq": "28097658790",
|
||
# "side": "Sell",
|
||
# "indexPrice": "",
|
||
# "leavesQty": "0",
|
||
# "isMaker": False,
|
||
# "execFee": "-0.10232512",
|
||
# "execId": "8d1ef156-4ec6-4445-9a6c-1c0c24dbd046",
|
||
# "marketUnit": "",
|
||
# "execQty": "0.98",
|
||
# "nextPageCursor": "5774437%3A0%2C5771289%3A0"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(income, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'contract')
|
||
code = 'USDT'
|
||
if market['inverse']:
|
||
code = market['quote']
|
||
timestamp = self.safe_integer(income, 'execTime')
|
||
return {
|
||
'info': income,
|
||
'symbol': self.safe_symbol(marketId, market, '-', 'swap'),
|
||
'code': code,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'id': self.safe_string(income, 'execId'),
|
||
'amount': self.safe_number(income, 'execFee'),
|
||
'rate': self.safe_number(income, 'feeRate'),
|
||
}
|
||
|
||
def fetch_option(self, symbol: str, params={}) -> Option:
|
||
"""
|
||
fetches option data that is commonly found in an option chain
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `option chain structure <https://docs.ccxt.com/#/?id=option-chain-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'category': 'option',
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "SUCCESS",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-27DEC24-55000-P",
|
||
# "bid1Price": "0",
|
||
# "bid1Size": "0",
|
||
# "bid1Iv": "0",
|
||
# "ask1Price": "0",
|
||
# "ask1Size": "0",
|
||
# "ask1Iv": "0",
|
||
# "lastPrice": "10980",
|
||
# "highPrice24h": "0",
|
||
# "lowPrice24h": "0",
|
||
# "markPrice": "11814.66756236",
|
||
# "indexPrice": "63838.92",
|
||
# "markIv": "0.8866",
|
||
# "underlyingPrice": "71690.55303594",
|
||
# "openInterest": "0.01",
|
||
# "turnover24h": "0",
|
||
# "volume24h": "0",
|
||
# "totalVolume": "2",
|
||
# "totalTurnover": "78719",
|
||
# "delta": "-0.23284954",
|
||
# "gamma": "0.0000055",
|
||
# "vega": "191.70757975",
|
||
# "theta": "-30.43617927",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1711162003672
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
resultList = self.safe_list(result, 'list', [])
|
||
chain = self.safe_dict(resultList, 0, {})
|
||
return self.parse_option(chain, None, market)
|
||
|
||
def fetch_option_chain(self, code: str, params={}) -> OptionChain:
|
||
"""
|
||
fetches data for an underlying asset that is commonly found in an option chain
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/tickers
|
||
|
||
:param str code: base currency to fetch an option chain for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a list of `option chain structures <https://docs.ccxt.com/#/?id=option-chain-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'category': 'option',
|
||
'baseCoin': currency['id'],
|
||
}
|
||
response = self.publicGetV5MarketTickers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "SUCCESS",
|
||
# "result": {
|
||
# "category": "option",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-27DEC24-55000-P",
|
||
# "bid1Price": "0",
|
||
# "bid1Size": "0",
|
||
# "bid1Iv": "0",
|
||
# "ask1Price": "0",
|
||
# "ask1Size": "0",
|
||
# "ask1Iv": "0",
|
||
# "lastPrice": "10980",
|
||
# "highPrice24h": "0",
|
||
# "lowPrice24h": "0",
|
||
# "markPrice": "11814.66756236",
|
||
# "indexPrice": "63838.92",
|
||
# "markIv": "0.8866",
|
||
# "underlyingPrice": "71690.55303594",
|
||
# "openInterest": "0.01",
|
||
# "turnover24h": "0",
|
||
# "volume24h": "0",
|
||
# "totalVolume": "2",
|
||
# "totalTurnover": "78719",
|
||
# "delta": "-0.23284954",
|
||
# "gamma": "0.0000055",
|
||
# "vega": "191.70757975",
|
||
# "theta": "-30.43617927",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0"
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1711162003672
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
resultList = self.safe_list(result, 'list', [])
|
||
return self.parse_option_chain(resultList, None, 'symbol')
|
||
|
||
def parse_option(self, chain: dict, currency: Currency = None, market: Market = None) -> Option:
|
||
#
|
||
# {
|
||
# "symbol": "BTC-27DEC24-55000-P",
|
||
# "bid1Price": "0",
|
||
# "bid1Size": "0",
|
||
# "bid1Iv": "0",
|
||
# "ask1Price": "0",
|
||
# "ask1Size": "0",
|
||
# "ask1Iv": "0",
|
||
# "lastPrice": "10980",
|
||
# "highPrice24h": "0",
|
||
# "lowPrice24h": "0",
|
||
# "markPrice": "11814.66756236",
|
||
# "indexPrice": "63838.92",
|
||
# "markIv": "0.8866",
|
||
# "underlyingPrice": "71690.55303594",
|
||
# "openInterest": "0.01",
|
||
# "turnover24h": "0",
|
||
# "volume24h": "0",
|
||
# "totalVolume": "2",
|
||
# "totalTurnover": "78719",
|
||
# "delta": "-0.23284954",
|
||
# "gamma": "0.0000055",
|
||
# "vega": "191.70757975",
|
||
# "theta": "-30.43617927",
|
||
# "predictedDeliveryPrice": "0",
|
||
# "change24h": "0"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(chain, 'symbol')
|
||
market = self.safe_market(marketId, market)
|
||
return {
|
||
'info': chain,
|
||
'currency': None,
|
||
'symbol': market['symbol'],
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'impliedVolatility': self.safe_number(chain, 'markIv'),
|
||
'openInterest': self.safe_number(chain, 'openInterest'),
|
||
'bidPrice': self.safe_number(chain, 'bid1Price'),
|
||
'askPrice': self.safe_number(chain, 'ask1Price'),
|
||
'midPrice': None,
|
||
'markPrice': self.safe_number(chain, 'markPrice'),
|
||
'lastPrice': self.safe_number(chain, 'lastPrice'),
|
||
'underlyingPrice': self.safe_number(chain, 'underlyingPrice'),
|
||
'change': self.safe_number(chain, 'change24h'),
|
||
'percentage': None,
|
||
'baseVolume': self.safe_number(chain, 'totalVolume'),
|
||
'quoteVolume': None,
|
||
}
|
||
|
||
def fetch_positions_history(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
||
"""
|
||
fetches historical positions
|
||
|
||
https://bybit-exchange.github.io/docs/v5/position/close-pnl
|
||
|
||
: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=50, max=100
|
||
: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.subType]: 'linear' or 'inverse'
|
||
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
subType = None
|
||
symbolsLength = 0
|
||
if symbols is not None:
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength > 0:
|
||
market = self.market(symbols[0])
|
||
until = self.safe_integer(params, 'until')
|
||
subType, params = self.handle_sub_type_and_params('fetchPositionsHistory', market, params, 'linear')
|
||
params = self.omit(params, 'until')
|
||
request: dict = {
|
||
'category': subType,
|
||
}
|
||
if (symbols is not None) and (symbolsLength == 1):
|
||
request['symbol'] = market['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
response = self.privateGetV5PositionClosedPnl(self.extend(request, params))
|
||
#
|
||
# {
|
||
# retCode: '0',
|
||
# retMsg: 'OK',
|
||
# result: {
|
||
# nextPageCursor: '071749f3-a9fa-427b-b5ca-27b2f52b81de%3A1712717265566520788%2C071749f3-a9fa-427b-b5ca-27b2f52b81de%3A1712717265566520788',
|
||
# category: 'linear',
|
||
# list: [
|
||
# {
|
||
# symbol: 'XRPUSDT',
|
||
# orderType: 'Market',
|
||
# leverage: '10',
|
||
# updatedTime: '1712717265572',
|
||
# side: 'Sell',
|
||
# orderId: '071749f3-a9fa-427b-b5ca-27b2f52b81de',
|
||
# closedPnl: '-0.00049568',
|
||
# avgEntryPrice: '0.6045',
|
||
# qty: '3',
|
||
# cumEntryValue: '1.8135',
|
||
# createdTime: '1712717265566',
|
||
# orderPrice: '0.5744',
|
||
# closedSize: '3',
|
||
# avgExitPrice: '0.605',
|
||
# execType: 'Trade',
|
||
# fillCount: '1',
|
||
# cumExitValue: '1.815'
|
||
# }
|
||
# ]
|
||
# },
|
||
# retExtInfo: {},
|
||
# time: '1712717286073'
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result')
|
||
rawPositions = self.safe_list(result, 'list')
|
||
positions = self.parse_positions(rawPositions, symbols, params)
|
||
return self.filter_by_since_limit(positions, since, limit)
|
||
|
||
def fetch_convert_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies that can be converted
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/convert/convert-coin-list
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.accountType]: eb_convert_uta, eb_convert_spot, eb_convert_funding, eb_convert_inverse, or eb_convert_contract
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
self.load_markets()
|
||
accountType = None
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
accountTypeDefault = 'eb_convert_uta' if isUnifiedAccount else 'eb_convert_spot'
|
||
accountType, params = self.handle_option_and_params(params, 'fetchConvertCurrencies', 'accountType', accountTypeDefault)
|
||
request: dict = {
|
||
'accountType': accountType,
|
||
}
|
||
response = self.privateGetV5AssetExchangeQueryCoinList(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "ok",
|
||
# "result": {
|
||
# "coins": [
|
||
# {
|
||
# "coin": "MATIC",
|
||
# "fullName": "MATIC",
|
||
# "icon": "https://s1.bycsi.com/app/assets/token/0552ae79c535c3095fa18f7b377dd2e9.svg",
|
||
# "iconNight": "https://t1.bycsi.com/app/assets/token/f59301aef2d6ac2165c4c4603e672fb4.svg",
|
||
# "accuracyLength": 8,
|
||
# "coinType": "crypto",
|
||
# "balance": "0",
|
||
# "uBalance": "0",
|
||
# "timePeriod": 0,
|
||
# "singleFromMinLimit": "1.1",
|
||
# "singleFromMaxLimit": "20001",
|
||
# "singleToMinLimit": "0",
|
||
# "singleToMaxLimit": "0",
|
||
# "dailyFromMinLimit": "0",
|
||
# "dailyFromMaxLimit": "0",
|
||
# "dailyToMinLimit": "0",
|
||
# "dailyToMaxLimit": "0",
|
||
# "disableFrom": False,
|
||
# "disableTo": False
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1727256416250
|
||
# }
|
||
#
|
||
result: dict = {}
|
||
data = self.safe_dict(response, 'result', {})
|
||
coins = self.safe_list(data, 'coins', [])
|
||
for i in range(0, len(coins)):
|
||
entry = coins[i]
|
||
id = self.safe_string(entry, 'coin')
|
||
disableFrom = self.safe_bool(entry, 'disableFrom')
|
||
disableTo = self.safe_bool(entry, 'disableTo')
|
||
inactive = (disableFrom or disableTo)
|
||
code = self.safe_currency_code(id)
|
||
result[code] = {
|
||
'info': entry,
|
||
'id': id,
|
||
'code': code,
|
||
'networks': None,
|
||
'type': self.safe_string(entry, 'coinType'),
|
||
'name': self.safe_string(entry, 'fullName'),
|
||
'active': not inactive,
|
||
'deposit': None,
|
||
'withdraw': self.safe_number(entry, 'balance'),
|
||
'fee': None,
|
||
'precision': None,
|
||
'limits': {
|
||
'amount': {
|
||
'min': self.safe_number(entry, 'singleFromMinLimit'),
|
||
'max': self.safe_number(entry, 'singleFromMaxLimit'),
|
||
},
|
||
'withdraw': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
}
|
||
return result
|
||
|
||
def fetch_convert_quote(self, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
|
||
"""
|
||
fetch a quote for converting from one currency to another
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/convert/apply-quote
|
||
|
||
:param str fromCode: the currency that you want to sell and convert from
|
||
:param str toCode: the currency that you want to buy and convert into
|
||
:param float [amount]: how much you want to trade in units of the from currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.accountType]: eb_convert_uta, eb_convert_spot, eb_convert_funding, eb_convert_inverse, or eb_convert_contract
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
accountType = None
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
accountTypeDefault = 'eb_convert_uta' if isUnifiedAccount else 'eb_convert_spot'
|
||
accountType, params = self.handle_option_and_params(params, 'fetchConvertQuote', 'accountType', accountTypeDefault)
|
||
request: dict = {
|
||
'fromCoin': fromCode,
|
||
'toCoin': toCode,
|
||
'requestAmount': self.number_to_string(amount),
|
||
'requestCoin': fromCode,
|
||
'accountType': accountType,
|
||
}
|
||
response = self.privatePostV5AssetExchangeQuoteApply(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "ok",
|
||
# "result": {
|
||
# "quoteTxId": "1010020692439481682687668224",
|
||
# "exchangeRate": "0.000015330836780000",
|
||
# "fromCoin": "USDT",
|
||
# "fromCoinType": "crypto",
|
||
# "toCoin": "BTC",
|
||
# "toCoinType": "crypto",
|
||
# "fromAmount": "10",
|
||
# "toAmount": "0.000153308367800000",
|
||
# "expiredTime": "1727257413353",
|
||
# "requestId": ""
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1727257398375
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
fromCurrencyId = self.safe_string(data, 'fromCoin', fromCode)
|
||
fromCurrency = self.currency(fromCurrencyId)
|
||
toCurrencyId = self.safe_string(data, 'toCoin', toCode)
|
||
toCurrency = self.currency(toCurrencyId)
|
||
return self.parse_conversion(data, fromCurrency, toCurrency)
|
||
|
||
def create_convert_trade(self, id: str, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
|
||
"""
|
||
convert from one currency to another
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/convert/confirm-quote
|
||
|
||
:param str id: the id of the trade that you want to make
|
||
:param str fromCode: the currency that you want to sell and convert from
|
||
:param str toCode: the currency that you want to buy and convert into
|
||
:param float amount: how much you want to trade in units of the from currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'quoteTxId': id,
|
||
}
|
||
response = self.privatePostV5AssetExchangeConvertExecute(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "ok",
|
||
# "result": {
|
||
# "exchangeStatus": "processing",
|
||
# "quoteTxId": "1010020692439483803499737088"
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1727257904969
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
return self.parse_conversion(data)
|
||
|
||
def fetch_convert_trade(self, id: str, code: Str = None, params={}) -> Conversion:
|
||
"""
|
||
fetch the data for a conversion trade
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/convert/get-convert-result
|
||
|
||
:param str id: the id of the trade that you want to fetch
|
||
:param str [code]: the unified currency code of the conversion trade
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.accountType]: eb_convert_uta, eb_convert_spot, eb_convert_funding, eb_convert_inverse, or eb_convert_contract
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
accountType = None
|
||
enableUnifiedMargin, enableUnifiedAccount = self.is_unified_enabled()
|
||
isUnifiedAccount = (enableUnifiedMargin or enableUnifiedAccount)
|
||
accountTypeDefault = 'eb_convert_uta' if isUnifiedAccount else 'eb_convert_spot'
|
||
accountType, params = self.handle_option_and_params(params, 'fetchConvertQuote', 'accountType', accountTypeDefault)
|
||
request: dict = {
|
||
'quoteTxId': id,
|
||
'accountType': accountType,
|
||
}
|
||
response = self.privateGetV5AssetExchangeConvertResultQuery(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "ok",
|
||
# "result": {
|
||
# "result": {
|
||
# "accountType": "eb_convert_uta",
|
||
# "exchangeTxId": "1010020692439483803499737088",
|
||
# "userId": "100406395",
|
||
# "fromCoin": "USDT",
|
||
# "fromCoinType": "crypto",
|
||
# "fromAmount": "10",
|
||
# "toCoin": "BTC",
|
||
# "toCoinType": "crypto",
|
||
# "toAmount": "0.00015344889",
|
||
# "exchangeStatus": "success",
|
||
# "extInfo": {},
|
||
# "convertRate": "0.000015344889",
|
||
# "createdAt": "1727257904726"
|
||
# }
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1727258257216
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
result = self.safe_dict(data, 'result', {})
|
||
fromCurrencyId = self.safe_string(result, 'fromCoin')
|
||
toCurrencyId = self.safe_string(result, 'toCoin')
|
||
fromCurrency = None
|
||
toCurrency = None
|
||
if fromCurrencyId is not None:
|
||
fromCurrency = self.currency(fromCurrencyId)
|
||
if toCurrencyId is not None:
|
||
toCurrency = self.currency(toCurrencyId)
|
||
return self.parse_conversion(result, fromCurrency, toCurrency)
|
||
|
||
def fetch_convert_trade_history(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Conversion]:
|
||
"""
|
||
fetch the users history of conversion trades
|
||
|
||
https://bybit-exchange.github.io/docs/v5/asset/convert/get-convert-history
|
||
|
||
:param str [code]: the unified currency code
|
||
:param int [since]: the earliest time in ms to fetch conversions for
|
||
:param int [limit]: the maximum number of conversion structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.accountType]: eb_convert_uta, eb_convert_spot, eb_convert_funding, eb_convert_inverse, or eb_convert_contract
|
||
:returns dict[]: a list of `conversion structures <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.privateGetV5AssetExchangeQueryConvertHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "ok",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "accountType": "eb_convert_uta",
|
||
# "exchangeTxId": "1010020692439483803499737088",
|
||
# "userId": "100406395",
|
||
# "fromCoin": "USDT",
|
||
# "fromCoinType": "crypto",
|
||
# "fromAmount": "10",
|
||
# "toCoin": "BTC",
|
||
# "toCoinType": "crypto",
|
||
# "toAmount": "0.00015344889",
|
||
# "exchangeStatus": "success",
|
||
# "extInfo": {},
|
||
# "convertRate": "0.000015344889",
|
||
# "createdAt": "1727257904726"
|
||
# }
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1727258761874
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'result', {})
|
||
dataList = self.safe_list(data, 'list', [])
|
||
return self.parse_conversions(dataList, code, 'fromCoin', 'toCoin', since, limit)
|
||
|
||
def parse_conversion(self, conversion: dict, fromCurrency: Currency = None, toCurrency: Currency = None) -> Conversion:
|
||
#
|
||
# fetchConvertQuote
|
||
#
|
||
# {
|
||
# "quoteTxId": "1010020692439481682687668224",
|
||
# "exchangeRate": "0.000015330836780000",
|
||
# "fromCoin": "USDT",
|
||
# "fromCoinType": "crypto",
|
||
# "toCoin": "BTC",
|
||
# "toCoinType": "crypto",
|
||
# "fromAmount": "10",
|
||
# "toAmount": "0.000153308367800000",
|
||
# "expiredTime": "1727257413353",
|
||
# "requestId": ""
|
||
# }
|
||
#
|
||
# createConvertTrade
|
||
#
|
||
# {
|
||
# "exchangeStatus": "processing",
|
||
# "quoteTxId": "1010020692439483803499737088"
|
||
# }
|
||
#
|
||
# fetchConvertTrade, fetchConvertTradeHistory
|
||
#
|
||
# {
|
||
# "accountType": "eb_convert_uta",
|
||
# "exchangeTxId": "1010020692439483803499737088",
|
||
# "userId": "100406395",
|
||
# "fromCoin": "USDT",
|
||
# "fromCoinType": "crypto",
|
||
# "fromAmount": "10",
|
||
# "toCoin": "BTC",
|
||
# "toCoinType": "crypto",
|
||
# "toAmount": "0.00015344889",
|
||
# "exchangeStatus": "success",
|
||
# "extInfo": {},
|
||
# "convertRate": "0.000015344889",
|
||
# "createdAt": "1727257904726"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(conversion, 'expiredTime', 'createdAt')
|
||
fromCoin = self.safe_string(conversion, 'fromCoin')
|
||
fromCode = self.safe_currency_code(fromCoin, fromCurrency)
|
||
to = self.safe_string(conversion, 'toCoin')
|
||
toCode = self.safe_currency_code(to, toCurrency)
|
||
return {
|
||
'info': conversion,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'id': self.safe_string_2(conversion, 'quoteTxId', 'exchangeTxId'),
|
||
'fromCurrency': fromCode,
|
||
'fromAmount': self.safe_number(conversion, 'fromAmount'),
|
||
'toCurrency': toCode,
|
||
'toAmount': self.safe_number(conversion, 'toAmount'),
|
||
'price': None,
|
||
'fee': None,
|
||
}
|
||
|
||
def fetch_long_short_ratio_history(self, symbol: Str = None, timeframe: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LongShortRatio]:
|
||
"""
|
||
fetches the long short ratio history for a unified market symbol
|
||
|
||
https://bybit-exchange.github.io/docs/v5/market/long-short-ratio
|
||
|
||
:param str symbol: unified symbol of the market to fetch the long short ratio for
|
||
:param str [timeframe]: the period for the ratio, default is 24 hours
|
||
:param int [since]: the earliest time in ms to fetch ratios for
|
||
:param int [limit]: the maximum number of long short ratio structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of `long short ratio structures <https://docs.ccxt.com/#/?id=long-short-ratio-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
type = None
|
||
type, params = self.get_bybit_type('fetchLongShortRatioHistory', market, params)
|
||
if type == 'spot' or type == 'option':
|
||
raise NotSupported(self.id + ' fetchLongShortRatioHistory() only support linear and inverse markets')
|
||
if timeframe is None:
|
||
timeframe = '1d'
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'period': timeframe,
|
||
'category': type,
|
||
}
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.publicGetV5MarketAccountRatio(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "retCode": 0,
|
||
# "retMsg": "OK",
|
||
# "result": {
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "buyRatio": "0.5707",
|
||
# "sellRatio": "0.4293",
|
||
# "timestamp": "1729123200000"
|
||
# },
|
||
# ]
|
||
# },
|
||
# "retExtInfo": {},
|
||
# "time": 1729147842516
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'result', {})
|
||
data = self.safe_list(result, 'list', [])
|
||
return self.parse_long_short_ratio_history(data, market)
|
||
|
||
def parse_long_short_ratio(self, info: dict, market: Market = None) -> LongShortRatio:
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "buyRatio": "0.5707",
|
||
# "sellRatio": "0.4293",
|
||
# "timestamp": "1729123200000"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(info, 'symbol')
|
||
timestamp = self.safe_integer_omit_zero(info, 'timestamp')
|
||
longString = self.safe_string(info, 'buyRatio')
|
||
shortString = self.safe_string(info, 'sellRatio')
|
||
return {
|
||
'info': info,
|
||
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'timeframe': None,
|
||
'longShortRatio': self.parse_to_numeric(Precise.string_div(longString, shortString)),
|
||
}
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
url = self.implode_hostname(self.urls['api'][api]) + '/' + path
|
||
if api == 'public':
|
||
if params:
|
||
url += '?' + self.rawencode(params)
|
||
elif api == 'private':
|
||
self.check_required_credentials()
|
||
isOpenapi = url.find('openapi') >= 0
|
||
isV3UnifiedMargin = url.find('unified/v3') >= 0
|
||
isV3Contract = url.find('contract/v3') >= 0
|
||
isV5UnifiedAccount = url.find('v5') >= 0
|
||
timestamp = str(self.nonce())
|
||
if isOpenapi:
|
||
if params:
|
||
body = self.json(params)
|
||
else:
|
||
# self fix for PHP is required otherwise it generates
|
||
# '[]' on empty arrays even when forced to use objects
|
||
body = '{}'
|
||
payload = timestamp + self.apiKey + body
|
||
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'hex')
|
||
headers = {
|
||
'Content-Type': 'application/json',
|
||
'X-BAPI-API-KEY': self.apiKey,
|
||
'X-BAPI-TIMESTAMP': timestamp,
|
||
'X-BAPI-SIGN': signature,
|
||
}
|
||
elif isV3UnifiedMargin or isV3Contract or isV5UnifiedAccount:
|
||
headers = {
|
||
'Content-Type': 'application/json',
|
||
'X-BAPI-API-KEY': self.apiKey,
|
||
'X-BAPI-TIMESTAMP': timestamp,
|
||
'X-BAPI-RECV-WINDOW': str(self.options['recvWindow']),
|
||
}
|
||
if isV3UnifiedMargin or isV3Contract:
|
||
headers['X-BAPI-SIGN-TYPE'] = '2'
|
||
query = self.extend({}, params)
|
||
queryEncoded = self.rawencode(query)
|
||
auth_base = str(timestamp) + self.apiKey + str(self.options['recvWindow'])
|
||
authFull = None
|
||
if method == 'POST':
|
||
body = self.json(query)
|
||
authFull = auth_base + body
|
||
else:
|
||
authFull = auth_base + queryEncoded
|
||
url += '?' + queryEncoded
|
||
signature = None
|
||
if self.secret.find('PRIVATE KEY') > -1:
|
||
signature = self.rsa(authFull, self.secret, 'sha256')
|
||
else:
|
||
signature = self.hmac(self.encode(authFull), self.encode(self.secret), hashlib.sha256)
|
||
headers['X-BAPI-SIGN'] = signature
|
||
else:
|
||
query = self.extend(params, {
|
||
'api_key': self.apiKey,
|
||
'recv_window': self.options['recvWindow'],
|
||
'timestamp': timestamp,
|
||
})
|
||
sortedQuery = self.keysort(query)
|
||
auth = self.rawencode(sortedQuery, True)
|
||
signature = None
|
||
if self.secret.find('PRIVATE KEY') > -1:
|
||
signature = self.rsa(auth, self.secret, 'sha256')
|
||
else:
|
||
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
|
||
if method == 'POST':
|
||
isSpot = url.find('spot') >= 0
|
||
extendedQuery = self.extend(query, {
|
||
'sign': signature,
|
||
})
|
||
if isSpot:
|
||
body = self.urlencode(extendedQuery)
|
||
headers = {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
}
|
||
else:
|
||
body = self.json(extendedQuery)
|
||
headers = {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
else:
|
||
url += '?' + self.rawencode(sortedQuery, True)
|
||
url += '&sign=' + signature
|
||
if method == 'POST':
|
||
brokerId = self.safe_string(self.options, 'brokerId')
|
||
if brokerId is not None:
|
||
headers['Referer'] = brokerId
|
||
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 not response:
|
||
return None # fallback to default error handler
|
||
#
|
||
# {
|
||
# "ret_code": 10001,
|
||
# "ret_msg": "ReadMapCB: expect {or n, but found \u0000, error " +
|
||
# "found in #0 byte of ...||..., bigger context " +
|
||
# "...||...",
|
||
# "ext_code": '',
|
||
# "ext_info": '',
|
||
# "result": null,
|
||
# "time_now": "1583934106.590436"
|
||
# }
|
||
#
|
||
# {
|
||
# "retCode":10001,
|
||
# "retMsg":"symbol params err",
|
||
# "result":{"symbol":"","bid":"","bidIv":"","bidSize":"","ask":"","askIv":"","askSize":"","lastPrice":"","openInterest":"","indexPrice":"","markPrice":"","markPriceIv":"","change24h":"","high24h":"","low24h":"","volume24h":"","turnover24h":"","totalVolume":"","totalTurnover":"","fundingRate":"","predictedFundingRate":"","nextFundingTime":"","countdownHour":"0","predictedDeliveryPrice":"","underlyingPrice":"","delta":"","gamma":"","vega":"","theta":""}
|
||
# }
|
||
#
|
||
errorCode = self.safe_string_2(response, 'ret_code', 'retCode')
|
||
if errorCode != '0':
|
||
if errorCode == '30084':
|
||
# not an error
|
||
# https://github.com/ccxt/ccxt/issues/11268
|
||
# https://github.com/ccxt/ccxt/pull/11624
|
||
# POST https://api.bybit.com/v2/private/position/switch-isolated 200 OK
|
||
# {"ret_code":30084,"ret_msg":"Isolated not modified","ext_code":"","ext_info":"","result":null,"time_now":"1642005219.937988","rate_limit_status":73,"rate_limit_reset_ms":1642005219894,"rate_limit":75}
|
||
return None
|
||
feedback = None
|
||
if errorCode == '10005' and url.find('order') < 0:
|
||
feedback = self.id + ' private api uses /user/v3/private/query-api to check if you have a unified account. The API key of user id must own one of permissions: "Account Transfer", "Subaccount Transfer", "Withdrawal" ' + body
|
||
else:
|
||
feedback = self.id + ' ' + body
|
||
if body.find('Withdraw address chain or destination tag are not equal') > -1:
|
||
feedback = feedback + '; You might also need to ensure the address is whitelisted'
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
||
raise ExchangeError(feedback) # unknown message
|
||
return None
|