ccxt 4.4.95__py2.py3-none-any.whl → 4.4.97__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. ccxt/__init__.py +3 -1
  2. ccxt/abstract/binance.py +3 -0
  3. ccxt/abstract/binancecoinm.py +3 -0
  4. ccxt/abstract/binanceus.py +3 -0
  5. ccxt/abstract/binanceusdm.py +3 -0
  6. ccxt/abstract/foxbit.py +26 -0
  7. ccxt/abstract/hyperliquid.py +1 -1
  8. ccxt/abstract/phemex.py +1 -0
  9. ccxt/apex.py +3 -3
  10. ccxt/ascendex.py +2 -2
  11. ccxt/async_support/__init__.py +3 -1
  12. ccxt/async_support/apex.py +3 -3
  13. ccxt/async_support/ascendex.py +2 -2
  14. ccxt/async_support/base/exchange.py +10 -5
  15. ccxt/async_support/base/ws/future.py +5 -3
  16. ccxt/async_support/binance.py +90 -34
  17. ccxt/async_support/binancecoinm.py +5 -1
  18. ccxt/async_support/binanceus.py +3 -1
  19. ccxt/async_support/binanceusdm.py +3 -1
  20. ccxt/async_support/bingx.py +1 -1
  21. ccxt/async_support/bitget.py +30 -143
  22. ccxt/async_support/bitmart.py +2 -2
  23. ccxt/async_support/bitrue.py +13 -8
  24. ccxt/async_support/bybit.py +14 -5
  25. ccxt/async_support/coinbaseexchange.py +4 -2
  26. ccxt/async_support/coinbaseinternational.py +2 -2
  27. ccxt/async_support/coinspot.py +36 -1
  28. ccxt/async_support/cryptocom.py +78 -3
  29. ccxt/async_support/cryptomus.py +41 -1
  30. ccxt/async_support/defx.py +1 -1
  31. ccxt/async_support/derive.py +1 -1
  32. ccxt/async_support/ellipx.py +40 -0
  33. ccxt/async_support/exmo.py +1 -1
  34. ccxt/async_support/foxbit.py +1935 -0
  35. ccxt/async_support/gate.py +1 -2
  36. ccxt/async_support/hashkey.py +39 -0
  37. ccxt/async_support/hyperliquid.py +42 -27
  38. ccxt/async_support/independentreserve.py +35 -0
  39. ccxt/async_support/indodax.py +34 -0
  40. ccxt/async_support/kucoin.py +3 -2
  41. ccxt/async_support/kucoinfutures.py +3 -2
  42. ccxt/async_support/latoken.py +42 -0
  43. ccxt/async_support/luno.py +36 -0
  44. ccxt/async_support/mercado.py +34 -0
  45. ccxt/async_support/mexc.py +31 -32
  46. ccxt/async_support/modetrade.py +3 -3
  47. ccxt/async_support/okcoin.py +1 -1
  48. ccxt/async_support/okx.py +10 -3
  49. ccxt/async_support/onetrading.py +1 -1
  50. ccxt/async_support/oxfun.py +2 -1
  51. ccxt/async_support/paradex.py +2 -2
  52. ccxt/async_support/phemex.py +36 -31
  53. ccxt/async_support/vertex.py +3 -2
  54. ccxt/async_support/woo.py +6 -2
  55. ccxt/async_support/woofipro.py +2 -2
  56. ccxt/base/decimal_to_precision.py +16 -10
  57. ccxt/base/errors.py +6 -0
  58. ccxt/base/exchange.py +60 -17
  59. ccxt/binance.py +90 -34
  60. ccxt/binancecoinm.py +5 -1
  61. ccxt/binanceus.py +3 -1
  62. ccxt/binanceusdm.py +3 -1
  63. ccxt/bingx.py +1 -1
  64. ccxt/bitget.py +30 -143
  65. ccxt/bitmart.py +2 -2
  66. ccxt/bitrue.py +13 -8
  67. ccxt/bybit.py +14 -5
  68. ccxt/coinbaseexchange.py +4 -2
  69. ccxt/coinbaseinternational.py +2 -2
  70. ccxt/coinspot.py +36 -1
  71. ccxt/cryptocom.py +78 -3
  72. ccxt/cryptomus.py +41 -1
  73. ccxt/defx.py +1 -1
  74. ccxt/derive.py +1 -1
  75. ccxt/ellipx.py +40 -0
  76. ccxt/exmo.py +1 -1
  77. ccxt/foxbit.py +1935 -0
  78. ccxt/gate.py +1 -2
  79. ccxt/hashkey.py +39 -0
  80. ccxt/hyperliquid.py +42 -27
  81. ccxt/independentreserve.py +35 -0
  82. ccxt/indodax.py +34 -0
  83. ccxt/kucoin.py +3 -2
  84. ccxt/kucoinfutures.py +3 -2
  85. ccxt/latoken.py +42 -0
  86. ccxt/luno.py +36 -0
  87. ccxt/mercado.py +34 -0
  88. ccxt/mexc.py +31 -32
  89. ccxt/modetrade.py +3 -3
  90. ccxt/okcoin.py +1 -1
  91. ccxt/okx.py +10 -3
  92. ccxt/onetrading.py +1 -1
  93. ccxt/oxfun.py +2 -1
  94. ccxt/paradex.py +2 -2
  95. ccxt/phemex.py +36 -31
  96. ccxt/pro/__init__.py +1 -1
  97. ccxt/pro/binancecoinm.py +3 -1
  98. ccxt/pro/binanceus.py +3 -1
  99. ccxt/pro/binanceusdm.py +3 -1
  100. ccxt/pro/bybit.py +33 -1
  101. ccxt/test/tests_async.py +15 -0
  102. ccxt/test/tests_sync.py +15 -0
  103. ccxt/vertex.py +3 -2
  104. ccxt/woo.py +6 -2
  105. ccxt/woofipro.py +2 -2
  106. {ccxt-4.4.95.dist-info → ccxt-4.4.97.dist-info}/METADATA +19 -19
  107. {ccxt-4.4.95.dist-info → ccxt-4.4.97.dist-info}/RECORD +110 -107
  108. {ccxt-4.4.95.dist-info → ccxt-4.4.97.dist-info}/LICENSE.txt +0 -0
  109. {ccxt-4.4.95.dist-info → ccxt-4.4.97.dist-info}/WHEEL +0 -0
  110. {ccxt-4.4.95.dist-info → ccxt-4.4.97.dist-info}/top_level.txt +0 -0
