/python/ccxt/binance.py

https://github.com/kroitor/ccxt · Python · 2396 lines · 1563 code · 47 blank · 786 comment · 245 complexity · abde173efb0eefcaa0c3a83cd9cbb0c8 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
  3. # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
  4. from ccxt.base.exchange import Exchange
  5. import math
  6. import json
  7. from ccxt.base.errors import ExchangeError
  8. from ccxt.base.errors import AuthenticationError
  9. from ccxt.base.errors import PermissionDenied
  10. from ccxt.base.errors import AccountSuspended
  11. from ccxt.base.errors import ArgumentsRequired
  12. from ccxt.base.errors import BadRequest
  13. from ccxt.base.errors import BadSymbol
  14. from ccxt.base.errors import InsufficientFunds
  15. from ccxt.base.errors import InvalidAddress
  16. from ccxt.base.errors import InvalidOrder
  17. from ccxt.base.errors import OrderNotFound
  18. from ccxt.base.errors import OrderImmediatelyFillable
  19. from ccxt.base.errors import NotSupported
  20. from ccxt.base.errors import DDoSProtection
  21. from ccxt.base.errors import RateLimitExceeded
  22. from ccxt.base.errors import ExchangeNotAvailable
  23. from ccxt.base.errors import InvalidNonce
  24. from ccxt.base.decimal_to_precision import ROUND
  25. from ccxt.base.decimal_to_precision import TRUNCATE
  26. class binance(Exchange):
  27. def describe(self):
  28. return self.deep_extend(super(binance, self).describe(), {
  29. 'id': 'binance',
  30. 'name': 'Binance',
  31. 'countries': ['JP', 'MT'], # Japan, Malta
  32. 'rateLimit': 500,
  33. 'certified': True,
  34. 'pro': True,
  35. # new metainfo interface
  36. 'has': {
  37. 'cancelAllOrders': True,
  38. 'cancelOrder': True,
  39. 'CORS': False,
  40. 'createOrder': True,
  41. 'fetchBalance': True,
  42. 'fetchBidsAsks': True,
  43. 'fetchClosedOrders': 'emulated',
  44. 'fetchDepositAddress': True,
  45. 'fetchDeposits': True,
  46. 'fetchFundingFees': True,
  47. 'fetchMarkets': True,
  48. 'fetchMyTrades': True,
  49. 'fetchOHLCV': True,
  50. 'fetchOpenOrders': True,
  51. 'fetchOrder': True,
  52. 'fetchOrders': True,
  53. 'fetchOrderBook': True,
  54. 'fetchStatus': True,
  55. 'fetchTicker': True,
  56. 'fetchTickers': True,
  57. 'fetchTime': True,
  58. 'fetchTrades': True,
  59. 'fetchTradingFee': True,
  60. 'fetchTradingFees': True,
  61. 'fetchTransactions': False,
  62. 'fetchWithdrawals': True,
  63. 'withdraw': True,
  64. },
  65. 'timeframes': {
  66. '1m': '1m',
  67. '3m': '3m',
  68. '5m': '5m',
  69. '15m': '15m',
  70. '30m': '30m',
  71. '1h': '1h',
  72. '2h': '2h',
  73. '4h': '4h',
  74. '6h': '6h',
  75. '8h': '8h',
  76. '12h': '12h',
  77. '1d': '1d',
  78. '3d': '3d',
  79. '1w': '1w',
  80. '1M': '1M',
  81. },
  82. 'urls': {
  83. 'logo': 'https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg',
  84. 'test': {
  85. 'dapiPublic': 'https://testnet.binancefuture.com/dapi/v1',
  86. 'dapiPrivate': 'https://testnet.binancefuture.com/dapi/v1',
  87. 'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1',
  88. 'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1',
  89. 'fapiPrivateV2': 'https://testnet.binancefuture.com/fapi/v2',
  90. 'public': 'https://testnet.binance.vision/api/v3',
  91. 'private': 'https://testnet.binance.vision/api/v3',
  92. 'v3': 'https://testnet.binance.vision/api/v3',
  93. 'v1': 'https://testnet.binance.vision/api/v1',
  94. },
  95. 'api': {
  96. 'wapi': 'https://api.binance.com/wapi/v3',
  97. 'sapi': 'https://api.binance.com/sapi/v1',
  98. 'dapiPublic': 'https://dapi.binance.com/dapi/v1',
  99. 'dapiPrivate': 'https://dapi.binance.com/dapi/v1',
  100. 'dapiData': 'https://dapi.binance.com/futures/data',
  101. 'fapiPublic': 'https://fapi.binance.com/fapi/v1',
  102. 'fapiPrivate': 'https://fapi.binance.com/fapi/v1',
  103. 'fapiData': 'https://fapi.binance.com/futures/data',
  104. 'fapiPrivateV2': 'https://fapi.binance.com/fapi/v2',
  105. 'public': 'https://api.binance.com/api/v3',
  106. 'private': 'https://api.binance.com/api/v3',
  107. 'v3': 'https://api.binance.com/api/v3',
  108. 'v1': 'https://api.binance.com/api/v1',
  109. },
  110. 'www': 'https://www.binance.com',
  111. 'referral': 'https://www.binance.com/?ref=10205187',
  112. 'doc': [
  113. 'https://binance-docs.github.io/apidocs/spot/en',
  114. ],
  115. 'api_management': 'https://www.binance.com/en/usercenter/settings/api-management',
  116. 'fees': 'https://www.binance.com/en/fee/schedule',
  117. },
  118. 'api': {
  119. # the API structure below will need 3-layer apidefs
  120. 'sapi': {
  121. 'get': [
  122. 'accountSnapshot',
  123. # these endpoints require self.apiKey
  124. 'margin/asset',
  125. 'margin/pair',
  126. 'margin/allAssets',
  127. 'margin/allPairs',
  128. 'margin/priceIndex',
  129. # these endpoints require self.apiKey + self.secret
  130. 'asset/assetDividend',
  131. 'margin/loan',
  132. 'margin/repay',
  133. 'margin/account',
  134. 'margin/transfer',
  135. 'margin/interestHistory',
  136. 'margin/forceLiquidationRec',
  137. 'margin/order',
  138. 'margin/openOrders',
  139. 'margin/allOrders',
  140. 'margin/myTrades',
  141. 'margin/maxBorrowable',
  142. 'margin/maxTransferable',
  143. 'margin/isolated/transfer',
  144. 'margin/isolated/account',
  145. 'margin/isolated/pair',
  146. 'margin/isolated/allPairs',
  147. 'futures/transfer',
  148. 'futures/loan/borrow/history',
  149. 'futures/loan/repay/history',
  150. 'futures/loan/wallet',
  151. 'futures/loan/configs',
  152. 'futures/loan/calcAdjustLevel',
  153. 'futures/loan/calcMaxAdjustAmount',
  154. 'futures/loan/adjustCollateral/history',
  155. 'futures/loan/liquidationHistory',
  156. # https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi
  157. 'capital/config/getall', # get networks for withdrawing USDT ERC20 vs USDT Omni
  158. 'capital/deposit/address',
  159. 'capital/deposit/hisrec',
  160. 'capital/deposit/subAddress',
  161. 'capital/deposit/subHisrec',
  162. 'capital/withdraw/history',
  163. 'sub-account/futures/account',
  164. 'sub-account/futures/accountSummary',
  165. 'sub-account/futures/positionRisk',
  166. 'sub-account/futures/internalTransfer',
  167. 'sub-account/margin/account',
  168. 'sub-account/margin/accountSummary',
  169. 'sub-account/spotSummary',
  170. 'sub-account/status',
  171. 'sub-account/transfer/subUserHistory',
  172. # lending endpoints
  173. 'lending/daily/product/list',
  174. 'lending/daily/userLeftQuota',
  175. 'lending/daily/userRedemptionQuota',
  176. 'lending/daily/token/position',
  177. 'lending/union/account',
  178. 'lending/union/purchaseRecord',
  179. 'lending/union/redemptionRecord',
  180. 'lending/union/interestHistory',
  181. 'lending/project/list',
  182. 'lending/project/position/list',
  183. # mining endpoints
  184. 'mining/pub/algoList',
  185. 'mining/pub/coinList',
  186. 'mining/worker/detail',
  187. 'mining/worker/list',
  188. 'mining/payment/list',
  189. 'mining/statistics/user/status',
  190. 'mining/statistics/user/list',
  191. ],
  192. 'post': [
  193. 'asset/dust',
  194. 'account/disableFastWithdrawSwitch',
  195. 'account/enableFastWithdrawSwitch',
  196. 'capital/withdraw/apply',
  197. 'margin/transfer',
  198. 'margin/loan',
  199. 'margin/repay',
  200. 'margin/order',
  201. 'margin/isolated/create',
  202. 'margin/isolated/transfer',
  203. 'sub-account/margin/transfer',
  204. 'sub-account/margin/enable',
  205. 'sub-account/margin/enable',
  206. 'sub-account/futures/enable',
  207. 'sub-account/futures/transfer',
  208. 'sub-account/futures/internalTransfer',
  209. 'sub-account/transfer/subToSub',
  210. 'sub-account/transfer/subToMaster',
  211. 'userDataStream',
  212. 'userDataStream/isolated',
  213. 'futures/transfer',
  214. 'futures/loan/borrow',
  215. 'futures/loan/repay',
  216. 'futures/loan/adjustCollateral',
  217. # lending
  218. 'lending/customizedFixed/purchase',
  219. 'lending/daily/purchase',
  220. 'lending/daily/redeem',
  221. ],
  222. 'put': [
  223. 'userDataStream',
  224. 'userDataStream/isolated',
  225. ],
  226. 'delete': [
  227. 'margin/order',
  228. 'userDataStream',
  229. 'userDataStream/isolated',
  230. ],
  231. },
  232. 'wapi': {
  233. 'post': [
  234. 'withdraw',
  235. 'sub-account/transfer',
  236. ],
  237. 'get': [
  238. 'depositHistory',
  239. 'withdrawHistory',
  240. 'depositAddress',
  241. 'accountStatus',
  242. 'systemStatus',
  243. 'apiTradingStatus',
  244. 'userAssetDribbletLog',
  245. 'tradeFee',
  246. 'assetDetail',
  247. 'sub-account/list',
  248. 'sub-account/transfer/history',
  249. 'sub-account/assets',
  250. ],
  251. },
  252. 'dapiPublic': {
  253. 'get': [
  254. 'ping',
  255. 'time',
  256. 'exchangeInfo',
  257. 'depth',
  258. 'trades',
  259. 'historicalTrades',
  260. 'aggTrades',
  261. 'premiumIndex',
  262. 'fundingRate',
  263. 'klines',
  264. 'continuousKlines',
  265. 'indexPriceKlines',
  266. 'markPriceKlines',
  267. 'ticker/24hr',
  268. 'ticker/price',
  269. 'ticker/bookTicker',
  270. 'allForceOrders',
  271. 'openInterest',
  272. ],
  273. },
  274. 'dapiData': {
  275. 'get': [
  276. 'openInterestHist',
  277. 'topLongShortAccountRatio',
  278. 'topLongShortPositionRatio',
  279. 'globalLongShortAccountRatio',
  280. 'takerBuySellVol',
  281. 'basis',
  282. ],
  283. },
  284. 'dapiPrivate': {
  285. 'get': [
  286. 'positionSide/dual',
  287. 'order',
  288. 'openOrder',
  289. 'openOrders',
  290. 'allOrders',
  291. 'balance',
  292. 'account',
  293. 'positionMargin/history',
  294. 'positionRisk',
  295. 'userTrades',
  296. 'income',
  297. 'leverageBracket',
  298. 'forceOrders',
  299. 'adlQuantile',
  300. ],
  301. 'post': [
  302. 'positionSide/dual',
  303. 'order',
  304. 'batchOrders',
  305. 'countdownCancelAll',
  306. 'leverage',
  307. 'marginType',
  308. 'positionMargin',
  309. 'listenKey',
  310. ],
  311. 'put': [
  312. 'listenKey',
  313. ],
  314. 'delete': [
  315. 'order',
  316. 'allOpenOrders',
  317. 'batchOrders',
  318. 'listenKey',
  319. ],
  320. },
  321. 'fapiPublic': {
  322. 'get': [
  323. 'ping',
  324. 'time',
  325. 'exchangeInfo',
  326. 'depth',
  327. 'trades',
  328. 'historicalTrades',
  329. 'aggTrades',
  330. 'klines',
  331. 'fundingRate',
  332. 'premiumIndex',
  333. 'ticker/24hr',
  334. 'ticker/price',
  335. 'ticker/bookTicker',
  336. 'allForceOrders',
  337. 'openInterest',
  338. ],
  339. },
  340. 'fapiData': {
  341. 'get': [
  342. 'openInterestHist',
  343. 'topLongShortAccountRatio',
  344. 'topLongShortPositionRatio',
  345. 'globalLongShortAccountRatio',
  346. 'takerlongshortRatio',
  347. ],
  348. },
  349. 'fapiPrivate': {
  350. 'get': [
  351. 'allForceOrders',
  352. 'allOrders',
  353. 'openOrder',
  354. 'openOrders',
  355. 'order',
  356. 'account',
  357. 'balance',
  358. 'leverageBracket',
  359. 'positionMargin/history',
  360. 'positionRisk',
  361. 'positionSide/dual',
  362. 'userTrades',
  363. 'income',
  364. ],
  365. 'post': [
  366. 'batchOrders',
  367. 'positionSide/dual',
  368. 'positionMargin',
  369. 'marginType',
  370. 'order',
  371. 'leverage',
  372. 'listenKey',
  373. 'countdownCancelAll',
  374. ],
  375. 'put': [
  376. 'listenKey',
  377. ],
  378. 'delete': [
  379. 'batchOrders',
  380. 'order',
  381. 'allOpenOrders',
  382. 'listenKey',
  383. ],
  384. },
  385. 'fapiPrivateV2': {
  386. 'get': [
  387. 'account',
  388. 'balance',
  389. 'positionRisk',
  390. ],
  391. },
  392. 'v3': {
  393. 'get': [
  394. 'ticker/price',
  395. 'ticker/bookTicker',
  396. ],
  397. },
  398. 'public': {
  399. 'get': [
  400. 'ping',
  401. 'time',
  402. 'depth',
  403. 'trades',
  404. 'aggTrades',
  405. 'historicalTrades',
  406. 'klines',
  407. 'ticker/24hr',
  408. 'ticker/price',
  409. 'ticker/bookTicker',
  410. 'exchangeInfo',
  411. ],
  412. 'put': ['userDataStream'],
  413. 'post': ['userDataStream'],
  414. 'delete': ['userDataStream'],
  415. },
  416. 'private': {
  417. 'get': [
  418. 'allOrderList', # oco
  419. 'openOrderList', # oco
  420. 'orderList', # oco
  421. 'order',
  422. 'openOrders',
  423. 'allOrders',
  424. 'account',
  425. 'myTrades',
  426. ],
  427. 'post': [
  428. 'order/oco',
  429. 'order',
  430. 'order/test',
  431. ],
  432. 'delete': [
  433. 'openOrders', # added on 2020-04-25 for canceling all open orders per symbol
  434. 'orderList', # oco
  435. 'order',
  436. ],
  437. },
  438. },
  439. 'fees': {
  440. 'trading': {
  441. 'tierBased': False,
  442. 'percentage': True,
  443. 'taker': 0.001,
  444. 'maker': 0.001,
  445. },
  446. },
  447. 'commonCurrencies': {
  448. 'BCC': 'BCC', # kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848
  449. 'YOYO': 'YOYOW',
  450. },
  451. # exchange-specific options
  452. 'options': {
  453. # 'fetchTradesMethod': 'publicGetAggTrades', # publicGetTrades, publicGetHistoricalTrades
  454. 'defaultTimeInForce': 'GTC', # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
  455. 'defaultType': 'spot', # 'spot', 'future', 'margin', 'delivery'
  456. 'hasAlreadyAuthenticatedSuccessfully': False,
  457. 'warnOnFetchOpenOrdersWithoutSymbol': True,
  458. 'recvWindow': 5 * 1000, # 5 sec, binance default
  459. 'timeDifference': 0, # the difference between system clock and Binance clock
  460. 'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
  461. 'parseOrderToPrecision': False, # force amounts and costs in parseOrder to precision
  462. 'newOrderRespType': {
  463. 'market': 'FULL', # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
  464. 'limit': 'RESULT', # we change it from 'ACK' by default to 'RESULT'
  465. },
  466. 'quoteOrderQty': True, # whether market orders support amounts in quote currency
  467. },
  468. # https://binance-docs.github.io/apidocs/spot/en/#error-codes-2
  469. 'exceptions': {
  470. 'API key does not exist': AuthenticationError,
  471. 'Order would trigger immediately.': OrderImmediatelyFillable,
  472. 'Order would immediately match and take.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Order would immediately match and take."}
  473. 'Account has insufficient balance for requested action.': InsufficientFunds,
  474. 'Rest API trading is not enabled.': ExchangeNotAvailable,
  475. "You don't have permission.": PermissionDenied, # {"msg":"You don't have permission.","success":false}
  476. 'Market is closed.': ExchangeNotAvailable, # {"code":-1013,"msg":"Market is closed."}
  477. 'Too many requests.': DDoSProtection, # {"msg":"Too many requests. Please try again later.","success":false}
  478. '-1000': ExchangeNotAvailable, # {"code":-1000,"msg":"An unknown error occured while processing the request."}
  479. '-1001': ExchangeNotAvailable, # 'Internal error; unable to process your request. Please try again.'
  480. '-1002': AuthenticationError, # 'You are not authorized to execute self request.'
  481. '-1003': RateLimitExceeded, # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
  482. '-1013': InvalidOrder, # createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
  483. '-1015': RateLimitExceeded, # 'Too many new orders; current limit is %s orders per %s.'
  484. '-1016': ExchangeNotAvailable, # 'This service is no longer available.',
  485. '-1020': BadRequest, # 'This operation is not supported.'
  486. '-1021': InvalidNonce, # 'your time is ahead of server'
  487. '-1022': AuthenticationError, # {"code":-1022,"msg":"Signature for self request is not valid."}
  488. '-1100': BadRequest, # createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
  489. '-1101': BadRequest, # Too many parameters; expected %s and received %s.
  490. '-1102': BadRequest, # Param %s or %s must be sent, but both were empty
  491. '-1103': BadRequest, # An unknown parameter was sent.
  492. '-1104': BadRequest, # Not all sent parameters were read, read 8 parameters but was sent 9
  493. '-1105': BadRequest, # Parameter %s was empty.
  494. '-1106': BadRequest, # Parameter %s sent when not required.
  495. '-1111': BadRequest, # Precision is over the maximum defined for self asset.
  496. '-1112': InvalidOrder, # No orders on book for symbol.
  497. '-1114': BadRequest, # TimeInForce parameter sent when not required.
  498. '-1115': BadRequest, # Invalid timeInForce.
  499. '-1116': BadRequest, # Invalid orderType.
  500. '-1117': BadRequest, # Invalid side.
  501. '-1118': BadRequest, # New client order ID was empty.
  502. '-1119': BadRequest, # Original client order ID was empty.
  503. '-1120': BadRequest, # Invalid interval.
  504. '-1121': BadSymbol, # Invalid symbol.
  505. '-1125': AuthenticationError, # This listenKey does not exist.
  506. '-1127': BadRequest, # More than %s hours between startTime and endTime.
  507. '-1128': BadRequest, # {"code":-1128,"msg":"Combination of optional parameters invalid."}
  508. '-1130': BadRequest, # Data sent for paramter %s is not valid.
  509. '-1131': BadRequest, # recvWindow must be less than 60000
  510. '-2010': ExchangeError, # generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc...
  511. '-2011': OrderNotFound, # cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
  512. '-2013': OrderNotFound, # fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'
  513. '-2014': AuthenticationError, # {"code":-2014, "msg": "API-key format invalid."}
  514. '-2015': AuthenticationError, # "Invalid API-key, IP, or permissions for action."
  515. '-3005': InsufficientFunds, # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
  516. '-3008': InsufficientFunds, # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
  517. '-3010': ExchangeError, # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
  518. '-3022': AccountSuspended, # You account's trading is banned.
  519. },
  520. })
  521. def nonce(self):
  522. return self.milliseconds() - self.options['timeDifference']
  523. def fetch_time(self, params={}):
  524. type = self.safe_string_2(self.options, 'fetchTime', 'defaultType', 'spot')
  525. method = 'publicGetTime'
  526. if type == 'future':
  527. method = 'fapiPublicGetTime'
  528. elif type == 'delivery':
  529. method = 'dapiPublicGetTime'
  530. response = getattr(self, method)(params)
  531. return self.safe_integer(response, 'serverTime')
  532. def load_time_difference(self, params={}):
  533. serverTime = self.fetch_time(params)
  534. after = self.milliseconds()
  535. self.options['timeDifference'] = after - serverTime
  536. return self.options['timeDifference']
  537. def fetch_markets(self, params={}):
  538. defaultType = self.safe_string_2(self.options, 'fetchMarkets', 'defaultType', 'spot')
  539. type = self.safe_string(params, 'type', defaultType)
  540. query = self.omit(params, 'type')
  541. if (type != 'spot') and (type != 'future') and (type != 'margin') and (type != 'delivery'):
  542. raise ExchangeError(self.id + " does not support '" + type + "' type, set exchange.options['defaultType'] to 'spot', 'margin', 'delivery' or 'future'") # eslint-disable-line quotes
  543. method = 'publicGetExchangeInfo'
  544. if type == 'future':
  545. method = 'fapiPublicGetExchangeInfo'
  546. elif type == 'delivery':
  547. method = 'dapiPublicGetExchangeInfo'
  548. response = getattr(self, method)(query)
  549. #
  550. # spot / margin
  551. #
  552. # {
  553. # "timezone":"UTC",
  554. # "serverTime":1575416692969,
  555. # "rateLimits":[
  556. # {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
  557. # {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100},
  558. # {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000}
  559. # ],
  560. # "exchangeFilters":[],
  561. # "symbols":[
  562. # {
  563. # "symbol":"ETHBTC",
  564. # "status":"TRADING",
  565. # "baseAsset":"ETH",
  566. # "baseAssetPrecision":8,
  567. # "quoteAsset":"BTC",
  568. # "quotePrecision":8,
  569. # "baseCommissionPrecision":8,
  570. # "quoteCommissionPrecision":8,
  571. # "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],
  572. # "icebergAllowed":true,
  573. # "ocoAllowed":true,
  574. # "quoteOrderQtyMarketAllowed":true,
  575. # "isSpotTradingAllowed":true,
  576. # "isMarginTradingAllowed":true,
  577. # "filters":[
  578. # {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},
  579. # {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5},
  580. # {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"},
  581. # {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5},
  582. # {"filterType":"ICEBERG_PARTS","limit":10},
  583. # {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"},
  584. # {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5}
  585. # ]
  586. # },
  587. # ],
  588. # }
  589. #
  590. # futures/usdt-margined(fapi)
  591. #
  592. # {
  593. # "timezone":"UTC",
  594. # "serverTime":1575417244353,
  595. # "rateLimits":[
  596. # {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
  597. # {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200}
  598. # ],
  599. # "exchangeFilters":[],
  600. # "symbols":[
  601. # {
  602. # "symbol":"BTCUSDT",
  603. # "status":"TRADING",
  604. # "maintMarginPercent":"2.5000",
  605. # "requiredMarginPercent":"5.0000",
  606. # "baseAsset":"BTC",
  607. # "quoteAsset":"USDT",
  608. # "pricePrecision":2,
  609. # "quantityPrecision":3,
  610. # "baseAssetPrecision":8,
  611. # "quotePrecision":8,
  612. # "filters":[
  613. # {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"},
  614. # {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"},
  615. # {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"},
  616. # {"limit":200,"filterType":"MAX_NUM_ORDERS"},
  617. # {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
  618. # ],
  619. # "orderTypes":["LIMIT","MARKET","STOP"],
  620. # "timeInForce":["GTC","IOC","FOK","GTX"]
  621. # }
  622. # ]
  623. # }
  624. #
  625. # delivery/coin-margined(dapi)
  626. #
  627. # {
  628. # "timezone": "UTC",
  629. # "serverTime": 1597667052958,
  630. # "rateLimits": [
  631. # {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},
  632. # {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":6000}
  633. # ],
  634. # "exchangeFilters": [],
  635. # "symbols": [
  636. # {
  637. # "symbol": "BTCUSD_200925",
  638. # "pair": "BTCUSD",
  639. # "contractType": "CURRENT_QUARTER",
  640. # "deliveryDate": 1601020800000,
  641. # "onboardDate": 1590739200000,
  642. # "contractStatus": "TRADING",
  643. # "contractSize": 100,
  644. # "marginAsset": "BTC",
  645. # "maintMarginPercent": "2.5000",
  646. # "requiredMarginPercent": "5.0000",
  647. # "baseAsset": "BTC",
  648. # "quoteAsset": "USD",
  649. # "pricePrecision": 1,
  650. # "quantityPrecision": 0,
  651. # "baseAssetPrecision": 8,
  652. # "quotePrecision": 8,
  653. # "equalQtyPrecision": 4,
  654. # "filters": [
  655. # {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
  656. # {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
  657. # {"stepSize":"0","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
  658. # {"limit":200,"filterType":"MAX_NUM_ORDERS"},
  659. # {"multiplierDown":"0.9500","multiplierUp":"1.0500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
  660. # ],
  661. # "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
  662. # "timeInForce": ["GTC","IOC","FOK","GTX"]
  663. # },
  664. # {
  665. # "symbol": "BTCUSD_PERP",
  666. # "pair": "BTCUSD",
  667. # "contractType": "PERPETUAL",
  668. # "deliveryDate": 4133404800000,
  669. # "onboardDate": 1596006000000,
  670. # "contractStatus": "TRADING",
  671. # "contractSize": 100,
  672. # "marginAsset": "BTC",
  673. # "maintMarginPercent": "2.5000",
  674. # "requiredMarginPercent": "5.0000",
  675. # "baseAsset": "BTC",
  676. # "quoteAsset": "USD",
  677. # "pricePrecision": 1,
  678. # "quantityPrecision": 0,
  679. # "baseAssetPrecision": 8,
  680. # "quotePrecision": 8,
  681. # "equalQtyPrecision": 4,
  682. # "filters": [
  683. # {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
  684. # {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
  685. # {"stepSize":"1","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
  686. # {"limit":200,"filterType":"MAX_NUM_ORDERS"},
  687. # {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
  688. # ],
  689. # "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
  690. # "timeInForce": ["GTC","IOC","FOK","GTX"]
  691. # }
  692. # ]
  693. # }
  694. #
  695. if self.options['adjustForTimeDifference']:
  696. self.load_time_difference()
  697. markets = self.safe_value(response, 'symbols')
  698. result = []
  699. for i in range(0, len(markets)):
  700. market = markets[i]
  701. marketType = 'spot'
  702. future = False
  703. delivery = False
  704. if 'maintMarginPercent' in market:
  705. delivery = ('marginAsset' in market)
  706. future = not delivery
  707. marketType = 'delivery' if delivery else 'future'
  708. spot = not (future or delivery)
  709. id = self.safe_string(market, 'symbol')
  710. lowercaseId = self.safe_string_lower(market, 'symbol')
  711. baseId = self.safe_string(market, 'baseAsset')
  712. quoteId = self.safe_string(market, 'quoteAsset')
  713. base = self.safe_currency_code(baseId)
  714. quote = self.safe_currency_code(quoteId)
  715. symbol = id if delivery else (base + '/' + quote)
  716. filters = self.safe_value(market, 'filters', [])
  717. filtersByType = self.index_by(filters, 'filterType')
  718. precision = {
  719. 'base': self.safe_integer(market, 'baseAssetPrecision'),
  720. 'quote': self.safe_integer(market, 'quotePrecision'),
  721. 'amount': self.safe_integer(market, 'baseAssetPrecision'),
  722. 'price': self.safe_integer(market, 'quotePrecision'),
  723. }
  724. status = self.safe_string_2(market, 'status', 'contractStatus')
  725. active = (status == 'TRADING')
  726. margin = self.safe_value(market, 'isMarginTradingAllowed', future or delivery)
  727. entry = {
  728. 'id': id,
  729. 'lowercaseId': lowercaseId,
  730. 'symbol': symbol,
  731. 'base': base,
  732. 'quote': quote,
  733. 'baseId': baseId,
  734. 'quoteId': quoteId,
  735. 'info': market,
  736. 'type': marketType,
  737. 'spot': spot,
  738. 'margin': margin,
  739. 'future': future,
  740. 'delivery': delivery,
  741. 'active': active,
  742. 'precision': precision,
  743. 'limits': {
  744. 'amount': {
  745. 'min': math.pow(10, -precision['amount']),
  746. 'max': None,
  747. },
  748. 'price': {
  749. 'min': None,
  750. 'max': None,
  751. },
  752. 'cost': {
  753. 'min': None,
  754. 'max': None,
  755. },
  756. },
  757. }
  758. if 'PRICE_FILTER' in filtersByType:
  759. filter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
  760. # PRICE_FILTER reports zero values for maxPrice
  761. # since they updated filter types in November 2018
  762. # https://github.com/ccxt/ccxt/issues/4286
  763. # therefore limits['price']['max'] doesn't have any meaningful value except None
  764. entry['limits']['price'] = {
  765. 'min': self.safe_float(filter, 'minPrice'),
  766. 'max': None,
  767. }
  768. maxPrice = self.safe_float(filter, 'maxPrice')
  769. if (maxPrice is not None) and (maxPrice > 0):
  770. entry['limits']['price']['max'] = maxPrice
  771. entry['precision']['price'] = self.precision_from_string(filter['tickSize'])
  772. if 'LOT_SIZE' in filtersByType:
  773. filter = self.safe_value(filtersByType, 'LOT_SIZE', {})
  774. stepSize = self.safe_string(filter, 'stepSize')
  775. entry['precision']['amount'] = self.precision_from_string(stepSize)
  776. entry['limits']['amount'] = {
  777. 'min': self.safe_float(filter, 'minQty'),
  778. 'max': self.safe_float(filter, 'maxQty'),
  779. }
  780. if 'MARKET_LOT_SIZE' in filtersByType:
  781. filter = self.safe_value(filtersByType, 'MARKET_LOT_SIZE', {})
  782. entry['limits']['market'] = {
  783. 'min': self.safe_float(filter, 'minQty'),
  784. 'max': self.safe_float(filter, 'maxQty'),
  785. }
  786. if 'MIN_NOTIONAL' in filtersByType:
  787. filter = self.safe_value(filtersByType, 'MIN_NOTIONAL', {})
  788. entry['limits']['cost']['min'] = self.safe_float(filter, 'minNotional')
  789. result.append(entry)
  790. return result
  791. def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
  792. market = self.markets[symbol]
  793. key = 'quote'
  794. rate = market[takerOrMaker]
  795. cost = amount * rate
  796. precision = market['precision']['price']
  797. if side == 'sell':
  798. cost *= price
  799. else:
  800. key = 'base'
  801. precision = market['precision']['amount']
  802. cost = self.decimal_to_precision(cost, ROUND, precision, self.precisionMode)
  803. return {
  804. 'type': takerOrMaker,
  805. 'currency': market[key],
  806. 'rate': rate,
  807. 'cost': float(cost),
  808. }
  809. def fetch_balance(self, params={}):
  810. self.load_markets()
  811. defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
  812. type = self.safe_string(params, 'type', defaultType)
  813. method = 'privateGetAccount'
  814. if type == 'future':
  815. options = self.safe_value(self.options, 'future', {})
  816. fetchBalanceOptions = self.safe_value(options, 'fetchBalance', {})
  817. method = self.safe_string(fetchBalanceOptions, 'method', 'fapiPrivateV2GetAccount')
  818. elif type == 'delivery':
  819. options = self.safe_value(self.options, 'delivery', {})
  820. fetchBalanceOptions = self.safe_value(options, 'fetchBalance', {})
  821. method = self.safe_string(fetchBalanceOptions, 'method', 'dapiPrivateGetAccount')
  822. elif type == 'margin':
  823. method = 'sapiGetMarginAccount'
  824. query = self.omit(params, 'type')
  825. response = getattr(self, method)(query)
  826. #
  827. # spot
  828. #
  829. # {
  830. # makerCommission: 10,
  831. # takerCommission: 10,
  832. # buyerCommission: 0,
  833. # sellerCommission: 0,
  834. # canTrade: True,
  835. # canWithdraw: True,
  836. # canDeposit: True,
  837. # updateTime: 1575357359602,
  838. # accountType: "MARGIN",
  839. # balances: [
  840. # {asset: "BTC", free: "0.00219821", locked: "0.00000000" },
  841. # ]
  842. # }
  843. #
  844. # margin
  845. #
  846. # {
  847. # "borrowEnabled":true,
  848. # "marginLevel":"999.00000000",
  849. # "totalAssetOfBtc":"0.00000000",
  850. # "totalLiabilityOfBtc":"0.00000000",
  851. # "totalNetAssetOfBtc":"0.00000000",
  852. # "tradeEnabled":true,
  853. # "transferEnabled":true,
  854. # "userAssets":[
  855. # {"asset":"MATIC","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
  856. # {"asset":"VET","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
  857. # {"asset":"USDT","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}
  858. # ],
  859. # }
  860. #
  861. # futures(fapi)
  862. #
  863. # fapiPrivateGetAccount
  864. #
  865. # {
  866. # "feeTier":0,
  867. # "canTrade":true,
  868. # "canDeposit":true,
  869. # "canWithdraw":true,
  870. # "updateTime":0,
  871. # "totalInitialMargin":"0.00000000",
  872. # "totalMaintMargin":"0.00000000",
  873. # "totalWalletBalance":"4.54000000",
  874. # "totalUnrealizedProfit":"0.00000000",
  875. # "totalMarginBalance":"4.54000000",
  876. # "totalPositionInitialMargin":"0.00000000",
  877. # "totalOpenOrderInitialMargin":"0.00000000",
  878. # "maxWithdrawAmount":"4.54000000",
  879. # "assets":[
  880. # {
  881. # "asset":"USDT",
  882. # "walletBalance":"4.54000000",
  883. # "unrealizedProfit":"0.00000000",
  884. # "marginBalance":"4.54000000",
  885. # "maintMargin":"0.00000000",
  886. # "initialMargin":"0.00000000",
  887. # "positionInitialMargin":"0.00000000",
  888. # "openOrderInitialMargin":"0.00000000",
  889. # "maxWithdrawAmount":"4.54000000"
  890. # }
  891. # ],
  892. # "positions":[
  893. # {
  894. # "symbol":"BTCUSDT",
  895. # "initialMargin":"0.00000",
  896. # "maintMargin":"0.00000",
  897. # "unrealizedProfit":"0.00000000",
  898. # "positionInitialMargin":"0.00000",
  899. # "openOrderInitialMargin":"0.00000"
  900. # }
  901. # ]
  902. # }
  903. #
  904. # fapiPrivateV2GetAccount
  905. #
  906. # {
  907. # "feeTier":0,
  908. # "canTrade":true,
  909. # "canDeposit":true,
  910. # "canWithdraw":true,
  911. # "updateTime":0,
  912. # "totalInitialMargin":"0.00000000",
  913. # "totalMaintMargin":"0.00000000",
  914. # "totalWalletBalance":"0.00000000",
  915. # "totalUnrealizedProfit":"0.00000000",
  916. # "totalMarginBalance":"0.00000000",
  917. # "totalPositionInitialMargin":"0.00000000",
  918. # "totalOpenOrderInitialMargin":"0.00000000",
  919. # "totalCrossWalletBalance":"0.00000000",
  920. # "totalCrossUnPnl":"0.00000000",
  921. # "availableBalance":"0.00000000",
  922. # "maxWithdrawAmount":"0.00000000",
  923. # "assets":[
  924. # {
  925. # "asset":"BNB",
  926. # "walletBalance":"0.01000000",
  927. # "unrealizedProfit":"0.00000000",
  928. # "marginBalance":"0.01000000",
  929. # "maintMargin":"0.00000000",
  930. # "initialMargin":"0.00000000",
  931. # "positionInitialMargin":"0.00000000",
  932. # "openOrderInitialMargin":"0.00000000",
  933. # "maxWithdrawAmount":"0.01000000",
  934. # "crossWalletBalance":"0.01000000",
  935. # "crossUnPnl":"0.00000000",
  936. # "availableBalance":"0.01000000"
  937. # }
  938. # ],
  939. # "positions":[
  940. # {
  941. # "symbol":"BTCUSDT",
  942. # "initialMargin":"0",
  943. # "maintMargin":"0",
  944. # "unrealizedProfit":"0.00000000",
  945. # "positionInitialMargin":"0",
  946. # "openOrderInitialMargin":"0",
  947. # "leverage":"20",
  948. # "isolated":false,
  949. # "entryPrice":"0.00000",
  950. # "maxNotional":"5000000",
  951. # "positionSide":"BOTH"
  952. # },
  953. # ]
  954. # }
  955. #
  956. # fapiPrivateV2GetBalance
  957. #
  958. # [
  959. # {
  960. # "accountAlias":"FzFzXquXXqoC",
  961. # "asset":"BNB",
  962. # "balance":"0.01000000",
  963. # "crossWalletBalance":"0.01000000",
  964. # "crossUnPnl":"0.00000000",
  965. # "availableBalance":"0.01000000",
  966. # "maxWithdrawAmount":"0.01000000"
  967. # }
  968. # ]
  969. #
  970. result = {'info': response}
  971. if (type == 'spot') or (type == 'margin'):
  972. balances = self.safe_value_2(response, 'balances', 'userAssets', [])
  973. for i in range(0, len(balances)):
  974. balance = balances[i]
  975. currencyId = self.safe_string(balance, 'asset')
  976. code = self.safe_currency_code(currencyId)
  977. account = self.account()
  978. account['free'] = self.safe_float(balance, 'free')
  979. account['used'] = self.safe_float(balance, 'locked')
  980. result[code] = account
  981. else:
  982. balances = response
  983. if not isinstance(response, list):
  984. balances = self.safe_value(response, 'assets', [])
  985. for i in range(0, len(balances)):
  986. balance = balances[i]
  987. currencyId = self.safe_string(balance, 'asset')
  988. code = self.safe_currency_code(currencyId)
  989. account = self.account()
  990. account['free'] = self.safe_float(balance, 'availableBalance')
  991. account['used'] = self.safe_float(balance, 'initialMargin')
  992. account['total'] = self.safe_float_2(balance, 'marginBalance', 'balance')
  993. result[code] = account
  994. return self.parse_balance(result)
  995. def fetch_order_book(self, symbol, limit=None, params={}):
  996. self.load_markets()
  997. market = self.market(symbol)
  998. request = {
  999. 'symbol': market['id'],
  1000. }
  1001. if limit is not None:
  1002. request['limit'] = limit # default 100, max 5000, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book
  1003. method = 'publicGetDepth'
  1004. if market['future']:
  1005. method = 'fapiPublicGetDepth'
  1006. elif market['delivery']:
  1007. method = 'dapiPublicGetDepth'
  1008. response = getattr(self, method)(self.extend(request, params))
  1009. orderbook = self.parse_order_book(response)
  1010. orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
  1011. return orderbook
  1012. def parse_ticker(self, ticker, market=None):
  1013. #
  1014. # {
  1015. # symbol: 'ETHBTC',
  1016. # priceChange: '0.00068700',
  1017. # priceChangePercent: '2.075',
  1018. # weightedAvgPrice: '0.03342681',
  1019. # prevClosePrice: '0.03310300',
  1020. # lastPrice: '0.03378900',
  1021. # lastQty: '0.07700000',
  1022. # bidPrice: '0.03378900',
  1023. # bidQty: '7.16800000',
  1024. # askPrice: '0.03379000',
  1025. # askQty: '24.00000000',
  1026. # openPrice: '0.03310200',
  1027. # highPrice: '0.03388900',
  1028. # lowPrice: '0.03306900',
  1029. # volume: '205478.41000000',
  1030. # quoteVolume: '6868.48826294',
  1031. # openTime: 1601469986932,
  1032. # closeTime: 1601556386932,
  1033. # firstId: 196098772,
  1034. # lastId: 196186315,
  1035. # count: 87544
  1036. # }
  1037. #
  1038. timestamp = self.safe_integer(ticker, 'closeTime')
  1039. marketId = self.safe_string(ticker, 'symbol')
  1040. symbol = self.safe_symbol(marketId, market)
  1041. last = self.safe_float(ticker, 'lastPrice')
  1042. return {
  1043. 'symbol': symbol,
  1044. 'timestamp': timestamp,
  1045. 'datetime': self.iso8601(timestamp),
  1046. 'high': self.safe_float(ticker, 'highPrice'),
  1047. 'low': self.safe_float(ticker, 'lowPrice'),
  1048. 'bid': self.safe_float(ticker, 'bidPrice'),
  1049. 'bidVolume': self.safe_float(ticker, 'bidQty'),
  1050. 'ask': self.safe_float(ticker, 'askPrice'),
  1051. 'askVolume': self.safe_float(ticker, 'askQty'),
  1052. 'vwap': self.safe_float(ticker, 'weightedAvgPrice'),
  1053. 'open': self.safe_float(ticker, 'openPrice'),
  1054. 'close': last,
  1055. 'last': last,
  1056. 'previousClose': self.safe_float(ticker, 'prevClosePrice'), # previous day close
  1057. 'change': self.safe_float(ticker, 'priceChange'),
  1058. 'percentage': self.safe_float(ticker, 'priceChangePercent'),
  1059. 'average': None,
  1060. 'baseVolume': self.safe_float(ticker, 'volume'),
  1061. 'quoteVolume': self.safe_float(ticker, 'quoteVolume'),
  1062. 'info': ticker,
  1063. }
  1064. def fetch_status(self, params={}):
  1065. response = self.wapiGetSystemStatus(params)
  1066. status = self.safe_value(response, 'status')
  1067. if status is not None:
  1068. status = 'ok' if (status == 0) else 'maintenance'
  1069. self.status = self.extend(self.status, {
  1070. 'status': status,
  1071. 'updated': self.milliseconds(),
  1072. })
  1073. return self.status
  1074. def fetch_ticker(self, symbol, params={}):
  1075. self.load_markets()
  1076. market = self.market(symbol)
  1077. request = {
  1078. 'symbol': market['id'],
  1079. }
  1080. method = 'publicGetTicker24hr'
  1081. if market['future']:
  1082. method = 'fapiPublicGetTicker24hr'
  1083. elif market['delivery']:
  1084. method = 'dapiPublicGetTicker24hr'
  1085. response = getattr(self, method)(self.extend(request, params))
  1086. return self.parse_ticker(response, market)
  1087. def parse_tickers(self, rawTickers, symbols=None):
  1088. tickers = []
  1089. for i in range(0, len(rawTickers)):
  1090. tickers.append(self.parse_ticker(rawTickers[i]))
  1091. return self.filter_by_array(tickers, 'symbol', symbols)
  1092. def fetch_bids_asks(self, symbols=None, params={}):
  1093. self.load_markets()
  1094. defaultType = self.safe_string_2(self.options, 'fetchBidsAsks', 'defaultType', 'spot')
  1095. type = self.safe_string(params, 'type', defaultType)
  1096. query = self.omit(params, 'type')
  1097. method = None
  1098. if type == 'future':
  1099. method = 'fapiPublicGetTickerBookTicker'
  1100. elif type == 'delivery':
  1101. method = 'dapiPublicGetTickerBookTicker'
  1102. else:
  1103. method = 'publicGetTickerBookTicker'
  1104. response = getattr(self, method)(query)
  1105. return self.parse_tickers(response, symbols)
  1106. def fetch_tickers(self, symbols=None, params={}):
  1107. self.load_markets()
  1108. defaultType = self.safe_string_2(self.options, 'fetchTickers', 'defaultType', 'spot')
  1109. type = self.safe_string(params, 'type', defaultType)
  1110. query = self.omit(params, 'type')
  1111. defaultMethod = None
  1112. if type == 'future':
  1113. defaultMethod = 'fapiPublicGetTicker24hr'
  1114. elif type == 'delivery':
  1115. defaultMethod = 'dapiPublicGetTicker24hr'
  1116. else:
  1117. defaultMethod = 'publicGetTicker24hr'
  1118. method = self.safe_string(self.options, 'fetchTickersMethod', defaultMethod)
  1119. response = getattr(self, method)(query)
  1120. return self.parse_tickers(response, symbols)
  1121. def parse_ohlcv(self, ohlcv, market=None):
  1122. #
  1123. # [
  1124. # 1591478520000,
  1125. # "0.02501300",
  1126. # "0.02501800",
  1127. # "0.02500000",
  1128. # "0.02500000",
  1129. # "22.19000000",
  1130. # 1591478579999,
  1131. # "0.55490906",
  1132. # 40,
  1133. # "10.92900000",
  1134. # "0.27336462",
  1135. # "0"
  1136. # ]
  1137. #
  1138. return [
  1139. self.safe_integer(ohlcv, 0),
  1140. self.safe_float(ohlcv, 1),
  1141. self.safe_float(ohlcv, 2),
  1142. self.safe_float(ohlcv, 3),
  1143. self.safe_float(ohlcv, 4),
  1144. self.safe_float(ohlcv, 5),
  1145. ]
  1146. def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  1147. self.load_markets()
  1148. market = self.market(symbol)
  1149. request = {
  1150. 'symbol': market['id'],
  1151. 'interval': self.timeframes[timeframe],
  1152. }
  1153. if since is not None:
  1154. request['startTime'] = since
  1155. if limit is not None:
  1156. request['limit'] = limit # default == max == 500
  1157. method = 'publicGetKlines'
  1158. if market['future']:
  1159. method = 'fapiPublicGetKlines'
  1160. elif market['delivery']:
  1161. method = 'dapiPublicGetKlines'
  1162. response = getattr(self, method)(self.extend(request, params))
  1163. #
  1164. # [
  1165. # [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"],
  1166. # [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"],
  1167. # [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"],
  1168. # ]
  1169. #
  1170. return self.parse_ohlcvs(response, market, timeframe, since, limit)
  1171. def parse_trade(self, trade, market=None):
  1172. if 'isDustTrade' in trade:
  1173. return self.parse_dust_trade(trade, market)
  1174. #
  1175. # aggregate trades
  1176. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
  1177. #
  1178. # {
  1179. # "a": 26129, # Aggregate tradeId
  1180. # "p": "0.01633102", # Price
  1181. # "q": "4.70443515", # Quantity
  1182. # "f": 27781, # First tradeId
  1183. # "l": 27781, # Last tradeId
  1184. # "T": 1498793709153, # Timestamp
  1185. # "m": True, # Was the buyer the maker?
  1186. # "M": True # Was the trade the best price match?
  1187. # }
  1188. #
  1189. # recent public trades and old public trades
  1190. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list
  1191. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data
  1192. #
  1193. # {
  1194. # "id": 28457,
  1195. # "price": "4.00000100",
  1196. # "qty": "12.00000000",
  1197. # "time": 1499865549590,
  1198. # "isBuyerMaker": True,
  1199. # "isBestMatch": True
  1200. # }
  1201. #
  1202. # private trades
  1203. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data
  1204. #
  1205. # {
  1206. # "symbol": "BNBBTC",
  1207. # "id": 28457,
  1208. # "orderId": 100234,
  1209. # "price": "4.00000100",
  1210. # "qty": "12.00000000",
  1211. # "commission": "10.10000000",
  1212. # "commissionAsset": "BNB",
  1213. # "time": 1499865549590,
  1214. # "isBuyer": True,
  1215. # "isMaker": False,
  1216. # "isBestMatch": True
  1217. # }
  1218. #
  1219. # futures trades
  1220. # https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data
  1221. #
  1222. # {
  1223. # "accountId": 20,
  1224. # "buyer": False,
  1225. # "commission": "-0.07819010",
  1226. # "commissionAsset": "USDT",
  1227. # "counterPartyId": 653,
  1228. # "id": 698759,
  1229. # "maker": False,
  1230. # "orderId": 25851813,
  1231. # "price": "7819.01",
  1232. # "qty": "0.002",
  1233. # "quoteQty": "0.01563",
  1234. # "realizedPnl": "-0.91539999",
  1235. # "side": "SELL",
  1236. # "symbol": "BTCUSDT",
  1237. # "time": 1569514978020
  1238. # }
  1239. #
  1240. timestamp = self.safe_integer_2(trade, 'T', 'time')
  1241. price = self.safe_float_2(trade, 'p', 'price')
  1242. amount = self.safe_float_2(trade, 'q', 'qty')
  1243. id = self.safe_string_2(trade, 'a', 'id')
  1244. side = None
  1245. orderId = self.safe_string(trade, 'orderId')
  1246. if 'm' in trade:
  1247. side = 'sell' if trade['m'] else 'buy' # self is reversed intentionally
  1248. elif 'isBuyerMaker' in trade:
  1249. side = 'sell' if trade['isBuyerMaker'] else 'buy'
  1250. elif 'side' in trade:
  1251. side = self.safe_string_lower(trade, 'side')
  1252. else:
  1253. if 'isBuyer' in trade:
  1254. side = 'buy' if trade['isBuyer'] else 'sell' # self is a True side
  1255. fee = None
  1256. if 'commission' in trade:
  1257. fee = {
  1258. 'cost': self.safe_float(trade, 'commission'),
  1259. 'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
  1260. }
  1261. takerOrMaker = None
  1262. if 'isMaker' in trade:
  1263. takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
  1264. marketId = self.safe_string(trade, 'symbol')
  1265. symbol = self.safe_symbol(marketId, market)
  1266. cost = None
  1267. if (price is not None) and (amount is not None):
  1268. cost = price * amount
  1269. return {
  1270. 'info': trade,
  1271. 'timestamp': timestamp,
  1272. 'datetime': self.iso8601(timestamp),
  1273. 'symbol': symbol,
  1274. 'id': id,
  1275. 'order': orderId,
  1276. 'type': None,
  1277. 'side': side,
  1278. 'takerOrMaker': takerOrMaker,
  1279. 'price': price,
  1280. 'amount': amount,
  1281. 'cost': cost,
  1282. 'fee': fee,
  1283. }
  1284. def fetch_trades(self, symbol, since=None, limit=None, params={}):
  1285. self.load_markets()
  1286. market = self.market(symbol)
  1287. request = {
  1288. 'symbol': market['id'],
  1289. # 'fromId': 123, # ID to get aggregate trades from INCLUSIVE.
  1290. # 'startTime': 456, # Timestamp in ms to get aggregate trades from INCLUSIVE.
  1291. # 'endTime': 789, # Timestamp in ms to get aggregate trades until INCLUSIVE.
  1292. # 'limit': 500, # default = 500, maximum = 1000
  1293. }
  1294. defaultType = self.safe_string_2(self.options, 'fetchTrades', 'defaultType', 'spot')
  1295. type = self.safe_string(params, 'type', defaultType)
  1296. query = self.omit(params, 'type')
  1297. defaultMethod = None
  1298. if type == 'future':
  1299. defaultMethod = 'fapiPublicGetAggTrades'
  1300. elif type == 'delivery':
  1301. defaultMethod = 'dapiPublicGetAggTrades'
  1302. else:
  1303. defaultMethod = 'publicGetAggTrades'
  1304. method = self.safe_string(self.options, 'fetchTradesMethod', defaultMethod)
  1305. if method == 'publicGetAggTrades':
  1306. if since is not None:
  1307. request['startTime'] = since
  1308. # https://github.com/ccxt/ccxt/issues/6400
  1309. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
  1310. request['endTime'] = self.sum(since, 3600000)
  1311. if type == 'future':
  1312. method = 'fapiPublicGetAggTrades'
  1313. elif type == 'delivery':
  1314. method = 'dapiPublicGetAggTrades'
  1315. elif method == 'publicGetHistoricalTrades':
  1316. if type == 'future':
  1317. method = 'fapiPublicGetHistoricalTrades'
  1318. elif type == 'delivery':
  1319. method = 'dapiPublicGetHistoricalTrades'
  1320. if limit is not None:
  1321. request['limit'] = limit # default = 500, maximum = 1000
  1322. #
  1323. # Caveats:
  1324. # - default limit(500) applies only if no other parameters set, trades up
  1325. # to the maximum limit may be returned to satisfy other parameters
  1326. # - if both limit and time window is set and time window contains more
  1327. # trades than the limit then the last trades from the window are returned
  1328. # - 'tradeId' accepted and returned by self method is "aggregate" trade id
  1329. # which is different from actual trade id
  1330. # - setting both fromId and time window results in error
  1331. response = getattr(self, method)(self.extend(request, query))
  1332. #
  1333. # aggregate trades
  1334. #
  1335. # [
  1336. # {
  1337. # "a": 26129, # Aggregate tradeId
  1338. # "p": "0.01633102", # Price
  1339. # "q": "4.70443515", # Quantity
  1340. # "f": 27781, # First tradeId
  1341. # "l": 27781, # Last tradeId
  1342. # "T": 1498793709153, # Timestamp
  1343. # "m": True, # Was the buyer the maker?
  1344. # "M": True # Was the trade the best price match?
  1345. # }
  1346. # ]
  1347. #
  1348. # recent public trades and historical public trades
  1349. #
  1350. # [
  1351. # {
  1352. # "id": 28457,
  1353. # "price": "4.00000100",
  1354. # "qty": "12.00000000",
  1355. # "time": 1499865549590,
  1356. # "isBuyerMaker": True,
  1357. # "isBestMatch": True
  1358. # }
  1359. # ]
  1360. #
  1361. return self.parse_trades(response, market, since, limit)
  1362. def parse_order_status(self, status):
  1363. statuses = {
  1364. 'NEW': 'open',
  1365. 'PARTIALLY_FILLED': 'open',
  1366. 'FILLED': 'closed',
  1367. 'CANCELED': 'canceled',
  1368. 'PENDING_CANCEL': 'canceling', # currently unused
  1369. 'REJECTED': 'rejected',
  1370. 'EXPIRED': 'canceled',
  1371. }
  1372. return self.safe_string(statuses, status, status)
  1373. def parse_order(self, order, market=None):
  1374. #
  1375. # spot
  1376. #
  1377. # {
  1378. # "symbol": "LTCBTC",
  1379. # "orderId": 1,
  1380. # "clientOrderId": "myOrder1",
  1381. # "price": "0.1",
  1382. # "origQty": "1.0",
  1383. # "executedQty": "0.0",
  1384. # "cummulativeQuoteQty": "0.0",
  1385. # "status": "NEW",
  1386. # "timeInForce": "GTC",
  1387. # "type": "LIMIT",
  1388. # "side": "BUY",
  1389. # "stopPrice": "0.0",
  1390. # "icebergQty": "0.0",
  1391. # "time": 1499827319559,
  1392. # "updateTime": 1499827319559,
  1393. # "isWorking": True
  1394. # }
  1395. #
  1396. # futures
  1397. #
  1398. # {
  1399. # "symbol": "BTCUSDT",
  1400. # "orderId": 1,
  1401. # "clientOrderId": "myOrder1",
  1402. # "price": "0.1",
  1403. # "origQty": "1.0",
  1404. # "executedQty": "1.0",
  1405. # "cumQuote": "10.0",
  1406. # "status": "NEW",
  1407. # "timeInForce": "GTC",
  1408. # "type": "LIMIT",
  1409. # "side": "BUY",
  1410. # "stopPrice": "0.0",
  1411. # "updateTime": 1499827319559
  1412. # }
  1413. #
  1414. status = self.parse_order_status(self.safe_string(order, 'status'))
  1415. marketId = self.safe_string(order, 'symbol')
  1416. symbol = self.safe_symbol(marketId, market)
  1417. timestamp = None
  1418. if 'time' in order:
  1419. timestamp = self.safe_integer(order, 'time')
  1420. elif 'transactTime' in order:
  1421. timestamp = self.safe_integer(order, 'transactTime')
  1422. price = self.safe_float(order, 'price')
  1423. amount = self.safe_float(order, 'origQty')
  1424. filled = self.safe_float(order, 'executedQty')
  1425. remaining = None
  1426. # - Spot/Margin market: cummulativeQuoteQty
  1427. # - Futures market: cumQuote.
  1428. # Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
  1429. cost = self.safe_float_2(order, 'cummulativeQuoteQty', 'cumQuote')
  1430. if filled is not None:
  1431. if amount is not None:
  1432. remaining = amount - filled
  1433. if self.options['parseOrderToPrecision']:
  1434. remaining = float(self.amount_to_precision(symbol, remaining))
  1435. remaining = max(remaining, 0.0)
  1436. if price is not None:
  1437. if cost is None:
  1438. cost = price * filled
  1439. id = self.safe_string(order, 'orderId')
  1440. type = self.safe_string_lower(order, 'type')
  1441. if type == 'market':
  1442. if price == 0.0:
  1443. if (cost is not None) and (filled is not None):
  1444. if (cost > 0) and (filled > 0):
  1445. price = cost / filled
  1446. if self.options['parseOrderToPrecision']:
  1447. price = float(self.price_to_precision(symbol, price))
  1448. elif type == 'limit_maker':
  1449. type = 'limit'
  1450. side = self.safe_string_lower(order, 'side')
  1451. fee = None
  1452. trades = None
  1453. fills = self.safe_value(order, 'fills')
  1454. if fills is not None:
  1455. trades = self.parse_trades(fills, market)
  1456. numTrades = len(trades)
  1457. if numTrades > 0:
  1458. cost = trades[0]['cost']
  1459. fee = {
  1460. 'cost': trades[0]['fee']['cost'],
  1461. 'currency': trades[0]['fee']['currency'],
  1462. }
  1463. for i in range(1, len(trades)):
  1464. cost = self.sum(cost, trades[i]['cost'])
  1465. fee['cost'] = self.sum(fee['cost'], trades[i]['fee']['cost'])
  1466. average = None
  1467. if cost is not None:
  1468. if filled:
  1469. average = cost / filled
  1470. if self.options['parseOrderToPrecision']:
  1471. average = float(self.price_to_precision(symbol, average))
  1472. if self.options['parseOrderToPrecision']:
  1473. cost = float(self.cost_to_precision(symbol, cost))
  1474. clientOrderId = self.safe_string(order, 'clientOrderId')
  1475. return {
  1476. 'info': order,
  1477. 'id': id,
  1478. 'clientOrderId': clientOrderId,
  1479. 'timestamp': timestamp,
  1480. 'datetime': self.iso8601(timestamp),
  1481. 'lastTradeTimestamp': None,
  1482. 'symbol': symbol,
  1483. 'type': type,
  1484. 'side': side,
  1485. 'price': price,
  1486. 'amount': amount,
  1487. 'cost': cost,
  1488. 'average': average,
  1489. 'filled': filled,
  1490. 'remaining': remaining,
  1491. 'status': status,
  1492. 'fee': fee,
  1493. 'trades': trades,
  1494. }
  1495. def create_order(self, symbol, type, side, amount, price=None, params={}):
  1496. self.load_markets()
  1497. market = self.market(symbol)
  1498. defaultType = self.safe_string_2(self.options, 'createOrder', 'defaultType', market['type'])
  1499. orderType = self.safe_string(params, 'type', defaultType)
  1500. clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
  1501. params = self.omit(params, ['type', 'newClientOrderId', 'clientOrderId'])
  1502. method = 'privatePostOrder'
  1503. if orderType == 'future':
  1504. method = 'fapiPrivatePostOrder'
  1505. elif orderType == 'delivery':
  1506. method = 'dapiPrivatePostOrder'
  1507. elif orderType == 'margin':
  1508. method = 'sapiPostMarginOrder'
  1509. # the next 5 lines are added to support for testing orders
  1510. if market['spot']:
  1511. test = self.safe_value(params, 'test', False)
  1512. if test:
  1513. method += 'Test'
  1514. params = self.omit(params, 'test')
  1515. uppercaseType = type.upper()
  1516. validOrderTypes = self.safe_value(market['info'], 'orderTypes')
  1517. if not self.in_array(uppercaseType, validOrderTypes):
  1518. raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type in ' + market['type'] + ' market ' + symbol)
  1519. request = {
  1520. 'symbol': market['id'],
  1521. 'type': uppercaseType,
  1522. 'side': side.upper(),
  1523. }
  1524. if clientOrderId is not None:
  1525. request['newClientOrderId'] = clientOrderId
  1526. if market['spot']:
  1527. request['newOrderRespType'] = self.safe_value(self.options['newOrderRespType'], type, 'RESULT') # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
  1528. # additional required fields depending on the order type
  1529. timeInForceIsRequired = False
  1530. priceIsRequired = False
  1531. stopPriceIsRequired = False
  1532. quantityIsRequired = False
  1533. #
  1534. # spot/margin
  1535. #
  1536. # LIMIT timeInForce, quantity, price
  1537. # MARKET quantity or quoteOrderQty
  1538. # STOP_LOSS quantity, stopPrice
  1539. # STOP_LOSS_LIMIT timeInForce, quantity, price, stopPrice
  1540. # TAKE_PROFIT quantity, stopPrice
  1541. # TAKE_PROFIT_LIMIT timeInForce, quantity, price, stopPrice
  1542. # LIMIT_MAKER quantity, price
  1543. #
  1544. # futures
  1545. #
  1546. # LIMIT timeInForce, quantity, price
  1547. # MARKET quantity
  1548. # STOP/TAKE_PROFIT quantity, price, stopPrice
  1549. # STOP_MARKET stopPrice
  1550. # TAKE_PROFIT_MARKET stopPrice
  1551. # TRAILING_STOP_MARKET callbackRate
  1552. #
  1553. if uppercaseType == 'MARKET':
  1554. quoteOrderQty = self.safe_value(self.options, 'quoteOrderQty', False)
  1555. if quoteOrderQty:
  1556. quoteOrderQty = self.safe_float(params, 'quoteOrderQty')
  1557. precision = market['precision']['price']
  1558. if quoteOrderQty is not None:
  1559. request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQty, TRUNCATE, precision, self.precisionMode)
  1560. params = self.omit(params, 'quoteOrderQty')
  1561. elif price is not None:
  1562. request['quoteOrderQty'] = self.decimal_to_precision(amount * price, TRUNCATE, precision, self.precisionMode)
  1563. else:
  1564. quantityIsRequired = True
  1565. else:
  1566. quantityIsRequired = True
  1567. elif uppercaseType == 'LIMIT':
  1568. priceIsRequired = True
  1569. timeInForceIsRequired = True
  1570. quantityIsRequired = True
  1571. elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
  1572. stopPriceIsRequired = True
  1573. quantityIsRequired = True
  1574. if market['future']:
  1575. priceIsRequired = True
  1576. elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
  1577. quantityIsRequired = True
  1578. stopPriceIsRequired = True
  1579. priceIsRequired = True
  1580. timeInForceIsRequired = True
  1581. elif uppercaseType == 'LIMIT_MAKER':
  1582. priceIsRequired = True
  1583. quantityIsRequired = True
  1584. elif uppercaseType == 'STOP':
  1585. quantityIsRequired = True
  1586. stopPriceIsRequired = True
  1587. priceIsRequired = True
  1588. elif (uppercaseType == 'STOP_MARKET') or (uppercaseType == 'TAKE_PROFIT_MARKET'):
  1589. closePosition = self.safe_value(params, 'closePosition')
  1590. if closePosition is None:
  1591. quantityIsRequired = True
  1592. stopPriceIsRequired = True
  1593. elif uppercaseType == 'TRAILING_STOP_MARKET':
  1594. quantityIsRequired = True
  1595. callbackRate = self.safe_float(params, 'callbackRate')
  1596. if callbackRate is None:
  1597. raise InvalidOrder(self.id + ' createOrder method requires a callbackRate extra param for a ' + type + ' order')
  1598. if quantityIsRequired:
  1599. request['quantity'] = self.amount_to_precision(symbol, amount)
  1600. if priceIsRequired:
  1601. if price is None:
  1602. raise InvalidOrder(self.id + ' createOrder method requires a price argument for a ' + type + ' order')
  1603. request['price'] = self.price_to_precision(symbol, price)
  1604. if timeInForceIsRequired:
  1605. request['timeInForce'] = self.options['defaultTimeInForce'] # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
  1606. if stopPriceIsRequired:
  1607. stopPrice = self.safe_float(params, 'stopPrice')
  1608. if stopPrice is None:
  1609. raise InvalidOrder(self.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order')
  1610. else:
  1611. params = self.omit(params, 'stopPrice')
  1612. request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
  1613. response = getattr(self, method)(self.extend(request, params))
  1614. return self.parse_order(response, market)
  1615. def fetch_order(self, id, symbol=None, params={}):
  1616. if symbol is None:
  1617. raise ArgumentsRequired(self.id + ' fetchOrder requires a symbol argument')
  1618. self.load_markets()
  1619. market = self.market(symbol)
  1620. defaultType = self.safe_string_2(self.options, 'fetchOrder', 'defaultType', market['type'])
  1621. type = self.safe_string(params, 'type', defaultType)
  1622. method = 'privateGetOrder'
  1623. if type == 'future':
  1624. method = 'fapiPrivateGetOrder'
  1625. elif type == 'delivery':
  1626. method = 'dapiPrivateGetOrder'
  1627. elif type == 'margin':
  1628. method = 'sapiGetMarginOrder'
  1629. request = {
  1630. 'symbol': market['id'],
  1631. }
  1632. clientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
  1633. if clientOrderId is not None:
  1634. request['origClientOrderId'] = clientOrderId
  1635. else:
  1636. request['orderId'] = int(id)
  1637. query = self.omit(params, ['type', 'clientOrderId', 'origClientOrderId'])
  1638. response = getattr(self, method)(self.extend(request, query))
  1639. return self.parse_order(response, market)
  1640. def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
  1641. if symbol is None:
  1642. raise ArgumentsRequired(self.id + ' fetchOrders requires a symbol argument')
  1643. self.load_markets()
  1644. market = self.market(symbol)
  1645. defaultType = self.safe_string_2(self.options, 'fetchOrders', 'defaultType', market['type'])
  1646. type = self.safe_string(params, 'type', defaultType)
  1647. method = 'privateGetAllOrders'
  1648. if type == 'future':
  1649. method = 'fapiPrivateGetAllOrders'
  1650. elif type == 'delivery':
  1651. method = 'dapiPrivateGetAllOrders'
  1652. elif type == 'margin':
  1653. method = 'sapiGetMarginAllOrders'
  1654. request = {
  1655. 'symbol': market['id'],
  1656. }
  1657. if since is not None:
  1658. request['startTime'] = since
  1659. if limit is not None:
  1660. request['limit'] = limit
  1661. query = self.omit(params, 'type')
  1662. response = getattr(self, method)(self.extend(request, query))
  1663. #
  1664. # spot
  1665. #
  1666. # [
  1667. # {
  1668. # "symbol": "LTCBTC",
  1669. # "orderId": 1,
  1670. # "clientOrderId": "myOrder1",
  1671. # "price": "0.1",
  1672. # "origQty": "1.0",
  1673. # "executedQty": "0.0",
  1674. # "cummulativeQuoteQty": "0.0",
  1675. # "status": "NEW",
  1676. # "timeInForce": "GTC",
  1677. # "type": "LIMIT",
  1678. # "side": "BUY",
  1679. # "stopPrice": "0.0",
  1680. # "icebergQty": "0.0",
  1681. # "time": 1499827319559,
  1682. # "updateTime": 1499827319559,
  1683. # "isWorking": True
  1684. # }
  1685. # ]
  1686. #
  1687. # futures
  1688. #
  1689. # [
  1690. # {
  1691. # "symbol": "BTCUSDT",
  1692. # "orderId": 1,
  1693. # "clientOrderId": "myOrder1",
  1694. # "price": "0.1",
  1695. # "origQty": "1.0",
  1696. # "executedQty": "1.0",
  1697. # "cumQuote": "10.0",
  1698. # "status": "NEW",
  1699. # "timeInForce": "GTC",
  1700. # "type": "LIMIT",
  1701. # "side": "BUY",
  1702. # "stopPrice": "0.0",
  1703. # "updateTime": 1499827319559
  1704. # }
  1705. # ]
  1706. #
  1707. return self.parse_orders(response, market, since, limit)
  1708. def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  1709. self.load_markets()
  1710. market = None
  1711. query = None
  1712. type = None
  1713. request = {}
  1714. if symbol is not None:
  1715. market = self.market(symbol)
  1716. request['symbol'] = market['id']
  1717. defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', market['type'])
  1718. type = self.safe_string(params, 'type', defaultType)
  1719. query = self.omit(params, 'type')
  1720. elif self.options['warnOnFetchOpenOrdersWithoutSymbol']:
  1721. symbols = self.symbols
  1722. numSymbols = len(symbols)
  1723. fetchOpenOrdersRateLimit = int(numSymbols / 2)
  1724. raise ExchangeError(self.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + str(fetchOpenOrdersRateLimit) + ' seconds. Do not call self method frequently to avoid ban. Set ' + self.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = False to suppress self warning message.')
  1725. else:
  1726. defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', 'spot')
  1727. type = self.safe_string(params, 'type', defaultType)
  1728. query = self.omit(params, 'type')
  1729. method = 'privateGetOpenOrders'
  1730. if type == 'future':
  1731. method = 'fapiPrivateGetOpenOrders'
  1732. elif type == 'delivery':
  1733. method = 'dapiPrivateGetOpenOrders'
  1734. elif type == 'margin':
  1735. method = 'sapiGetMarginOpenOrders'
  1736. response = getattr(self, method)(self.extend(request, query))
  1737. return self.parse_orders(response, market, since, limit)
  1738. def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
  1739. orders = self.fetch_orders(symbol, since, limit, params)
  1740. return self.filter_by(orders, 'status', 'closed')
  1741. def cancel_order(self, id, symbol=None, params={}):
  1742. if symbol is None:
  1743. raise ArgumentsRequired(self.id + ' cancelOrder requires a symbol argument')
  1744. self.load_markets()
  1745. market = self.market(symbol)
  1746. defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', market['type'])
  1747. type = self.safe_string(params, 'type', defaultType)
  1748. # https://github.com/ccxt/ccxt/issues/6507
  1749. origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
  1750. request = {
  1751. 'symbol': market['id'],
  1752. # 'orderId': int(id),
  1753. # 'origClientOrderId': id,
  1754. }
  1755. if origClientOrderId is None:
  1756. request['orderId'] = int(id)
  1757. else:
  1758. request['origClientOrderId'] = origClientOrderId
  1759. method = 'privateDeleteOrder'
  1760. if type == 'future':
  1761. method = 'fapiPrivateDeleteOrder'
  1762. elif type == 'delivery':
  1763. method = 'dapiPrivateDeleteOrder'
  1764. elif type == 'margin':
  1765. method = 'sapiDeleteMarginOrder'
  1766. query = self.omit(params, ['type', 'origClientOrderId', 'clientOrderId'])
  1767. response = getattr(self, method)(self.extend(request, query))
  1768. return self.parse_order(response)
  1769. def cancel_all_orders(self, symbol=None, params={}):
  1770. if symbol is None:
  1771. raise ArgumentsRequired(self.id + ' cancelAllOrders requires a symbol argument')
  1772. self.load_markets()
  1773. market = self.market(symbol)
  1774. request = {
  1775. 'symbol': market['id'],
  1776. }
  1777. defaultType = self.safe_string_2(self.options, 'cancelAllOrders', 'defaultType', 'spot')
  1778. type = self.safe_string(params, 'type', defaultType)
  1779. query = self.omit(params, 'type')
  1780. method = 'privateDeleteOpenOrders'
  1781. if type == 'future':
  1782. method = 'fapiPrivateDeleteAllOpenOrders'
  1783. elif type == 'delivery':
  1784. method = 'dapiPrivateDeleteAllOpenOrders'
  1785. response = getattr(self, method)(self.extend(request, query))
  1786. if isinstance(response, list):
  1787. return self.parse_orders(response, market)
  1788. else:
  1789. return response
  1790. def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  1791. if symbol is None:
  1792. raise ArgumentsRequired(self.id + ' fetchMyTrades requires a symbol argument')
  1793. self.load_markets()
  1794. market = self.market(symbol)
  1795. type = self.safe_value(params, 'type', market['type'])
  1796. method = None
  1797. if type == 'spot':
  1798. method = 'privateGetMyTrades'
  1799. elif type == 'future':
  1800. method = 'fapiPrivateGetUserTrades'
  1801. elif type == 'delivery':
  1802. method = 'dapiPrivateGetUserTrades'
  1803. params = self.omit(params, 'type')
  1804. request = {
  1805. 'symbol': market['id'],
  1806. }
  1807. if since is not None:
  1808. request['startTime'] = since
  1809. if limit is not None:
  1810. request['limit'] = limit
  1811. response = getattr(self, method)(self.extend(request, params))
  1812. #
  1813. # spot trade
  1814. #
  1815. # [
  1816. # {
  1817. # "symbol": "BNBBTC",
  1818. # "id": 28457,
  1819. # "orderId": 100234,
  1820. # "price": "4.00000100",
  1821. # "qty": "12.00000000",
  1822. # "commission": "10.10000000",
  1823. # "commissionAsset": "BNB",
  1824. # "time": 1499865549590,
  1825. # "isBuyer": True,
  1826. # "isMaker": False,
  1827. # "isBestMatch": True,
  1828. # }
  1829. # ]
  1830. #
  1831. # futures trade
  1832. #
  1833. # [
  1834. # {
  1835. # "accountId": 20,
  1836. # "buyer": False,
  1837. # "commission": "-0.07819010",
  1838. # "commissionAsset": "USDT",
  1839. # "counterPartyId": 653,
  1840. # "id": 698759,
  1841. # "maker": False,
  1842. # "orderId": 25851813,
  1843. # "price": "7819.01",
  1844. # "qty": "0.002",
  1845. # "quoteQty": "0.01563",
  1846. # "realizedPnl": "-0.91539999",
  1847. # "side": "SELL",
  1848. # "symbol": "BTCUSDT",
  1849. # "time": 1569514978020
  1850. # }
  1851. # ]
  1852. #
  1853. return self.parse_trades(response, market, since, limit)
  1854. def fetch_my_dust_trades(self, symbol=None, since=None, limit=None, params={}):
  1855. #
  1856. # Binance provides an opportunity to trade insignificant(i.e. non-tradable and non-withdrawable)
  1857. # token leftovers(of any asset) into `BNB` coin which in turn can be used to pay trading fees with it.
  1858. # The corresponding trades history is called the `Dust Log` and can be requested via the following end-point:
  1859. # https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data
  1860. #
  1861. self.load_markets()
  1862. response = self.wapiGetUserAssetDribbletLog(params)
  1863. # {success: True,
  1864. # results: {total: 1,
  1865. # rows: [{ transfered_total: "1.06468458",
  1866. # service_charge_total: "0.02172826",
  1867. # tran_id: 2701371634,
  1868. # logs: [{ tranId: 2701371634,
  1869. # serviceChargeAmount: "0.00012819",
  1870. # uid: "35103861",
  1871. # amount: "0.8012",
  1872. # operateTime: "2018-10-07 17:56:07",
  1873. # transferedAmount: "0.00628141",
  1874. # fromAsset: "ADA" }],
  1875. # operate_time: "2018-10-07 17:56:06" }]}}
  1876. results = self.safe_value(response, 'results', {})
  1877. rows = self.safe_value(results, 'rows', [])
  1878. data = []
  1879. for i in range(0, len(rows)):
  1880. logs = rows[i]['logs']
  1881. for j in range(0, len(logs)):
  1882. logs[j]['isDustTrade'] = True
  1883. data.append(logs[j])
  1884. trades = self.parse_trades(data, None, since, limit)
  1885. return self.filter_by_since_limit(trades, since, limit)
  1886. def parse_dust_trade(self, trade, market=None):
  1887. # { tranId: 2701371634,
  1888. # serviceChargeAmount: "0.00012819",
  1889. # uid: "35103861",
  1890. # amount: "0.8012",
  1891. # operateTime: "2018-10-07 17:56:07",
  1892. # transferedAmount: "0.00628141",
  1893. # fromAsset: "ADA" },
  1894. orderId = self.safe_string(trade, 'tranId')
  1895. timestamp = self.parse8601(self.safe_string(trade, 'operateTime'))
  1896. tradedCurrency = self.safe_currency_code(self.safe_string(trade, 'fromAsset'))
  1897. earnedCurrency = self.currency('BNB')['code']
  1898. applicantSymbol = earnedCurrency + '/' + tradedCurrency
  1899. tradedCurrencyIsQuote = False
  1900. if applicantSymbol in self.markets:
  1901. tradedCurrencyIsQuote = True
  1902. #
  1903. # Warning
  1904. # Binance dust trade `fee` is already excluded from the `BNB` earning reported in the `Dust Log`.
  1905. # So the parser should either set the `fee.cost` to `0` or add it on top of the earned
  1906. # BNB `amount`(or `cost` depending on the trade `side`). The second of the above options
  1907. # is much more illustrative and therefore preferable.
  1908. #
  1909. fee = {
  1910. 'currency': earnedCurrency,
  1911. 'cost': self.safe_float(trade, 'serviceChargeAmount'),
  1912. }
  1913. symbol = None
  1914. amount = None
  1915. cost = None
  1916. side = None
  1917. if tradedCurrencyIsQuote:
  1918. symbol = applicantSymbol
  1919. amount = self.sum(self.safe_float(trade, 'transferedAmount'), fee['cost'])
  1920. cost = self.safe_float(trade, 'amount')
  1921. side = 'buy'
  1922. else:
  1923. symbol = tradedCurrency + '/' + earnedCurrency
  1924. amount = self.safe_float(trade, 'amount')
  1925. cost = self.sum(self.safe_float(trade, 'transferedAmount'), fee['cost'])
  1926. side = 'sell'
  1927. price = None
  1928. if cost is not None:
  1929. if amount:
  1930. price = cost / amount
  1931. id = None
  1932. type = None
  1933. takerOrMaker = None
  1934. return {
  1935. 'id': id,
  1936. 'timestamp': timestamp,
  1937. 'datetime': self.iso8601(timestamp),
  1938. 'symbol': symbol,
  1939. 'order': orderId,
  1940. 'type': type,
  1941. 'takerOrMaker': takerOrMaker,
  1942. 'side': side,
  1943. 'amount': amount,
  1944. 'price': price,
  1945. 'cost': cost,
  1946. 'fee': fee,
  1947. 'info': trade,
  1948. }
  1949. def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  1950. self.load_markets()
  1951. currency = None
  1952. request = {}
  1953. if code is not None:
  1954. currency = self.currency(code)
  1955. request['asset'] = currency['id']
  1956. if since is not None:
  1957. request['startTime'] = since
  1958. # max 3 months range https://github.com/ccxt/ccxt/issues/6495
  1959. request['endTime'] = self.sum(since, 7776000000)
  1960. response = self.wapiGetDepositHistory(self.extend(request, params))
  1961. #
  1962. # { success: True,
  1963. # depositList: [{insertTime: 1517425007000,
  1964. # amount: 0.3,
  1965. # address: "0x0123456789abcdef",
  1966. # addressTag: "",
  1967. # txId: "0x0123456789abcdef",
  1968. # asset: "ETH",
  1969. # status: 1 }]}
  1970. #
  1971. return self.parse_transactions(response['depositList'], currency, since, limit)
  1972. def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  1973. self.load_markets()
  1974. currency = None
  1975. request = {}
  1976. if code is not None:
  1977. currency = self.currency(code)
  1978. request['asset'] = currency['id']
  1979. if since is not None:
  1980. request['startTime'] = since
  1981. # max 3 months range https://github.com/ccxt/ccxt/issues/6495
  1982. request['endTime'] = self.sum(since, 7776000000)
  1983. response = self.wapiGetWithdrawHistory(self.extend(request, params))
  1984. #
  1985. # {withdrawList: [{ amount: 14,
  1986. # address: "0x0123456789abcdef...",
  1987. # successTime: 1514489710000,
  1988. # transactionFee: 0.01,
  1989. # addressTag: "",
  1990. # txId: "0x0123456789abcdef...",
  1991. # id: "0123456789abcdef...",
  1992. # asset: "ETH",
  1993. # applyTime: 1514488724000,
  1994. # status: 6 },
  1995. # { amount: 7600,
  1996. # address: "0x0123456789abcdef...",
  1997. # successTime: 1515323226000,
  1998. # transactionFee: 0.01,
  1999. # addressTag: "",
  2000. # txId: "0x0123456789abcdef...",
  2001. # id: "0123456789abcdef...",
  2002. # asset: "ICN",
  2003. # applyTime: 1515322539000,
  2004. # status: 6 } ],
  2005. # success: True }
  2006. #
  2007. return self.parse_transactions(response['withdrawList'], currency, since, limit)
  2008. def parse_transaction_status_by_type(self, status, type=None):
  2009. statusesByType = {
  2010. 'deposit': {
  2011. '0': 'pending',
  2012. '1': 'ok',
  2013. },
  2014. 'withdrawal': {
  2015. '0': 'pending', # Email Sent
  2016. '1': 'canceled', # Cancelled(different from 1 = ok in deposits)
  2017. '2': 'pending', # Awaiting Approval
  2018. '3': 'failed', # Rejected
  2019. '4': 'pending', # Processing
  2020. '5': 'failed', # Failure
  2021. '6': 'ok', # Completed
  2022. },
  2023. }
  2024. statuses = self.safe_value(statusesByType, type, {})
  2025. return self.safe_string(statuses, status, status)
  2026. def parse_transaction(self, transaction, currency=None):
  2027. #
  2028. # fetchDeposits
  2029. #
  2030. # {
  2031. # insertTime: 1517425007000,
  2032. # amount: 0.3,
  2033. # address: "0x0123456789abcdef",
  2034. # addressTag: "",
  2035. # txId: "0x0123456789abcdef",
  2036. # asset: "ETH",
  2037. # status: 1
  2038. # }
  2039. #
  2040. # fetchWithdrawals
  2041. #
  2042. # {
  2043. # amount: 14,
  2044. # address: "0x0123456789abcdef...",
  2045. # successTime: 1514489710000,
  2046. # transactionFee: 0.01,
  2047. # addressTag: "",
  2048. # txId: "0x0123456789abcdef...",
  2049. # id: "0123456789abcdef...",
  2050. # asset: "ETH",
  2051. # applyTime: 1514488724000,
  2052. # status: 6
  2053. # }
  2054. #
  2055. id = self.safe_string(transaction, 'id')
  2056. address = self.safe_string(transaction, 'address')
  2057. tag = self.safe_string(transaction, 'addressTag') # set but unused
  2058. if tag is not None:
  2059. if len(tag) < 1:
  2060. tag = None
  2061. txid = self.safe_string(transaction, 'txId')
  2062. currencyId = self.safe_string(transaction, 'asset')
  2063. code = self.safe_currency_code(currencyId, currency)
  2064. timestamp = None
  2065. insertTime = self.safe_integer(transaction, 'insertTime')
  2066. applyTime = self.safe_integer(transaction, 'applyTime')
  2067. type = self.safe_string(transaction, 'type')
  2068. if type is None:
  2069. if (insertTime is not None) and (applyTime is None):
  2070. type = 'deposit'
  2071. timestamp = insertTime
  2072. elif (insertTime is None) and (applyTime is not None):
  2073. type = 'withdrawal'
  2074. timestamp = applyTime
  2075. status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type)
  2076. amount = self.safe_float(transaction, 'amount')
  2077. feeCost = self.safe_float(transaction, 'transactionFee')
  2078. fee = None
  2079. if feeCost is not None:
  2080. fee = {'currency': code, 'cost': feeCost}
  2081. return {
  2082. 'info': transaction,
  2083. 'id': id,
  2084. 'txid': txid,
  2085. 'timestamp': timestamp,
  2086. 'datetime': self.iso8601(timestamp),
  2087. 'address': address,
  2088. 'tag': tag,
  2089. 'type': type,
  2090. 'amount': amount,
  2091. 'currency': code,
  2092. 'status': status,
  2093. 'updated': None,
  2094. 'fee': fee,
  2095. }
  2096. def fetch_deposit_address(self, code, params={}):
  2097. self.load_markets()
  2098. currency = self.currency(code)
  2099. request = {
  2100. 'asset': currency['id'],
  2101. }
  2102. response = self.wapiGetDepositAddress(self.extend(request, params))
  2103. success = self.safe_value(response, 'success')
  2104. if (success is None) or not success:
  2105. raise InvalidAddress(self.id + ' fetchDepositAddress returned an empty response – create the deposit address in the user settings first.')
  2106. address = self.safe_string(response, 'address')
  2107. tag = self.safe_string(response, 'addressTag')
  2108. self.check_address(address)
  2109. return {
  2110. 'currency': code,
  2111. 'address': self.check_address(address),
  2112. 'tag': tag,
  2113. 'info': response,
  2114. }
  2115. def fetch_funding_fees(self, codes=None, params={}):
  2116. response = self.wapiGetAssetDetail(params)
  2117. #
  2118. # {
  2119. # "success": True,
  2120. # "assetDetail": {
  2121. # "CTR": {
  2122. # "minWithdrawAmount": "70.00000000", #min withdraw amount
  2123. # "depositStatus": False,//deposit status
  2124. # "withdrawFee": 35, # withdraw fee
  2125. # "withdrawStatus": True, #withdraw status
  2126. # "depositTip": "Delisted, Deposit Suspended" #reason
  2127. # },
  2128. # "SKY": {
  2129. # "minWithdrawAmount": "0.02000000",
  2130. # "depositStatus": True,
  2131. # "withdrawFee": 0.01,
  2132. # "withdrawStatus": True
  2133. # }
  2134. # }
  2135. # }
  2136. #
  2137. detail = self.safe_value(response, 'assetDetail', {})
  2138. ids = list(detail.keys())
  2139. withdrawFees = {}
  2140. for i in range(0, len(ids)):
  2141. id = ids[i]
  2142. code = self.safe_currency_code(id)
  2143. withdrawFees[code] = self.safe_float(detail[id], 'withdrawFee')
  2144. return {
  2145. 'withdraw': withdrawFees,
  2146. 'deposit': {},
  2147. 'info': response,
  2148. }
  2149. def withdraw(self, code, amount, address, tag=None, params={}):
  2150. self.check_address(address)
  2151. self.load_markets()
  2152. currency = self.currency(code)
  2153. # name is optional, can be overrided via params
  2154. name = address[0:20]
  2155. request = {
  2156. 'asset': currency['id'],
  2157. 'address': address,
  2158. 'amount': float(amount),
  2159. 'name': name, # name is optional, can be overrided via params
  2160. # https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi
  2161. # issue sapiGetCapitalConfigGetall() to get networks for withdrawing USDT ERC20 vs USDT Omni
  2162. # 'network': 'ETH', # 'BTC', 'TRX', etc, optional
  2163. }
  2164. if tag is not None:
  2165. request['addressTag'] = tag
  2166. response = self.wapiPostWithdraw(self.extend(request, params))
  2167. return {
  2168. 'info': response,
  2169. 'id': self.safe_string(response, 'id'),
  2170. }
  2171. def parse_trading_fee(self, fee, market=None):
  2172. #
  2173. # {
  2174. # "symbol": "ADABNB",
  2175. # "maker": 0.9000,
  2176. # "taker": 1.0000
  2177. # }
  2178. #
  2179. marketId = self.safe_string(fee, 'symbol')
  2180. symbol = marketId
  2181. if marketId in self.markets_by_id:
  2182. market = self.markets_by_id[marketId]
  2183. symbol = market['symbol']
  2184. return {
  2185. 'info': fee,
  2186. 'symbol': symbol,
  2187. 'maker': self.safe_float(fee, 'maker'),
  2188. 'taker': self.safe_float(fee, 'taker'),
  2189. }
  2190. def fetch_trading_fee(self, symbol, params={}):
  2191. self.load_markets()
  2192. market = self.market(symbol)
  2193. request = {
  2194. 'symbol': market['id'],
  2195. }
  2196. response = self.wapiGetTradeFee(self.extend(request, params))
  2197. #
  2198. # {
  2199. # "tradeFee": [
  2200. # {
  2201. # "symbol": "ADABNB",
  2202. # "maker": 0.9000,
  2203. # "taker": 1.0000
  2204. # }
  2205. # ],
  2206. # "success": True
  2207. # }
  2208. #
  2209. tradeFee = self.safe_value(response, 'tradeFee', [])
  2210. first = self.safe_value(tradeFee, 0, {})
  2211. return self.parse_trading_fee(first)
  2212. def fetch_trading_fees(self, params={}):
  2213. self.load_markets()
  2214. response = self.wapiGetTradeFee(params)
  2215. #
  2216. # {
  2217. # "tradeFee": [
  2218. # {
  2219. # "symbol": "ADABNB",
  2220. # "maker": 0.9000,
  2221. # "taker": 1.0000
  2222. # }
  2223. # ],
  2224. # "success": True
  2225. # }
  2226. #
  2227. tradeFee = self.safe_value(response, 'tradeFee', [])
  2228. result = {}
  2229. for i in range(0, len(tradeFee)):
  2230. fee = self.parse_trading_fee(tradeFee[i])
  2231. symbol = fee['symbol']
  2232. result[symbol] = fee
  2233. return result
  2234. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  2235. if not (api in self.urls['api']):
  2236. raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints')
  2237. url = self.urls['api'][api]
  2238. url += '/' + path
  2239. if api == 'wapi':
  2240. url += '.html'
  2241. userDataStream = (path == 'userDataStream') or (path == 'listenKey')
  2242. if path == 'historicalTrades':
  2243. if self.apiKey:
  2244. headers = {
  2245. 'X-MBX-APIKEY': self.apiKey,
  2246. }
  2247. else:
  2248. raise AuthenticationError(self.id + ' historicalTrades endpoint requires `apiKey` credential')
  2249. elif userDataStream:
  2250. if self.apiKey:
  2251. # v1 special case for userDataStream
  2252. body = self.urlencode(params)
  2253. headers = {
  2254. 'X-MBX-APIKEY': self.apiKey,
  2255. 'Content-Type': 'application/x-www-form-urlencoded',
  2256. }
  2257. else:
  2258. raise AuthenticationError(self.id + ' userDataStream endpoint requires `apiKey` credential')
  2259. if (api == 'private') or (api == 'sapi') or (api == 'wapi' and path != 'systemStatus') or (api == 'dapiPrivate') or (api == 'fapiPrivate') or (api == 'fapiPrivateV2'):
  2260. self.check_required_credentials()
  2261. query = None
  2262. recvWindow = self.safe_integer(self.options, 'recvWindow', 5000)
  2263. if (api == 'sapi') and (path == 'asset/dust'):
  2264. query = self.urlencode_with_array_repeat(self.extend({
  2265. 'timestamp': self.nonce(),
  2266. 'recvWindow': recvWindow,
  2267. }, params))
  2268. elif path == 'batchOrders':
  2269. query = self.rawencode(self.extend({
  2270. 'timestamp': self.nonce(),
  2271. 'recvWindow': recvWindow,
  2272. }, params))
  2273. else:
  2274. query = self.urlencode(self.extend({
  2275. 'timestamp': self.nonce(),
  2276. 'recvWindow': recvWindow,
  2277. }, params))
  2278. signature = self.hmac(self.encode(query), self.encode(self.secret))
  2279. query += '&' + 'signature=' + signature
  2280. headers = {
  2281. 'X-MBX-APIKEY': self.apiKey,
  2282. }
  2283. if (method == 'GET') or (method == 'DELETE') or (api == 'wapi'):
  2284. url += '?' + query
  2285. else:
  2286. body = query
  2287. headers['Content-Type'] = 'application/x-www-form-urlencoded'
  2288. else:
  2289. # userDataStream endpoints are public, but POST, PUT, DELETE
  2290. # therefore they don't accept URL query arguments
  2291. # https://github.com/ccxt/ccxt/issues/5224
  2292. if not userDataStream:
  2293. if params:
  2294. url += '?' + self.urlencode(params)
  2295. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  2296. def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
  2297. if (code == 418) or (code == 429):
  2298. raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
  2299. # error response in a form: {"code": -1013, "msg": "Invalid quantity."}
  2300. # following block cointains legacy checks against message patterns in "msg" property
  2301. # will switch "code" checks eventually, when we know all of them
  2302. if code >= 400:
  2303. if body.find('Price * QTY is zero or less') >= 0:
  2304. raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
  2305. if body.find('LOT_SIZE') >= 0:
  2306. raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
  2307. if body.find('PRICE_FILTER') >= 0:
  2308. raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use self.price_to_precision(symbol, amount) ' + body)
  2309. if response is None:
  2310. return # fallback to default error handler
  2311. # check success value for wapi endpoints
  2312. # response in format {'msg': 'The coin does not exist.', 'success': True/false}
  2313. success = self.safe_value(response, 'success', True)
  2314. if not success:
  2315. message = self.safe_string(response, 'msg')
  2316. parsedMessage = None
  2317. if message is not None:
  2318. try:
  2319. parsedMessage = json.loads(message)
  2320. except Exception as e:
  2321. # do nothing
  2322. parsedMessage = None
  2323. if parsedMessage is not None:
  2324. response = parsedMessage
  2325. message = self.safe_string(response, 'msg')
  2326. if message is not None:
  2327. self.throw_exactly_matched_exception(self.exceptions, message, self.id + ' ' + message)
  2328. # checks against error codes
  2329. error = self.safe_string(response, 'code')
  2330. if error is not None:
  2331. # https://github.com/ccxt/ccxt/issues/6501
  2332. if error == '200':
  2333. return
  2334. # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
  2335. # despite that their message is very confusing, it is raised by Binance
  2336. # on a temporary ban, the API key is valid, but disabled for a while
  2337. if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
  2338. raise DDoSProtection(self.id + ' temporary banned: ' + body)
  2339. feedback = self.id + ' ' + body
  2340. self.throw_exactly_matched_exception(self.exceptions, error, feedback)
  2341. raise ExchangeError(feedback)
  2342. if not success:
  2343. raise ExchangeError(self.id + ' ' + body)
  2344. def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
  2345. response = self.fetch2(path, api, method, params, headers, body)
  2346. # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
  2347. if (api == 'private') or (api == 'wapi'):
  2348. self.options['hasAlreadyAuthenticatedSuccessfully'] = True
  2349. return response