ccxt/foxbit.py ADDED
@@ -0,0 +1,1935 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+
6
+ from ccxt.base.exchange import Exchange
7
+ from ccxt.abstract.foxbit import ImplicitAPI
8
+ import hashlib
9
+ from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, TradingFees, Transaction
10
+ from typing import List
11
+ from ccxt.base.errors import ExchangeError
12
+ from ccxt.base.errors import AuthenticationError
13
+ from ccxt.base.errors import PermissionDenied
14
+ from ccxt.base.errors import AccountSuspended
15
+ from ccxt.base.errors import ArgumentsRequired
16
+ from ccxt.base.errors import BadRequest
17
+ from ccxt.base.errors import BadSymbol
18
+ from ccxt.base.errors import InsufficientFunds
19
+ from ccxt.base.errors import InvalidOrder
20
+ from ccxt.base.errors import RateLimitExceeded
21
+ from ccxt.base.errors import ExchangeNotAvailable
22
+ from ccxt.base.errors import OnMaintenance
23
+ from ccxt.base.decimal_to_precision import DECIMAL_PLACES
24
+ from ccxt.base.precise import Precise
25
+
26
+
27
+ class foxbit(Exchange, ImplicitAPI):
28
+
29
+ def describe(self) -> Any:
30
+ return self.deep_extend(super(foxbit, self).describe(), {
31
+ 'id': 'foxbit',
32
+ 'name': 'Foxbit',
33
+ 'countries': ['pt-BR'],
34
+ # 300 requests per 10 seconds = 30 requests per second
35
+ # rateLimit = 1000 ms / 30 requests ~= 33.334
36
+ 'rateLimit': 33.334,
37
+ 'version': '1',
38
+ 'comment': 'Foxbit Exchange',
39
+ 'certified': False,
40
+ 'pro': False,
41
+ 'has': {
42
+ 'CORS': True,
43
+ 'spot': True,
44
+ 'margin': None,
45
+ 'swap': None,
46
+ 'future': None,
47
+ 'option': None,
48
+ 'cancelAllOrders': True,
49
+ 'cancelOrder': True,
50
+ 'createLimitBuyOrder': True,
51
+ 'createLimitSellOrder': True,
52
+ 'createMarketBuyOrder': True,
53
+ 'createMarketSellOrder': True,
54
+ 'createOrder': True,
55
+ 'fecthOrderBook': True,
56
+ 'fetchBalance': True,
57
+ 'fetchCanceledOrders': True,
58
+ 'fetchClosedOrders': True,
59
+ 'fetchCurrencies': True,
60
+ 'fetchDepositAddress': True,
61
+ 'fetchDeposits': True,
62
+ 'fetchL2OrderBook': True,
63
+ 'fetchLedger': True,
64
+ 'fetchMarkets': True,
65
+ 'fetchMyTrades': True,
66
+ 'fetchOHLCV': True,
67
+ 'fetchOpenOrders': True,
68
+ 'fetchOrder': True,
69
+ 'fetchOrders': True,
70
+ 'fetchTicker': True,
71
+ 'fetchTickers': True,
72
+ 'fetchTrades': True,
73
+ 'fetchTradingFee': True,
74
+ 'fetchTradingFees': True,
75
+ 'fetchTransactions': True,
76
+ 'fetchWithdrawals': True,
77
+ 'loadMarkets': True,
78
+ 'sandbox': False,
79
+ 'withdraw': True,
80
+ 'ws': False,
81
+ },
82
+ 'timeframes': {
83
+ '1m': '1m',
84
+ '5m': '5m',
85
+ '15m': '15m',
86
+ '30m': '30m',
87
+ '1h': '1h',
88
+ '2h': '2h',
89
+ '4h': '4h',
90
+ '6h': '6h',
91
+ '12h': '12h',
92
+ '1d': '1d',
93
+ '1w': '1w',
94
+ '2w': '2w',
95
+ '1M': '1M',
96
+ },
97
+ 'urls': {
98
+ 'logo': 'https://github.com/user-attachments/assets/ba1435eb-1d59-4393-8de7-0db10a002fb3',
99
+ 'api': {
100
+ 'public': 'https://api.foxbit.com.br',
101
+ 'private': 'https://api.foxbit.com.br',
102
+ 'status': 'https://metadata-v2.foxbit.com.br/api',
103
+ },
104
+ 'www': 'https://app.foxbit.com.br',
105
+ 'doc': [
106
+ 'https://docs.foxbit.com.br',
107
+ ],
108
+ },
109
+ 'precisionMode': DECIMAL_PLACES,
110
+ 'exceptions': {
111
+ 'exact': {
112
+ # https://docs.foxbit.com.br/rest/v3/#tag/API-Codes/Errors
113
+ '400': BadRequest, # Bad request. An unknown error occurred while processing request parameters.
114
+ '429': RateLimitExceeded, # Too many requests. Request limit exceeded. Try again later.
115
+ '404': BadRequest, # Resource not found. A resource was not found while processing the request.
116
+ '500': ExchangeError, # Internal server error. An unknown error occurred while processing the request.
117
+ '2001': AuthenticationError, # Authentication error. Error authenticating request.
118
+ '2002': AuthenticationError, # Invalid signature. The signature for self request is not valid.
119
+ '2003': AuthenticationError, # Invalid access key. Access key missing, invalid or not found.
120
+ '2004': BadRequest, # Invalid timestamp. Invalid or missing timestamp.
121
+ '2005': PermissionDenied, # IP not allowed. The IP address {IP_ADDR} isn't on the trusted list for self API key.
122
+ '3001': PermissionDenied, # Permission denied. Permission denied for self request.
123
+ '3002': PermissionDenied, # KYC required. A greater level of KYC verification is required to proceed with self request.
124
+ '3003': AccountSuspended, # Member disabled. This member is disabled. Please get in touch with our support for more information.
125
+ '4001': BadRequest, # Validation error. A validation error occurred.
126
+ '4002': InsufficientFunds, # Insufficient funds. Insufficient funds to proceed with self request.
127
+ '4003': InvalidOrder, # Quantity below the minimum allowed. Quantity below the minimum allowed to proceed with self request.
128
+ '4004': BadSymbol, # Invalid symbol. The market or asset symbol is invalid or was not found.
129
+ '4005': BadRequest, # Invalid idempotent. Characters allowed are "a-z", "0-9", "_" or "-", and 36 at max. We recommend UUID v4 in lowercase.
130
+ '4007': ExchangeError, # Locked error. There was an error in your allocated balance, please contact us.
131
+ '4008': InvalidOrder, # Cannot submit order. The order cannot be created.
132
+ '4009': PermissionDenied, # Invalid level. The sub-member does not have the required level to create the transaction.
133
+ '4011': RateLimitExceeded, # Too many open orders. You have reached the limit of open orders per market/side.
134
+ '4012': ExchangeError, # Too many simultaneous account operations. We are currently unable to process your balance change due to simultaneous operations on your account. Please retry shortly.
135
+ '5001': ExchangeNotAvailable, # Service unavailable. The requested resource is currently unavailable. Try again later.
136
+ '5002': OnMaintenance, # Service under maintenance. The requested resource is currently under maintenance. Try again later.
137
+ '5003': OnMaintenance, # Market under maintenance. The market is under maintenance. Try again later.
138
+ '5004': InvalidOrder, # Market is not deep enough. The market is not deep enough to complete your request.
139
+ '5005': InvalidOrder, # Price out of range from market. The order price is out of range from market to complete your request.
140
+ '5006': InvalidOrder, # Significant price deviation detected, exceeding acceptable limits. The order price is exceeding acceptable limits from market to complete your request.
141
+ },
142
+ 'broad': {
143
+ # todo: add details messages that can be usefull here, like when market is not found
144
+ },
145
+ },
146
+ 'requiredCredentials': {
147
+ 'apiKey': True,
148
+ 'secret': True,
149
+ },
150
+ 'api': {
151
+ 'v3': {
152
+ 'public': {
153
+ 'get': {
154
+ 'currencies': 5, # 6 requests per second
155
+ 'markets': 5, # 6 requests per second
156
+ 'markets/ticker/24hr': 60, # 1 request per 2 seconds
157
+ 'markets/{market}/orderbook': 6, # 10 requests per 2 seconds
158
+ 'markets/{market}/candlesticks': 12, # 5 requests per 2 seconds
159
+ 'markets/{market}/trades/history': 12, # 5 requests per 2 seconds
160
+ 'markets/{market}/ticker/24hr': 15, # 4 requests per 2 seconds
161
+ },
162
+ },
163
+ 'private': {
164
+ 'get': {
165
+ 'accounts': 2, # 15 requests per second
166
+ 'accounts/{symbol}/transactions': 60, # 1 requests per 2 seconds
167
+ 'orders': 2, # 30 requests per 2 seconds
168
+ 'orders/by-order-id/{id}': 2, # 30 requests per 2 seconds
169
+ 'trades': 6, # 5 orders per second
170
+ 'deposits/address': 10, # 3 requests per second
171
+ 'deposits': 10, # 3 requests per second
172
+ 'withdrawals': 10, # 3 requests per second
173
+ 'me/fees/trading': 60, # 1 requests per 2 seconds
174
+ },
175
+ 'post': {
176
+ 'orders': 2, # 30 requests per 2 seconds
177
+ 'orders/batch': 7.5, # 8 requests per 2 seconds
178
+ 'orders/cancel-replace': 3, # 20 requests per 2 seconds
179
+ 'withdrawals': 10, # 3 requests per second
180
+ },
181
+ 'put': {
182
+ 'orders/cancel': 2, # 30 requests per 2 seconds
183
+ },
184
+ },
185
+ },
186
+ 'status': {
187
+ 'public': {
188
+ 'get': {
189
+ 'status': 30, # 1 request per second
190
+ },
191
+ },
192
+ },
193
+ },
194
+ 'fees': {
195
+ 'trading': {
196
+ 'feeSide': 'get',
197
+ 'tierBased': False,
198
+ 'percentage': True,
199
+ 'taker': self.parse_number('0.005'),
200
+ 'maker': self.parse_number('0.0025'),
201
+ },
202
+ },
203
+ 'options': {
204
+ 'sandboxMode': False,
205
+ 'networksById': {
206
+ 'algorand': 'ALGO',
207
+ 'arbitrum': 'ARBITRUM',
208
+ 'avalanchecchain': 'AVAX',
209
+ 'bitcoin': 'BTC',
210
+ 'bitcoincash': 'BCH',
211
+ 'bsc': 'BEP20',
212
+ 'cardano': 'ADA',
213
+ 'cosmos': 'ATOM',
214
+ 'dogecoin': 'DOGE',
215
+ 'erc20': 'ETH',
216
+ 'hedera': 'HBAR',
217
+ 'litecoin': 'LTC',
218
+ 'near': 'NEAR',
219
+ 'optimism': 'OPTIMISM',
220
+ 'polkadot': 'DOT',
221
+ 'polygon': 'MATIC',
222
+ 'ripple': 'XRP',
223
+ 'solana': 'SOL',
224
+ 'stacks': 'STX',
225
+ 'stellar': 'XLM',
226
+ 'tezos': 'XTZ',
227
+ 'trc20': 'TRC20',
228
+ },
229
+ 'networks': {
230
+ 'ALGO': 'algorand',
231
+ 'ARBITRUM': 'arbitrum',
232
+ 'AVAX': 'avalanchecchain',
233
+ 'BTC': 'bitcoin',
234
+ 'BCH': 'bitcoincash',
235
+ 'BEP20': 'bsc',
236
+ 'ADA': 'cardano',
237
+ 'ATOM': 'cosmos',
238
+ 'DOGE': 'dogecoin',
239
+ 'ETH': 'erc20',
240
+ 'HBAR': 'hedera',
241
+ 'LTC': 'litecoin',
242
+ 'NEAR': 'near',
243
+ 'OPTIMISM': 'optimism',
244
+ 'DOT': 'polkadot',
245
+ 'MATIC': 'polygon',
246
+ 'XRP': 'ripple',
247
+ 'SOL': 'solana',
248
+ 'STX': 'stacks',
249
+ 'XLM': 'stellar',
250
+ 'XTZ': 'tezos',
251
+ 'TRC20': 'trc20',
252
+ },
253
+ },
254
+ 'features': {
255
+ 'spot': {
256
+ 'sandbox': False,
257
+ 'createOrder': {
258
+ 'marginMode': False,
259
+ 'triggerPrice': True,
260
+ 'triggerPriceType': {
261
+ 'last': True, # foxbit default trigger price type is last, no params will change it
262
+ 'mark': False,
263
+ 'index': False,
264
+ },
265
+ 'triggerDirection': False,
266
+ 'stopLossPrice': False,
267
+ 'takeProfitPrice': False,
268
+ 'attachedStopLossTakeProfit': None,
269
+ 'timeInForce': {
270
+ 'GTC': True,
271
+ 'FOK': True,
272
+ 'IOC': True,
273
+ 'PO': True,
274
+ 'GTD': False,
275
+ },
276
+ 'hedged': False,
277
+ 'leverage': False,
278
+ 'marketBuyByCost': False,
279
+ 'marketBuyRequiresPrice': False,
280
+ 'selfTradePrevention': {
281
+ 'expire_maker': True, # foxbit prevents self trading by default, no params can change self
282
+ 'expire_taker': True, # foxbit prevents self trading by default, no params can change self
283
+ 'expire_both': True, # foxbit prevents self trading by default, no params can change self
284
+ 'none': True, # foxbit prevents self trading by default, no params can change self
285
+ },
286
+ 'trailing': False,
287
+ 'icebergAmount': False,
288
+ },
289
+ 'createOrders': {
290
+ 'max': 5,
291
+ },
292
+ 'fetchMyTrades': {
293
+ 'marginMode': False,
294
+ 'limit': 100,
295
+ 'daysBack': 90,
296
+ 'untilDays': 10000, # high value just to keep clear that there is no range limit, just the limit of the page size
297
+ 'symbolRequired': True,
298
+ },
299
+ 'fetchOrder': {
300
+ 'marginMode': False,
301
+ 'limit': 1,
302
+ 'daysBack': 90,
303
+ 'trigger': False,
304
+ 'trailing': False,
305
+ 'symbolRequired': False,
306
+ },
307
+ 'fetchOpenOrders': {
308
+ 'marginMode': False,
309
+ 'limit': 100,
310
+ 'daysBack': 90,
311
+ 'trigger': False,
312
+ 'trailing': False,
313
+ 'symbolRequired': False,
314
+ },
315
+ 'fetchOrders': {
316
+ 'marginMode': True,
317
+ 'limit': 100,
318
+ 'daysBack': 90,
319
+ 'untilDays': 10000, # high value just to keep clear that there is no range limit, just the limit of the page size
320
+ 'trigger': False,
321
+ 'trailing': False,
322
+ 'symbolRequired': False,
323
+ },
324
+ 'fetchClosedOrders': {
325
+ 'marginMode': True,
326
+ 'limit': 100,
327
+ 'daysBack': 90,
328
+ 'daysBackCanceled': 90,
329
+ 'untilDays': 10000, # high value just to keep clear that there is no range limit, just the limit of the page size
330
+ 'trigger': False,
331
+ 'trailing': False,
332
+ 'symbolRequired': False,
333
+ },
334
+ 'fetchOHLCV': {
335
+ 'limit': 500,
336
+ },
337
+ },
338
+ },
339
+ })
340
+
341
+ def fetch_currencies(self, params={}) -> Currencies:
342
+ response = self.v3PublicGetCurrencies(params)
343
+ # {
344
+ # "data": [
345
+ # {
346
+ # "symbol": "btc",
347
+ # "name": "Bitcoin",
348
+ # "type": "CRYPTO",
349
+ # "precision": 8,
350
+ # "deposit_info": {
351
+ # "min_to_confirm": "1",
352
+ # "min_amount": "0.0001"
353
+ # },
354
+ # "withdraw_info": {
355
+ # "enabled": True,
356
+ # "min_amount": "0.0001",
357
+ # "fee": "0.0001"
358
+ # },
359
+ # "category": {
360
+ # "code": "cripto",
361
+ # "name": "Cripto"
362
+ # },
363
+ # "networks": [
364
+ # {
365
+ # "name": "Bitcoin",
366
+ # "code": "btc",
367
+ # "deposit_info": {
368
+ # status: "ENABLED",
369
+ # },
370
+ # "withdraw_info": {
371
+ # "status": "ENABLED",
372
+ # "fee": "0.0001",
373
+ # },
374
+ # "has_destination_tag": False
375
+ # }
376
+ # ]
377
+ # }
378
+ # ]
379
+ # }
380
+ data = self.safe_list(response, 'data', [])
381
+ result: dict = {}
382
+ for i in range(0, len(data)):
383
+ currency = data[i]
384
+ precision = self.safe_integer(currency, 'precision')
385
+ currencyId = self.safe_string(currency, 'symbol')
386
+ name = self.safe_string(currency, 'name')
387
+ code = self.safe_currency_code(currencyId)
388
+ depositInfo = self.safe_dict(currency, 'deposit_info')
389
+ withdrawInfo = self.safe_dict(currency, 'withdraw_info')
390
+ networks = self.safe_list(currency, 'networks', [])
391
+ type = self.safe_string_lower(currency, 'type')
392
+ parsedNetworks: dict = {}
393
+ for j in range(0, len(networks)):
394
+ network = networks[j]
395
+ networkId = self.safe_string(network, 'code')
396
+ networkCode = self.network_id_to_code(networkId, code)
397
+ networkWithdrawInfo = self.safe_dict(network, 'withdraw_info')
398
+ networkDepositInfo = self.safe_dict(network, 'deposit_info')
399
+ isWithdrawEnabled = self.safe_string(networkWithdrawInfo, 'status') == 'ENABLED'
400
+ isDepositEnabled = self.safe_string(networkDepositInfo, 'status') == 'ENABLED'
401
+ parsedNetworks[networkCode] = {
402
+ 'info': currency,
403
+ 'id': networkId,
404
+ 'network': networkCode,
405
+ 'name': self.safe_string(network, 'name'),
406
+ 'deposit': isDepositEnabled,
407
+ 'withdraw': isWithdrawEnabled,
408
+ 'active': True,
409
+ 'precision': precision,
410
+ 'fee': self.safe_number(networkWithdrawInfo, 'fee'),
411
+ 'limits': {
412
+ 'amount': {
413
+ 'min': None,
414
+ 'max': None,
415
+ },
416
+ 'deposit': {
417
+ 'min': self.safe_number(depositInfo, 'min_amount'),
418
+ 'max': None,
419
+ },
420
+ 'withdraw': {
421
+ 'min': self.safe_number(withdrawInfo, 'min_amount'),
422
+ 'max': None,
423
+ },
424
+ },
425
+ }
426
+ if self.safe_dict(result, code) is None:
427
+ result[code] = self.safe_currency_structure({
428
+ 'id': currencyId,
429
+ 'code': code,
430
+ 'info': currency,
431
+ 'name': name,
432
+ 'active': True,
433
+ 'type': type,
434
+ 'deposit': self.safe_bool(depositInfo, 'enabled', False),
435
+ 'withdraw': self.safe_bool(withdrawInfo, 'enabled', False),
436
+ 'fee': self.safe_number(withdrawInfo, 'fee'),
437
+ 'precision': precision,
438
+ 'limits': {
439
+ 'amount': {
440
+ 'min': None,
441
+ 'max': None,
442
+ },
443
+ 'deposit': {
444
+ 'min': self.safe_number(depositInfo, 'min_amount'),
445
+ 'max': None,
446
+ },
447
+ 'withdraw': {
448
+ 'min': self.safe_number(withdrawInfo, 'min_amount'),
449
+ 'max': None,
450
+ },
451
+ },
452
+ 'networks': parsedNetworks,
453
+ })
454
+ return result
455
+
456
+ def fetch_markets(self, params={}) -> List[Market]:
457
+ """
458
+ Retrieves data on all markets for foxbit.
459
+
460
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_index
461
+
462
+ :param dict [params]: extra parameters specific to the exchange API endpoint
463
+ :returns dict[]: an array of objects representing market data
464
+ """
465
+ response = self.v3PublicGetMarkets(params)
466
+ # {
467
+ # "data": [
468
+ # {
469
+ # "symbol": "btcbrl",
470
+ # "quantity_min": "0.00000236",
471
+ # "quantity_increment": "0.00000001",
472
+ # "quantity_precision": 8,
473
+ # "price_min": "0.0001",
474
+ # "price_increment": "0.0001",
475
+ # "price_precision": 4,
476
+ # "default_fees": {
477
+ # "maker": "0.001",
478
+ # "taker": "0.001"
479
+ # },
480
+ # "base": {
481
+ # "symbol": "btc",
482
+ # "name": "Bitcoin",
483
+ # "type": "CRYPTO",
484
+ # "precision": 8,
485
+ # "category": {
486
+ # "code": "cripto",
487
+ # "name": "Cripto"
488
+ # },
489
+ # "deposit_info": {
490
+ # "min_to_confirm": "1",
491
+ # "min_amount": "0.0001",
492
+ # "enabled": True
493
+ # },
494
+ # "withdraw_info": {
495
+ # "enabled": True,
496
+ # "min_amount": "0.0001",
497
+ # "fee": "0.0001"
498
+ # },
499
+ # "networks": [
500
+ # {
501
+ # "name": "Bitcoin",
502
+ # "code": "bitcoin",
503
+ # "deposit_info": {
504
+ # "status": "ENABLED"
505
+ # },
506
+ # "withdraw_info": {
507
+ # "status": "ENABLED",
508
+ # "fee": "0.0001"
509
+ # },
510
+ # "has_destination_tag": False
511
+ # }
512
+ # ],
513
+ # "default_network_code": "bitcoin"
514
+ # },
515
+ # "quote": {
516
+ # "symbol": "btc",
517
+ # "name": "Bitcoin",
518
+ # "type": "CRYPTO",
519
+ # "precision": 8,
520
+ # "category": {
521
+ # "code": "cripto",
522
+ # "name": "Cripto"
523
+ # },
524
+ # "deposit_info": {
525
+ # "min_to_confirm": "1",
526
+ # "min_amount": "0.0001",
527
+ # "enabled": True
528
+ # },
529
+ # "withdraw_info": {
530
+ # "enabled": True,
531
+ # "min_amount": "0.0001",
532
+ # "fee": "0.0001"
533
+ # },
534
+ # "networks": [
535
+ # {
536
+ # "name": "Bitcoin",
537
+ # "code": "bitcoin",
538
+ # "deposit_info": {
539
+ # "status": "ENABLED"
540
+ # },
541
+ # "withdraw_info": {
542
+ # "status": "ENABLED",
543
+ # "fee": "0.0001"
544
+ # },
545
+ # "has_destination_tag": False
546
+ # }
547
+ # ],
548
+ # "default_network_code": "bitcoin"
549
+ # },
550
+ # "order_type": [
551
+ # "LIMIT",
552
+ # "MARKET",
553
+ # "INSTANT",
554
+ # "STOP_LIMIT",
555
+ # "STOP_MARKET"
556
+ # ]
557
+ # }
558
+ # ]
559
+ # }
560
+ markets = self.safe_list(response, 'data', [])
561
+ return self.parse_markets(markets)
562
+
563
+ def fetch_ticker(self, symbol: str, params={}) -> Ticker:
564
+ """
565
+ Get last 24 hours ticker information, in real-time, for given market.
566
+
567
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_ticker
568
+
569
+ :param str symbol: unified symbol of the market to fetch the ticker for
570
+ :param dict [params]: extra parameters specific to the exchange API endpoint
571
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
572
+ """
573
+ self.load_markets()
574
+ market = self.market(symbol)
575
+ request: dict = {
576
+ 'market': market['id'],
577
+ }
578
+ response = self.v3PublicGetMarketsMarketTicker24hr(self.extend(request, params))
579
+ # {
580
+ # "data": [
581
+ # {
582
+ # "market_symbol": "btcbrl",
583
+ # "last_trade": {
584
+ # "price": "358504.69340000",
585
+ # "volume": "0.00027893",
586
+ # "date": "2024-01-01T00:00:00.000Z"
587
+ # },
588
+ # "rolling_24h": {
589
+ # "price_change": "3211.87290000",
590
+ # "price_change_percent": "0.90400726",
591
+ # "volume": "20.03206866",
592
+ # "trades_count": "4376",
593
+ # "open": "355292.82050000",
594
+ # "high": "362999.99990000",
595
+ # "low": "355002.88880000"
596
+ # },
597
+ # "best": {
598
+ # "ask": {
599
+ # "price": "358504.69340000",
600
+ # "volume": "0.00027893"
601
+ # },
602
+ # "bid": {
603
+ # "price": "358504.69340000",
604
+ # "volume": "0.00027893"
605
+ # }
606
+ # }
607
+ # }
608
+ # ]
609
+ # }
610
+ data = self.safe_list(response, 'data', [])
611
+ result = self.safe_dict(data, 0, {})
612
+ return self.parse_ticker(result, market)
613
+
614
+ def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
615
+ """
616
+ Retrieve the ticker data of all markets.
617
+
618
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_tickers
619
+
620
+ :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
621
+ :param dict [params]: extra parameters specific to the exchange API endpoint
622
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
623
+ """
624
+ self.load_markets()
625
+ symbols = self.market_symbols(symbols)
626
+ response = self.v3PublicGetMarketsTicker24hr(params)
627
+ # {
628
+ # "data": [
629
+ # {
630
+ # "market_symbol": "btcbrl",
631
+ # "last_trade": {
632
+ # "price": "358504.69340000",
633
+ # "volume": "0.00027893",
634
+ # "date": "2024-01-01T00:00:00.000Z"
635
+ # },
636
+ # "rolling_24h": {
637
+ # "price_change": "3211.87290000",
638
+ # "price_change_percent": "0.90400726",
639
+ # "volume": "20.03206866",
640
+ # "trades_count": "4376",
641
+ # "open": "355292.82050000",
642
+ # "high": "362999.99990000",
643
+ # "low": "355002.88880000"
644
+ # },
645
+ # }
646
+ # ]
647
+ # }
648
+ data = self.safe_list(response, 'data', [])
649
+ return self.parse_tickers(data, symbols)
650
+
651
+ def fetch_trading_fees(self, params={}) -> TradingFees:
652
+ """
653
+ fetch the trading fees for multiple markets
654
+
655
+ https://docs.foxbit.com.br/rest/v3/#tag/Member-Info/operation/MembersController_listTradingFees
656
+
657
+ :param dict [params]: extra parameters specific to the exchange API endpoint
658
+ :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
659
+ """
660
+ self.load_markets()
661
+ response = self.v3PrivateGetMeFeesTrading(params)
662
+ # [
663
+ # {
664
+ # "market_symbol": "btcbrl",
665
+ # "maker": "0.0025",
666
+ # "taker": "0.005"
667
+ # }
668
+ # ]
669
+ data = self.safe_list(response, 'data', [])
670
+ result = {}
671
+ for i in range(0, len(data)):
672
+ entry = data[i]
673
+ marketId = self.safe_string(entry, 'market_symbol')
674
+ market = self.safe_market(marketId)
675
+ symbol = market['symbol']
676
+ result[symbol] = self.parse_trading_fee(entry, market)
677
+ return result
678
+
679
+ def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
680
+ """
681
+ Exports a copy of the order book of a specific market.
682
+
683
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_findOrderbook
684
+
685
+ :param str symbol: unified symbol of the market to fetch the order book for
686
+ :param int [limit]: the maximum amount of order book entries to return, the maximum is 100
687
+ :param dict [params]: extra parameters specific to the exchange API endpoint
688
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
689
+ """
690
+ self.load_markets()
691
+ market = self.market(symbol)
692
+ defaultLimit = 20
693
+ request: dict = {
694
+ 'market': market['id'],
695
+ 'depth': defaultLimit if (limit is None) else limit,
696
+ }
697
+ response = self.v3PublicGetMarketsMarketOrderbook(self.extend(request, params))
698
+ # {
699
+ # "sequence_id": 1234567890,
700
+ # "timestamp": 1713187921336,
701
+ # "bids": [
702
+ # [
703
+ # "3.00000000",
704
+ # "300.00000000"
705
+ # ],
706
+ # [
707
+ # "1.70000000",
708
+ # "310.00000000"
709
+ # ]
710
+ # ],
711
+ # "asks": [
712
+ # [
713
+ # "3.00000000",
714
+ # "300.00000000"
715
+ # ],
716
+ # [
717
+ # "2.00000000",
718
+ # "321.00000000"
719
+ # ]
720
+ # ]
721
+ # }
722
+ timestamp = self.safe_integer(response, 'timestamp')
723
+ return self.parse_order_book(response, symbol, timestamp)
724
+
725
+ def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
726
+ """
727
+ Retrieve the trades of a specific market.
728
+
729
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_publicTrades
730
+
731
+ :param str symbol: unified symbol of the market to fetch trades for
732
+ :param int [since]: timestamp in ms of the earliest trade to fetch
733
+ :param int [limit]: the maximum amount of trades to fetch
734
+ :param dict [params]: extra parameters specific to the exchange API endpoint
735
+ :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
736
+ """
737
+ self.load_markets()
738
+ market = self.market(symbol)
739
+ request: dict = {
740
+ 'market': market['id'],
741
+ }
742
+ if limit is not None:
743
+ request['page_size'] = limit
744
+ if limit > 200:
745
+ request['page_size'] = 200
746
+ # [
747
+ # {
748
+ # "id": 1,
749
+ # "price": "329248.74700000",
750
+ # "volume": "0.00100000",
751
+ # "taker_side": "BUY",
752
+ # "created_at": "2024-01-01T00:00:00Z"
753
+ # }
754
+ # ]
755
+ response = self.v3PublicGetMarketsMarketTradesHistory(self.extend(request, params))
756
+ data = self.safe_list(response, 'data', [])
757
+ return self.parse_trades(data, market, since, limit)
758
+
759
+ def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
760
+ """
761
+ Fetch historical candlestick data containing the open, high, low, and close price, and the volume of a market.
762
+
763
+ https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_findCandlesticks
764
+
765
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
766
+ :param str timeframe: the length of time each candle represents
767
+ :param int [since]: timestamp in ms of the earliest candle to fetch
768
+ :param int [limit]: the maximum amount of candles to fetch
769
+ :param dict [params]: extra parameters specific to the exchange API endpoint
770
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
771
+ """
772
+ self.load_markets()
773
+ market = self.market(symbol)
774
+ interval = self.safe_string(self.timeframes, timeframe, timeframe)
775
+ request: dict = {
776
+ 'market': market['id'],
777
+ 'interval': interval,
778
+ }
779
+ if since is not None:
780
+ request['start_time'] = self.iso8601(since)
781
+ if limit is not None:
782
+ request['limit'] = limit
783
+ if limit > 500:
784
+ request['limit'] = 500
785
+ response = self.v3PublicGetMarketsMarketCandlesticks(self.extend(request, params))
786
+ # [
787
+ # [
788
+ # "1692918000000", # timestamp
789
+ # "127772.05150000", # open
790
+ # "128467.99980000", # high
791
+ # "127750.01000000", # low
792
+ # "128353.99990000", # close
793
+ # "1692918060000", # close timestamp
794
+ # "0.17080431", # base volume
795
+ # "21866.35948786", # quote volume
796
+ # 66, # number of trades
797
+ # "0.12073605", # taker buy base volume
798
+ # "15466.34096391" # taker buy quote volume
799
+ # ]
800
+ # ]
801
+ return self.parse_ohlcvs(response, market, interval, since, limit)
802
+
803
+ def fetch_balance(self, params={}) -> Balances:
804
+ """
805
+ Query for balance and get the amount of funds available for trading or funds locked in orders.
806
+
807
+ https://docs.foxbit.com.br/rest/v3/#tag/Account/operation/AccountsController_all
808
+
809
+ :param dict [params]: extra parameters specific to the exchange API endpoint
810
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
811
+ """
812
+ self.load_markets()
813
+ response = self.v3PrivateGetAccounts(params)
814
+ # {
815
+ # "data": [
816
+ # {
817
+ # "currency_symbol": "btc",
818
+ # "balance": "10000.0",
819
+ # "balance_available": "9000.0",
820
+ # "balance_locked": "1000.0"
821
+ # }
822
+ # ]
823
+ # }
824
+ accounts = self.safe_list(response, 'data', [])
825
+ result: dict = {
826
+ 'info': response,
827
+ }
828
+ for i in range(0, len(accounts)):
829
+ account = accounts[i]
830
+ currencyId = self.safe_string(account, 'currency_symbol')
831
+ currencyCode = self.safe_currency_code(currencyId)
832
+ total = self.safe_string(account, 'balance')
833
+ used = self.safe_string(account, 'balance_locked')
834
+ free = self.safe_string(account, 'balance_available')
835
+ balanceObj = {
836
+ 'free': free,
837
+ 'used': used,
838
+ 'total': total,
839
+ }
840
+ result[currencyCode] = balanceObj
841
+ return self.safe_balance(result)
842
+
843
+ def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
844
+ """
845
+ Fetch all unfilled currently open orders.
846
+
847
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
848
+
849
+ :param str symbol: unified market symbol
850
+ :param int [since]: the earliest time in ms to fetch open orders for
851
+ :param int [limit]: the maximum number of open order structures to retrieve
852
+ :param dict [params]: extra parameters specific to the exchange API endpoint
853
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
854
+ """
855
+ return self.fetch_orders_by_status('ACTIVE', symbol, since, limit, params)
856
+
857
+ def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
858
+ """
859
+ Fetch all currently closed orders.
860
+
861
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
862
+
863
+ :param str symbol: unified market symbol of the market orders were made in
864
+ :param int [since]: the earliest time in ms to fetch orders for
865
+ :param int [limit]: the maximum number of order structures to retrieve
866
+ :param dict [params]: extra parameters specific to the exchange API endpoint
867
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
868
+ """
869
+ return self.fetch_orders_by_status('FILLED', symbol, since, limit, params)
870
+
871
+ def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
872
+ return self.fetch_orders_by_status('CANCELED', symbol, since, limit, params)
873
+
874
+ def fetch_orders_by_status(self, status: Str, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
875
+ self.load_markets()
876
+ market = None
877
+ request: dict = {
878
+ 'state': status,
879
+ }
880
+ if symbol is not None:
881
+ market = self.market(symbol)
882
+ request['market_symbol'] = market['id']
883
+ if since is not None:
884
+ request['start_time'] = self.iso8601(since)
885
+ if limit is not None:
886
+ request['page_size'] = limit
887
+ if limit > 100:
888
+ request['page_size'] = 100
889
+ response = self.v3PrivateGetOrders(self.extend(request, params))
890
+ data = self.safe_list(response, 'data', [])
891
+ return self.parse_orders(data)
892
+
893
+ def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
894
+ """
895
+ Create an order with the specified characteristics
896
+
897
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_create
898
+
899
+ :param str symbol: unified symbol of the market to create an order in
900
+ :param str type: 'market', 'limit', 'stop_market', 'stop_limit', 'instant'
901
+ :param str side: 'buy' or 'sell'
902
+ :param float amount: how much you want to trade in units of the base currency
903
+ :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
904
+ :param dict [params]: extra parameters specific to the exchange API endpoint
905
+ :param str [params.timeInForce]: "GTC", "FOK", "IOC", "PO"
906
+ :param float [params.triggerPrice]: The time in force for the order. One of GTC, FOK, IOC, PO. See .features or foxbit's doc to see more details.
907
+ :param bool [params.postOnly]: True or False whether the order is post-only
908
+ :param str [params.clientOrderId]: a unique identifier for the order
909
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
910
+ """
911
+ self.load_markets()
912
+ market = self.market(symbol)
913
+ type = type.upper()
914
+ if type != 'LIMIT' and type != 'MARKET' and type != 'STOP_MARKET' and type != 'STOP_LIMIT' and type != 'INSTANT':
915
+ raise InvalidOrder('Invalid order type: ' + type + '. Must be one of: limit, market, stop_market, stop_limit, instant.')
916
+ timeInForce = self.safe_string_upper(params, 'timeInForce')
917
+ postOnly = self.safe_bool(params, 'postOnly', False)
918
+ triggerPrice = self.safe_number(params, 'triggerPrice')
919
+ request: dict = {
920
+ 'market_symbol': market['id'],
921
+ 'side': side.upper(),
922
+ 'type': type,
923
+ }
924
+ if type == 'STOP_MARKET' or type == 'STOP_LIMIT':
925
+ if triggerPrice is None:
926
+ raise InvalidOrder('Invalid order type: ' + type + '. Must have triggerPrice.')
927
+ if timeInForce is not None:
928
+ if timeInForce == 'PO':
929
+ request['post_only'] = True
930
+ else:
931
+ request['time_in_force'] = timeInForce
932
+ if postOnly:
933
+ request['post_only'] = True
934
+ if triggerPrice is not None:
935
+ request['stop_price'] = self.price_to_precision(symbol, triggerPrice)
936
+ if type == 'INSTANT':
937
+ request['amount'] = self.price_to_precision(symbol, amount)
938
+ else:
939
+ request['quantity'] = self.amount_to_precision(symbol, amount)
940
+ if type == 'LIMIT' or type == 'STOP_LIMIT':
941
+ request['price'] = self.price_to_precision(symbol, price)
942
+ clientOrderId = self.safe_string(params, 'clientOrderId')
943
+ if clientOrderId is not None:
944
+ request['client_order_id'] = clientOrderId
945
+ params = self.omit(params, ['timeInForce', 'postOnly', 'triggerPrice', 'clientOrderId'])
946
+ response = self.v3PrivatePostOrders(self.extend(request, params))
947
+ # {
948
+ # "id": 1234567890,
949
+ # "sn": "OKMAKSDHRVVREK",
950
+ # "client_order_id": "451637946501"
951
+ # }
952
+ return self.parse_order(response, market)
953
+
954
+ def create_orders(self, orders: List[OrderRequest], params={}):
955
+ """
956
+ create a list of trade orders
957
+
958
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/createBatch
959
+
960
+ :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
961
+ :param dict [params]: extra parameters specific to the exchange API endpoint
962
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
963
+ """
964
+ self.load_markets()
965
+ ordersRequests = []
966
+ for i in range(0, len(orders)):
967
+ order = self.safe_dict(orders, i)
968
+ symbol = self.safe_string(order, 'symbol')
969
+ market = self.market(symbol)
970
+ type = self.safe_string_upper(order, 'type')
971
+ orderParams = self.safe_dict(order, 'params', {})
972
+ if type != 'LIMIT' and type != 'MARKET' and type != 'STOP_MARKET' and type != 'STOP_LIMIT' and type != 'INSTANT':
973
+ raise InvalidOrder('Invalid order type: ' + type + '. Must be one of: limit, market, stop_market, stop_limit, instant.')
974
+ timeInForce = self.safe_string_upper(orderParams, 'timeInForce')
975
+ postOnly = self.safe_bool(orderParams, 'postOnly', False)
976
+ triggerPrice = self.safe_number(orderParams, 'triggerPrice')
977
+ request: dict = {
978
+ 'market_symbol': market['id'],
979
+ 'side': self.safe_string_upper(order, 'side'),
980
+ 'type': type,
981
+ }
982
+ if type == 'STOP_MARKET' or type == 'STOP_LIMIT':
983
+ if triggerPrice is None:
984
+ raise InvalidOrder('Invalid order type: ' + type + '. Must have triggerPrice.')
985
+ if timeInForce is not None:
986
+ if timeInForce == 'PO':
987
+ request['post_only'] = True
988
+ else:
989
+ request['time_in_force'] = timeInForce
990
+ del orderParams['timeInForce']
991
+ if postOnly:
992
+ request['post_only'] = True
993
+ del orderParams['postOnly']
994
+ if triggerPrice is not None:
995
+ request['stop_price'] = self.price_to_precision(symbol, triggerPrice)
996
+ del orderParams['triggerPrice']
997
+ if type == 'INSTANT':
998
+ request['amount'] = self.price_to_precision(symbol, self.safe_string(order, 'amount'))
999
+ else:
1000
+ request['quantity'] = self.amount_to_precision(symbol, self.safe_string(order, 'amount'))
1001
+ if type == 'LIMIT' or type == 'STOP_LIMIT':
1002
+ request['price'] = self.price_to_precision(symbol, self.safe_string(order, 'price'))
1003
+ ordersRequests.append(self.extend(request, orderParams))
1004
+ createOrdersRequest = {'data': ordersRequests}
1005
+ response = self.v3PrivatePostOrdersBatch(self.extend(createOrdersRequest, params))
1006
+ # {
1007
+ # "data": [
1008
+ # {
1009
+ # "side": "BUY",
1010
+ # "type": "LIMIT",
1011
+ # "market_symbol": "btcbrl",
1012
+ # "client_order_id": "451637946501",
1013
+ # "remark": "A remarkable note for the order.",
1014
+ # "quantity": "0.42",
1015
+ # "price": "250000.0",
1016
+ # "post_only": True,
1017
+ # "time_in_force": "GTC"
1018
+ # }
1019
+ # ]
1020
+ # }
1021
+ data = self.safe_list(response, 'data', [])
1022
+ return self.parse_orders(data)
1023
+
1024
+ def cancel_order(self, id: str, symbol: Str = None, params={}):
1025
+ """
1026
+ Cancel open orders.
1027
+
1028
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancel
1029
+
1030
+ :param str id: order id
1031
+ :param str symbol: unified symbol of the market the order was made in
1032
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1033
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1034
+ """
1035
+ self.load_markets()
1036
+ request: dict = {
1037
+ 'id': self.parse_number(id),
1038
+ 'type': 'ID',
1039
+ }
1040
+ response = self.v3PrivatePutOrdersCancel(self.extend(request, params))
1041
+ # {
1042
+ # "data": [
1043
+ # {
1044
+ # "sn": "OKMAKSDHRVVREK",
1045
+ # "id": 123456789
1046
+ # }
1047
+ # ]
1048
+ # }
1049
+ data = self.safe_list(response, 'data', [])
1050
+ result = self.safe_dict(data, 0, {})
1051
+ return self.parse_order(result)
1052
+
1053
+ def cancel_all_orders(self, symbol: Str = None, params={}):
1054
+ """
1055
+ Cancel all open orders or all open orders for a specific market.
1056
+
1057
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancel
1058
+
1059
+ :param str symbol: unified market symbol of the market to cancel orders in
1060
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1061
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1062
+ """
1063
+ self.load_markets()
1064
+ request: dict = {
1065
+ 'type': 'ALL',
1066
+ }
1067
+ if symbol is not None:
1068
+ market = self.market(symbol)
1069
+ request['type'] = 'MARKET'
1070
+ request['market_symbol'] = market['id']
1071
+ response = self.v3PrivatePutOrdersCancel(self.extend(request, params))
1072
+ # {
1073
+ # "data": [
1074
+ # {
1075
+ # "sn": "OKMAKSDHRVVREK",
1076
+ # "id": 123456789
1077
+ # }
1078
+ # ]
1079
+ # }
1080
+ return [self.safe_order({
1081
+ 'info': response,
1082
+ })]
1083
+
1084
+ def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
1085
+ """
1086
+ Get an order by ID.
1087
+
1088
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_findByOrderId
1089
+
1090
+ @param id
1091
+ :param str symbol: it is not used in the foxbit API
1092
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1093
+ :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1094
+ """
1095
+ self.load_markets()
1096
+ request: dict = {
1097
+ 'id': id,
1098
+ }
1099
+ response = self.v3PrivateGetOrdersByOrderIdId(self.extend(request, params))
1100
+ # {
1101
+ # "id": "1234567890",
1102
+ # "sn": "OKMAKSDHRVVREK",
1103
+ # "client_order_id": "451637946501",
1104
+ # "market_symbol": "btcbrl",
1105
+ # "side": "BUY",
1106
+ # "type": "LIMIT",
1107
+ # "state": "ACTIVE",
1108
+ # "price": "290000.0",
1109
+ # "price_avg": "295333.3333",
1110
+ # "quantity": "0.42",
1111
+ # "quantity_executed": "0.41",
1112
+ # "instant_amount": "290.0",
1113
+ # "instant_amount_executed": "290.0",
1114
+ # "created_at": "2021-02-15T22:06:32.999Z",
1115
+ # "trades_count": "2",
1116
+ # "remark": "A remarkable note for the order.",
1117
+ # "funds_received": "290.0"
1118
+ # }
1119
+ return self.parse_order(response, None)
1120
+
1121
+ def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1122
+ """
1123
+ fetches information on multiple orders made by the user
1124
+
1125
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
1126
+
1127
+ :param str symbol: unified market symbol of the market orders were made in
1128
+ :param int [since]: the earliest time in ms to fetch orders for
1129
+ :param int [limit]: the maximum number of order structures to retrieve
1130
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1131
+ :param str [params.state]: Enum: ACTIVE, CANCELED, FILLED, PARTIALLY_CANCELED, PARTIALLY_FILLED
1132
+ :param str [params.side]: Enum: BUY, SELL
1133
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1134
+ """
1135
+ self.load_markets()
1136
+ market = None
1137
+ request: dict = {}
1138
+ if symbol is not None:
1139
+ market = self.market(symbol)
1140
+ request['market_symbol'] = market['id']
1141
+ if since is not None:
1142
+ request['start_time'] = self.iso8601(since)
1143
+ if limit is not None:
1144
+ request['page_size'] = limit
1145
+ if limit > 100:
1146
+ request['page_size'] = 100
1147
+ response = self.v3PrivateGetOrders(self.extend(request, params))
1148
+ # {
1149
+ # "data": [
1150
+ # {
1151
+ # "id": "1234567890",
1152
+ # "sn": "OKMAKSDHRVVREK",
1153
+ # "client_order_id": "451637946501",
1154
+ # "market_symbol": "btcbrl",
1155
+ # "side": "BUY",
1156
+ # "type": "LIMIT",
1157
+ # "state": "ACTIVE",
1158
+ # "price": "290000.0",
1159
+ # "price_avg": "295333.3333",
1160
+ # "quantity": "0.42",
1161
+ # "quantity_executed": "0.41",
1162
+ # "instant_amount": "290.0",
1163
+ # "instant_amount_executed": "290.0",
1164
+ # "created_at": "2021-02-15T22:06:32.999Z",
1165
+ # "trades_count": "2",
1166
+ # "remark": "A remarkable note for the order.",
1167
+ # "funds_received": "290.0"
1168
+ # }
1169
+ # ]
1170
+ # }
1171
+ list = self.safe_list(response, 'data', [])
1172
+ return self.parse_orders(list, market, since, limit)
1173
+
1174
+ def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
1175
+ """
1176
+ Trade history queries will only have data available for the last 3 months, in descending order(most recents trades first).
1177
+
1178
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/TradesController_all
1179
+
1180
+ :param str symbol: unified market symbol
1181
+ :param int [since]: the earliest time in ms to fetch trades for
1182
+ :param int [limit]: the maximum number of trade structures to retrieve
1183
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1184
+ :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
1185
+ """
1186
+ if symbol is None:
1187
+ raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
1188
+ self.load_markets()
1189
+ market = self.market(symbol)
1190
+ request = {
1191
+ 'market_symbol': market['id'],
1192
+ }
1193
+ if since is not None:
1194
+ request['start_time'] = self.iso8601(since)
1195
+ if limit is not None:
1196
+ request['page_size'] = limit
1197
+ if limit > 100:
1198
+ request['page_size'] = 100
1199
+ response = self.v3PrivateGetTrades(self.extend(request, params))
1200
+ # {
1201
+ # "data": [
1202
+ # "id": 1234567890,
1203
+ # "sn": "TC5JZVW2LLJ3IW",
1204
+ # "order_id": 1234567890,
1205
+ # "market_symbol": "btcbrl",
1206
+ # "side": "BUY",
1207
+ # "price": "290000.0",
1208
+ # "quantity": "1.0",
1209
+ # "fee": "0.01",
1210
+ # "fee_currency_symbol": "btc",
1211
+ # "created_at": "2021-02-15T22:06:32.999Z"
1212
+ # ]
1213
+ # }
1214
+ data = self.safe_list(response, 'data', [])
1215
+ return self.parse_trades(data, market, since, limit)
1216
+
1217
+ def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
1218
+ """
1219
+ Fetch the deposit address for a currency associated with self account.
1220
+
1221
+ https://docs.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_depositAddress
1222
+
1223
+ :param str code: unified currency code
1224
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1225
+ :param str [params.networkCode]: the blockchain network to create a deposit address on
1226
+ :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
1227
+ """
1228
+ self.load_markets()
1229
+ currency = self.currency(code)
1230
+ request: dict = {
1231
+ 'currency_symbol': currency['id'],
1232
+ }
1233
+ networkCode, paramsOmited = self.handle_network_code_and_params(params)
1234
+ if networkCode is not None:
1235
+ request['network_code'] = self.network_code_to_id(networkCode, code)
1236
+ response = self.v3PrivateGetDepositsAddress(self.extend(request, paramsOmited))
1237
+ # {
1238
+ # "currency_symbol": "btc",
1239
+ # "address": "2N9sS8LgrY19rvcCWDmE1ou1tTVmqk4KQAB",
1240
+ # "message": "Address was retrieved successfully",
1241
+ # "destination_tag": "string",
1242
+ # "network": {
1243
+ # "name": "Bitcoin Network",
1244
+ # "code": "btc"
1245
+ # }
1246
+ # }
1247
+ return self.parse_deposit_address(response, currency)
1248
+
1249
+ def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1250
+ """
1251
+ Fetch all deposits made to an account.
1252
+
1253
+ https://docs.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_listOrders
1254
+
1255
+ :param str [code]: unified currency code
1256
+ :param int [since]: the earliest time in ms to fetch deposits for
1257
+ :param int [limit]: the maximum number of deposit structures to retrieve
1258
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1259
+ :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
1260
+ """
1261
+ self.load_markets()
1262
+ request: dict = {}
1263
+ currency = None
1264
+ if code is not None:
1265
+ currency = self.currency(code)
1266
+ if limit is not None:
1267
+ request['page_size'] = limit
1268
+ if limit > 100:
1269
+ request['page_size'] = 100
1270
+ if since is not None:
1271
+ request['start_time'] = self.iso8601(since)
1272
+ response = self.v3PrivateGetDeposits(self.extend(request, params))
1273
+ # {
1274
+ # "data": [
1275
+ # {
1276
+ # "sn": "OKMAKSDHRVVREK",
1277
+ # "state": "ACCEPTED",
1278
+ # "currency_symbol": "btc",
1279
+ # "amount": "1.0",
1280
+ # "fee": "0.1",
1281
+ # "created_at": "2022-02-18T22:06:32.999Z",
1282
+ # "details_crypto": {
1283
+ # "transaction_id": "e20f035387020c5d5ea18ad53244f09f3",
1284
+ # "receiving_address": "2N2rTrnKEFcyJjEJqvVjgWZ3bKvKT7Aij61"
1285
+ # }
1286
+ # }
1287
+ # ]
1288
+ # }
1289
+ data = self.safe_list(response, 'data', [])
1290
+ return self.parse_transactions(data, currency, since, limit)
1291
+
1292
+ def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1293
+ """
1294
+ Fetch all withdrawals made from an account.
1295
+
1296
+ https://docs.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_listWithdrawals
1297
+
1298
+ :param str [code]: unified currency code
1299
+ :param int [since]: the earliest time in ms to fetch withdrawals for
1300
+ :param int [limit]: the maximum number of withdrawal structures to retrieve
1301
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1302
+ :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
1303
+ """
1304
+ self.load_markets()
1305
+ request: dict = {}
1306
+ currency = None
1307
+ if code is not None:
1308
+ currency = self.currency(code)
1309
+ if limit is not None:
1310
+ request['page_size'] = limit
1311
+ if limit > 100:
1312
+ request['page_size'] = 100
1313
+ if since is not None:
1314
+ request['start_time'] = self.iso8601(since)
1315
+ response = self.v3PrivateGetWithdrawals(self.extend(request, params))
1316
+ # {
1317
+ # "data": [
1318
+ # {
1319
+ # "sn": "OKMAKSDHRVVREK",
1320
+ # "state": "ACCEPTED",
1321
+ # "rejection_reason": "monthly_limit_exceeded",
1322
+ # "currency_symbol": "btc",
1323
+ # "amount": "1.0",
1324
+ # "fee": "0.1",
1325
+ # "created_at": "2022-02-18T22:06:32.999Z",
1326
+ # "details_crypto": {
1327
+ # "transaction_id": "e20f035387020c5d5ea18ad53244f09f3",
1328
+ # "destination_address": "2N2rTrnKEFcyJjEJqvVjgWZ3bKvKT7Aij61"
1329
+ # },
1330
+ # "details_fiat": {
1331
+ # "bank": {
1332
+ # "code": "1",
1333
+ # "branch": {
1334
+ # "number": "1234567890",
1335
+ # "digit": "1"
1336
+ # },
1337
+ # "account": {
1338
+ # "number": "1234567890",
1339
+ # "digit": "1",
1340
+ # "type": "CHECK"
1341
+ # }
1342
+ # }
1343
+ # }
1344
+ # }
1345
+ # ]
1346
+ # }
1347
+ data = self.safe_list(response, 'data', [])
1348
+ return self.parse_transactions(data, currency, since, limit)
1349
+
1350
+ def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1351
+ """
1352
+ Fetch all transactions(deposits and withdrawals) made from an account.
1353
+
1354
+ https://docs.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_listWithdrawals
1355
+ https://docs.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_listOrders
1356
+
1357
+ :param str [code]: unified currency code
1358
+ :param int [since]: the earliest time in ms to fetch withdrawals for
1359
+ :param int [limit]: the maximum number of withdrawal structures to retrieve
1360
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1361
+ :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
1362
+ """
1363
+ withdrawals = self.fetch_withdrawals(code, since, limit, params)
1364
+ deposits = self.fetch_deposits(code, since, limit, params)
1365
+ allTransactions = self.array_concat(withdrawals, deposits)
1366
+ result = self.sort_by(allTransactions, 'timestamp')
1367
+ return result
1368
+
1369
+ def fetch_status(self, params={}):
1370
+ """
1371
+ The latest known information on the availability of the exchange API.
1372
+
1373
+ https://status.foxbit.com/
1374
+
1375
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1376
+ :returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
1377
+ """
1378
+ response = self.statusPublicGetStatus(params)
1379
+ # {
1380
+ # "data": {
1381
+ # "id": 1,
1382
+ # "attributes": {
1383
+ # "status": "NORMAL",
1384
+ # "createdAt": "2023-05-17T18:37:05.934Z",
1385
+ # "updatedAt": "2024-04-17T02:33:50.945Z",
1386
+ # "publishedAt": "2023-05-17T18:37:07.653Z",
1387
+ # "locale": "pt-BR"
1388
+ # }
1389
+ # },
1390
+ # "meta": {
1391
+ # }
1392
+ # }
1393
+ data = self.safe_dict(response, 'data', {})
1394
+ attributes = self.safe_dict(data, 'attributes', {})
1395
+ statusRaw = self.safe_string(attributes, 'status')
1396
+ statusMap = {
1397
+ 'NORMAL': 'ok',
1398
+ 'UNDER_MAINTENANCE': 'maintenance',
1399
+ }
1400
+ return {
1401
+ 'status': self.safe_string(statusMap, statusRaw, statusRaw),
1402
+ 'updated': self.safe_string(attributes, 'updatedAt'),
1403
+ 'eta': None,
1404
+ 'url': None,
1405
+ 'info': response,
1406
+ }
1407
+
1408
+ def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
1409
+ """
1410
+ Simultaneously cancel an existing order and create a new one.
1411
+
1412
+ https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancelReplace
1413
+
1414
+ :param str id: order id
1415
+ :param str symbol: unified symbol of the market to create an order in
1416
+ :param str type: 'market' or 'limit'
1417
+ :param str side: 'buy' or 'sell'
1418
+ :param float amount: how much of the currency you want to trade in units of the base currency
1419
+ :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders, used on stop market orders
1420
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1421
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1422
+ """
1423
+ if symbol is None:
1424
+ raise ArgumentsRequired(self.id + ' editOrder() requires a symbol argument')
1425
+ type = type.upper()
1426
+ if type != 'LIMIT' and type != 'MARKET' and type != 'STOP_MARKET' and type != 'INSTANT':
1427
+ raise InvalidOrder('Invalid order type: ' + type + '. Must be one of: LIMIT, MARKET, STOP_MARKET, INSTANT.')
1428
+ self.load_markets()
1429
+ market = self.market(symbol)
1430
+ request: dict = {
1431
+ 'mode': 'ALLOW_FAILURE',
1432
+ 'cancel': {
1433
+ 'type': 'ID',
1434
+ 'id': self.parse_number(id),
1435
+ },
1436
+ 'create': {
1437
+ 'type': type,
1438
+ 'side': side.upper(),
1439
+ 'market_symbol': market['id'],
1440
+ },
1441
+ }
1442
+ if type == 'LIMIT' or type == 'MARKET':
1443
+ request['create']['quantity'] = self.amount_to_precision(symbol, amount)
1444
+ if type == 'LIMIT':
1445
+ request['create']['price'] = self.price_to_precision(symbol, price)
1446
+ if type == 'STOP_MARKET':
1447
+ request['create']['stop_price'] = self.price_to_precision(symbol, price)
1448
+ request['create']['quantity'] = self.amount_to_precision(symbol, amount)
1449
+ if type == 'INSTANT':
1450
+ request['create']['amount'] = self.price_to_precision(symbol, amount)
1451
+ response = self.v3PrivatePostOrdersCancelReplace(self.extend(request, params))
1452
+ # {
1453
+ # "cancel": {
1454
+ # "id": 123456789
1455
+ # },
1456
+ # "create": {
1457
+ # "id": 1234567890,
1458
+ # "client_order_id": "451637946501"
1459
+ # }
1460
+ # }
1461
+ return self.parse_order(response['create'], market)
1462
+
1463
+ def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
1464
+ """
1465
+ Make a withdrawal.
1466
+
1467
+ https://docs.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_createWithdrawal
1468
+
1469
+ :param str code: unified currency code
1470
+ :param float amount: the amount to withdraw
1471
+ :param str address: the address to withdraw to
1472
+ :param str tag:
1473
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1474
+ :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
1475
+ """
1476
+ tag, params = self.handle_withdraw_tag_and_params(tag, params)
1477
+ self.load_markets()
1478
+ currency = self.currency(code)
1479
+ request: dict = {
1480
+ 'currency_symbol': currency['id'],
1481
+ 'amount': self.number_to_string(amount),
1482
+ 'destination_address': address,
1483
+ }
1484
+ if tag is not None:
1485
+ request['destination_tag'] = tag
1486
+ networkCode = None
1487
+ networkCode, params = self.handle_network_code_and_params(params)
1488
+ if networkCode is not None:
1489
+ request['network_code'] = self.network_code_to_id(networkCode)
1490
+ response = self.v3PrivatePostWithdrawals(self.extend(request, params))
1491
+ # {
1492
+ # "amount": "1",
1493
+ # "currency_symbol": "xrp",
1494
+ # "network_code": "ripple",
1495
+ # "destination_address": "0x1234567890123456789012345678",
1496
+ # "destination_tag": "123456"
1497
+ # }
1498
+ return self.parse_transaction(response)
1499
+
1500
+ def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
1501
+ """
1502
+ fetch the history of changes, actions done by the user or operations that altered balance of the user
1503
+
1504
+ https://docs.foxbit.com.br/rest/v3/#tag/Account/operation/AccountsController_getTransactions
1505
+
1506
+ :param str code: unified currency code, default is None
1507
+ :param int [since]: timestamp in ms of the earliest ledger entry, default is None
1508
+ :param int [limit]: max number of ledger entrys to return, default is None
1509
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1510
+ :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger-structure>`
1511
+ """
1512
+ self.load_markets()
1513
+ request: dict = {}
1514
+ if code is None:
1515
+ raise ArgumentsRequired(self.id + ' fetchLedger() requires a code argument')
1516
+ if limit is not None:
1517
+ request['page_size'] = limit
1518
+ if limit > 100:
1519
+ request['page_size'] = 100
1520
+ if since is not None:
1521
+ request['start_time'] = self.iso8601(since)
1522
+ currency = self.currency(code)
1523
+ request['symbol'] = currency['id']
1524
+ response = self.v3PrivateGetAccountsSymbolTransactions(self.extend(request, params))
1525
+ data = self.safe_list(response, 'data', [])
1526
+ return self.parse_ledger(data, currency, since, limit)
1527
+
1528
+ def parse_market(self, market: dict) -> Market:
1529
+ id = self.safe_string(market, 'symbol')
1530
+ baseAssets = self.safe_dict(market, 'base')
1531
+ baseId = self.safe_string(baseAssets, 'symbol')
1532
+ quoteAssets = self.safe_dict(market, 'quote')
1533
+ quoteId = self.safe_string(quoteAssets, 'symbol')
1534
+ base = self.safe_currency_code(baseId)
1535
+ quote = self.safe_currency_code(quoteId)
1536
+ symbol = base + '/' + quote
1537
+ fees = self.safe_dict(market, 'default_fees')
1538
+ return self.safe_market_structure({
1539
+ 'id': id,
1540
+ 'symbol': symbol,
1541
+ 'base': base,
1542
+ 'quote': quote,
1543
+ 'baseId': baseId,
1544
+ 'quoteId': quoteId,
1545
+ 'active': True,
1546
+ 'type': 'spot',
1547
+ 'spot': True,
1548
+ 'margin': False,
1549
+ 'future': False,
1550
+ 'swap': False,
1551
+ 'option': False,
1552
+ 'contract': False,
1553
+ 'settle': None,
1554
+ 'settleId': None,
1555
+ 'contractSize': None,
1556
+ 'linear': None,
1557
+ 'inverse': None,
1558
+ 'expiry': None,
1559
+ 'expiryDatetime': None,
1560
+ 'strike': None,
1561
+ 'optionType': None,
1562
+ 'taker': self.safe_number(fees, 'taker'),
1563
+ 'maker': self.safe_number(fees, 'maker'),
1564
+ 'percentage': True,
1565
+ 'tierBased': False,
1566
+ 'feeSide': 'get',
1567
+ 'precision': {
1568
+ 'price': self.safe_integer(quoteAssets, 'precision'),
1569
+ 'amount': self.safe_integer(baseAssets, 'precision'),
1570
+ 'cost': self.safe_integer(quoteAssets, 'precision'),
1571
+ },
1572
+ 'limits': {
1573
+ 'amount': {
1574
+ 'min': self.safe_number(market, 'quantity_min'),
1575
+ 'max': None,
1576
+ },
1577
+ 'price': {
1578
+ 'min': self.safe_number(market, 'price_min'),
1579
+ 'max': None,
1580
+ },
1581
+ 'cost': {
1582
+ 'min': None,
1583
+ 'max': None,
1584
+ },
1585
+ 'leverage': {
1586
+ 'min': None,
1587
+ 'max': None,
1588
+ },
1589
+ },
1590
+ 'info': market,
1591
+ })
1592
+
1593
+ def parse_trading_fee(self, entry: dict, market: Market = None) -> TradingFeeInterface:
1594
+ return {
1595
+ 'info': entry,
1596
+ 'symbol': market['symbol'],
1597
+ 'maker': self.safe_number(entry, 'maker'),
1598
+ 'taker': self.safe_number(entry, 'taker'),
1599
+ 'percentage': True,
1600
+ 'tierBased': True,
1601
+ }
1602
+
1603
+ def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
1604
+ marketId = self.safe_string(ticker, 'market_symbol')
1605
+ symbol = self.safe_symbol(marketId, market, None, 'spot')
1606
+ rolling_24h = ticker['rolling_24h']
1607
+ best = self.safe_dict(ticker, 'best')
1608
+ bestAsk = self.safe_dict(best, 'ask')
1609
+ bestBid = self.safe_dict(best, 'bid')
1610
+ lastTrade = ticker['last_trade']
1611
+ lastPrice = self.safe_string(lastTrade, 'price')
1612
+ return self.safe_ticker({
1613
+ 'symbol': symbol,
1614
+ 'timestamp': self.parse_date(self.safe_string(lastTrade, 'date')),
1615
+ 'datetime': self.iso8601(self.parse_date(self.safe_string(lastTrade, 'date'))),
1616
+ 'high': self.safe_number(rolling_24h, 'high'),
1617
+ 'low': self.safe_number(rolling_24h, 'low'),
1618
+ 'bid': self.safe_number(bestBid, 'price'),
1619
+ 'bidVolume': self.safe_number(bestBid, 'volume'),
1620
+ 'ask': self.safe_number(bestAsk, 'price'),
1621
+ 'askVolume': self.safe_number(bestAsk, 'volume'),
1622
+ 'vwap': None,
1623
+ 'open': self.safe_number(rolling_24h, 'open'),
1624
+ 'close': lastPrice,
1625
+ 'last': lastPrice,
1626
+ 'previousClose': None,
1627
+ 'change': self.safe_string(rolling_24h, 'price_change'),
1628
+ 'percentage': self.safe_string(rolling_24h, 'price_change_percent'),
1629
+ 'average': None,
1630
+ 'baseVolume': self.safe_string(rolling_24h, 'volume'),
1631
+ 'quoteVolume': None,
1632
+ 'info': ticker,
1633
+ }, market)
1634
+
1635
+ def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
1636
+ return [
1637
+ self.safe_integer(ohlcv, 0),
1638
+ self.safe_number(ohlcv, 1),
1639
+ self.safe_number(ohlcv, 2),
1640
+ self.safe_number(ohlcv, 3),
1641
+ self.safe_number(ohlcv, 4),
1642
+ self.safe_number(ohlcv, 6),
1643
+ ]
1644
+
1645
+ def parse_trade(self, trade, market=None) -> Trade:
1646
+ timestamp = self.parse_date(self.safe_string(trade, 'created_at'))
1647
+ price = self.safe_string(trade, 'price')
1648
+ amount = self.safe_string(trade, 'volume', self.safe_string(trade, 'quantity'))
1649
+ privateSideField = self.safe_string_lower(trade, 'side')
1650
+ side = self.safe_string_lower(trade, 'taker_side', privateSideField)
1651
+ cost = Precise.string_mul(price, amount)
1652
+ fee = {
1653
+ 'currency': self.safe_symbol(self.safe_string(trade, 'fee_currency_symbol')),
1654
+ 'cost': self.safe_number(trade, 'fee'),
1655
+ 'rate': None,
1656
+ }
1657
+ return self.safe_trade({
1658
+ 'id': self.safe_string(trade, 'id'),
1659
+ 'info': trade,
1660
+ 'timestamp': timestamp,
1661
+ 'datetime': self.iso8601(timestamp),
1662
+ 'symbol': market['symbol'],
1663
+ 'order': None,
1664
+ 'type': None,
1665
+ 'side': side,
1666
+ 'takerOrMaker': None,
1667
+ 'price': price,
1668
+ 'amount': amount,
1669
+ 'cost': cost,
1670
+ 'fee': fee,
1671
+ }, market)
1672
+
1673
+ def parse_order_status(self, status: Str):
1674
+ statuses: dict = {
1675
+ 'PARTIALLY_CANCELED': 'open',
1676
+ 'ACTIVE': 'open',
1677
+ 'PARTIALLY_FILLED': 'open',
1678
+ 'FILLED': 'closed',
1679
+ 'PENDING_CANCEL': 'canceled',
1680
+ 'CANCELED': 'canceled',
1681
+ }
1682
+ return self.safe_string(statuses, status, status)
1683
+
1684
+ def parse_order(self, order, market=None) -> Order:
1685
+ symbol = self.safe_string(order, 'market_symbol')
1686
+ if market is None and symbol is not None:
1687
+ market = self.market(symbol)
1688
+ if market is not None:
1689
+ symbol = market['symbol']
1690
+ timestamp = self.parse_date(self.safe_string(order, 'created_at'))
1691
+ price = self.safe_string(order, 'price')
1692
+ filled = self.safe_string(order, 'quantity_executed')
1693
+ remaining = self.safe_string(order, 'quantity')
1694
+ # TODO: validate logic of amount here, should self be calculated?
1695
+ amount = None
1696
+ if remaining is not None and filled is not None:
1697
+ amount = Precise.string_add(remaining, filled)
1698
+ cost = self.safe_string(order, 'funds_received')
1699
+ if not cost:
1700
+ priceAverage = self.safe_string(order, 'price_avg')
1701
+ priceToCalculate = self.safe_string(order, 'price', priceAverage)
1702
+ cost = Precise.string_mul(priceToCalculate, amount)
1703
+ side = self.safe_string_lower(order, 'side')
1704
+ feeCurrency = self.safe_string_upper(market, 'quoteId')
1705
+ if side == 'buy':
1706
+ feeCurrency = self.safe_string_upper(market, 'baseId')
1707
+ return self.safe_order({
1708
+ 'id': self.safe_string(order, 'id'),
1709
+ 'info': order,
1710
+ 'clientOrderId': self.safe_string(order, 'client_order_id'),
1711
+ 'timestamp': timestamp,
1712
+ 'datetime': self.iso8601(timestamp),
1713
+ 'lastTradeTimestamp': None,
1714
+ 'status': self.parse_order_status(self.safe_string(order, 'state')),
1715
+ 'symbol': self.safe_string(market, 'symbol'),
1716
+ 'type': self.safe_string(order, 'type'),
1717
+ 'timeInForce': self.safe_string(order, 'time_in_force'),
1718
+ 'postOnly': self.safe_bool(order, 'post_only'),
1719
+ 'reduceOnly': None,
1720
+ 'side': side,
1721
+ 'price': self.parse_number(price),
1722
+ 'triggerPrice': self.safe_number(order, 'stop_price'),
1723
+ 'takeProfitPrice': None,
1724
+ 'stopLossPrice': None,
1725
+ 'cost': self.parse_number(cost),
1726
+ 'average': self.safe_number(order, 'price_avg'),
1727
+ 'amount': self.parse_number(amount),
1728
+ 'filled': self.parse_number(filled),
1729
+ 'remaining': self.parse_number(remaining),
1730
+ 'trades': None,
1731
+ 'fee': {
1732
+ 'currency': feeCurrency,
1733
+ 'cost': self.safe_number(order, 'fee_paid'),
1734
+ },
1735
+ })
1736
+
1737
+ def parse_deposit_address(self, depositAddress, currency: Currency = None):
1738
+ network = self.safe_dict(depositAddress, 'network')
1739
+ networkId = self.safe_string(network, 'code')
1740
+ currencyCode = self.safe_currency_code(None, currency)
1741
+ unifiedNetwork = self.network_id_to_code(networkId, currencyCode)
1742
+ return {
1743
+ 'address': self.safe_string(depositAddress, 'address'),
1744
+ 'tag': self.safe_string(depositAddress, 'tag'),
1745
+ 'currency': currencyCode,
1746
+ 'network': unifiedNetwork,
1747
+ 'info': depositAddress,
1748
+ }
1749
+
1750
+ def parse_transaction_status(self, status: Str):
1751
+ statuses: dict = {
1752
+ # BOTH
1753
+ 'SUBMITTING': 'pending',
1754
+ 'SUBMITTED': 'pending',
1755
+ 'REJECTED': 'failed',
1756
+ # DEPOSIT-SPECIFIC
1757
+ 'CANCELLED': 'canceled',
1758
+ 'ACCEPTED': 'ok',
1759
+ 'WARNING': 'pending',
1760
+ 'UNBLOCKED': 'pending',
1761
+ 'BLOCKED': 'pending',
1762
+ # WITHDRAWAL-SPECIFIC
1763
+ 'PROCESSING': 'pending',
1764
+ 'CANCELED': 'canceled',
1765
+ 'FAILED': 'failed',
1766
+ 'DONE': 'ok',
1767
+ }
1768
+ return self.safe_string(statuses, status, status)
1769
+
1770
+ def parse_transaction(self, transaction, currency: Currency = None, since: Int = None, limit: Int = None) -> Transaction:
1771
+ cryptoDetails = self.safe_dict(transaction, 'details_crypto')
1772
+ address = self.safe_string_2(cryptoDetails, 'receiving_address', 'destination_address')
1773
+ sn = self.safe_string(transaction, 'sn')
1774
+ type = 'withdrawal'
1775
+ if sn is not None and sn[0] == 'D':
1776
+ type = 'deposit'
1777
+ fee = self.safe_string(transaction, 'fee', '0')
1778
+ amount = self.safe_string(transaction, 'amount')
1779
+ currencySymbol = self.safe_string(transaction, 'currency_symbol')
1780
+ actualAmount = amount
1781
+ currencyCode = self.safe_currency_code(currencySymbol)
1782
+ status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
1783
+ created_at = self.safe_string(transaction, 'created_at')
1784
+ timestamp = self.parse_date(created_at)
1785
+ datetime = self.iso8601(timestamp)
1786
+ if fee is not None and amount is not None:
1787
+ # actualAmount = amount - fee
1788
+ actualAmount = Precise.string_sub(amount, fee)
1789
+ feeRate = Precise.string_div(fee, actualAmount)
1790
+ feeObj = {
1791
+ 'cost': self.parse_number(fee),
1792
+ 'currency': currencyCode,
1793
+ 'rate': self.parse_number(feeRate),
1794
+ }
1795
+ return {
1796
+ 'info': transaction,
1797
+ 'id': self.safe_string(transaction, 'sn'),
1798
+ 'txid': self.safe_string(cryptoDetails, 'transaction_id'),
1799
+ 'timestamp': timestamp,
1800
+ 'datetime': datetime,
1801
+ 'network': self.safe_string(transaction, 'network_code'),
1802
+ 'address': address,
1803
+ 'addressTo': address,
1804
+ 'addressFrom': None,
1805
+ 'tag': self.safe_string(transaction, 'destination_tag'),
1806
+ 'tagTo': self.safe_string(transaction, 'destination_tag'),
1807
+ 'tagFrom': None,
1808
+ 'type': type,
1809
+ 'amount': self.parse_number(amount),
1810
+ 'currency': currencyCode,
1811
+ 'status': status,
1812
+ 'updated': None,
1813
+ 'fee': feeObj,
1814
+ 'comment': None,
1815
+ 'internal': None,
1816
+ }
1817
+
1818
+ def parse_ledger_entry_type(self, type):
1819
+ types: dict = {
1820
+ 'DEPOSITING': 'transaction',
1821
+ 'WITHDRAWING': 'transaction',
1822
+ 'TRADING': 'trade',
1823
+ 'INTERNAL_TRANSFERING': 'transfer',
1824
+ 'OTHERS': 'transaction',
1825
+ }
1826
+ return self.safe_string(types, type, type)
1827
+
1828
+ def parse_ledger_entry(self, item: dict, currency: Currency = None):
1829
+ # {
1830
+ # "uuid": "f8e9f2d6-3c1e-4f2d-8f8e-9f2d6c1e4f2d",
1831
+ # "amount": "0.0001",
1832
+ # "balance": "0.0002",
1833
+ # "created_at": "2021-07-01T12:00:00Z",
1834
+ # "currency_symbol": "btc",
1835
+ # "fee": "0.0001",
1836
+ # "locked": "0.0001",
1837
+ # "locked_amount": "0.0001",
1838
+ # "reason_type": "DEPOSITING"
1839
+ # }
1840
+ id = self.safe_string(item, 'uuid')
1841
+ createdAt = self.safe_string(item, 'created_at')
1842
+ timestamp = self.parse8601(createdAt)
1843
+ reasonType = self.safe_string(item, 'reason_type')
1844
+ type = self.parse_ledger_entry_type(reasonType)
1845
+ exchangeSymbol = self.safe_string(item, 'currency_symbol')
1846
+ currencySymbol = self.safe_currency_code(exchangeSymbol)
1847
+ direction = 'in'
1848
+ amount = self.safe_number(item, 'amount')
1849
+ realAmount = amount
1850
+ balance = self.safe_number(item, 'balance')
1851
+ fee = {
1852
+ 'cost': self.safe_number(item, 'fee'),
1853
+ 'currency': currencySymbol,
1854
+ }
1855
+ if amount < 0:
1856
+ direction = 'out'
1857
+ realAmount = amount * -1
1858
+ return {
1859
+ 'id': id,
1860
+ 'info': item,
1861
+ 'timestamp': timestamp,
1862
+ 'datetime': self.iso8601(timestamp),
1863
+ 'direction': direction,
1864
+ 'account': None,
1865
+ 'referenceId': None,
1866
+ 'referenceAccount': None,
1867
+ 'type': type,
1868
+ 'currency': currencySymbol,
1869
+ 'amount': realAmount,
1870
+ 'before': balance - amount,
1871
+ 'after': balance,
1872
+ 'status': 'ok',
1873
+ 'fee': fee,
1874
+ }
1875
+
1876
+ def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
1877
+ version = api[0]
1878
+ urlPath = api[1]
1879
+ fullPath = '/rest/' + version + '/' + self.implode_params(path, params)
1880
+ if version == 'status':
1881
+ fullPath = '/status'
1882
+ urlPath = 'status'
1883
+ url = self.urls['api'][urlPath] + fullPath
1884
+ params = self.omit(params, self.extract_params(path))
1885
+ timestamp = self.milliseconds()
1886
+ query = ''
1887
+ signatureQuery = ''
1888
+ if method == 'GET':
1889
+ paramKeys = list(params.keys())
1890
+ paramKeysLength = len(paramKeys)
1891
+ if paramKeysLength > 0:
1892
+ query = self.urlencode(params)
1893
+ url += '?' + query
1894
+ for i in range(0, len(paramKeys)):
1895
+ key = paramKeys[i]
1896
+ value = self.safe_string(params, key)
1897
+ if value is not None:
1898
+ signatureQuery += key + '=' + value
1899
+ if i < paramKeysLength - 1:
1900
+ signatureQuery += '&'
1901
+ if method == 'POST' or method == 'PUT':
1902
+ body = self.json(params)
1903
+ bodyToSignature = ''
1904
+ if body is not None:
1905
+ bodyToSignature = body
1906
+ headers = {
1907
+ 'Content-Type': 'application/json',
1908
+ }
1909
+ if urlPath == 'private':
1910
+ self.check_required_credentials()
1911
+ preHash = self.number_to_string(timestamp) + method + fullPath + signatureQuery + bodyToSignature
1912
+ signature = self.hmac(self.encode(preHash), self.encode(self.secret), hashlib.sha256, 'hex')
1913
+ headers['X-FB-ACCESS-KEY'] = self.apiKey
1914
+ headers['X-FB-ACCESS-TIMESTAMP'] = self.number_to_string(timestamp)
1915
+ headers['X-FB-ACCESS-SIGNATURE'] = signature
1916
+ return {'url': url, 'method': method, 'body': body, 'headers': headers}
1917
+
1918
+ def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
1919
+ if response is None:
1920
+ return None
1921
+ error = self.safe_dict(response, 'error')
1922
+ code = self.safe_string(error, 'code')
1923
+ details = self.safe_list(error, 'details')
1924
+ message = self.safe_string(error, 'message')
1925
+ detailsString = ''
1926
+ if details:
1927
+ for i in range(0, len(details)):
1928
+ detailsString = detailsString + details[i] + ' '
1929
+ if error is not None:
1930
+ feedback = self.id + ' ' + message + ' details: ' + detailsString
1931
+ self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
1932
+ self.throw_broadly_matched_exception(self.exceptions['broad'], detailsString, feedback)
1933
+ self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
1934
+ raise ExchangeError(feedback)
1935
+ return None