ccxt 4.4.21__py2.py3-none-any.whl → 4.4.23__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 (76) hide show
  1. ccxt/__init__.py +3 -1
  2. ccxt/abstract/binance.py +64 -43
  3. ccxt/abstract/binancecoinm.py +64 -43
  4. ccxt/abstract/binanceus.py +64 -43
  5. ccxt/abstract/binanceusdm.py +64 -43
  6. ccxt/abstract/bitflyer.py +1 -0
  7. ccxt/abstract/bitget.py +3 -0
  8. ccxt/abstract/cex.py +28 -29
  9. ccxt/abstract/coincatch.py +94 -0
  10. ccxt/abstract/gate.py +5 -0
  11. ccxt/abstract/gateio.py +5 -0
  12. ccxt/abstract/kucoin.py +1 -0
  13. ccxt/abstract/kucoinfutures.py +1 -0
  14. ccxt/abstract/okx.py +1 -0
  15. ccxt/alpaca.py +1 -0
  16. ccxt/async_support/__init__.py +3 -1
  17. ccxt/async_support/alpaca.py +1 -0
  18. ccxt/async_support/base/exchange.py +7 -1
  19. ccxt/async_support/bigone.py +3 -0
  20. ccxt/async_support/binance.py +183 -63
  21. ccxt/async_support/bitfinex.py +4 -0
  22. ccxt/async_support/bitflyer.py +57 -1
  23. ccxt/async_support/bitget.py +73 -1
  24. ccxt/async_support/bitrue.py +3 -0
  25. ccxt/async_support/bybit.py +76 -3
  26. ccxt/async_support/cex.py +1247 -1322
  27. ccxt/async_support/coinbase.py +1 -1
  28. ccxt/async_support/coinbaseexchange.py +3 -0
  29. ccxt/async_support/coincatch.py +4955 -0
  30. ccxt/async_support/coinex.py +60 -1
  31. ccxt/async_support/cryptocom.py +1 -1
  32. ccxt/async_support/gate.py +97 -2
  33. ccxt/async_support/htx.py +1 -5
  34. ccxt/async_support/hyperliquid.py +10 -8
  35. ccxt/async_support/kucoin.py +27 -57
  36. ccxt/async_support/latoken.py +6 -0
  37. ccxt/async_support/mexc.py +1 -1
  38. ccxt/async_support/oceanex.py +2 -0
  39. ccxt/async_support/okcoin.py +1 -0
  40. ccxt/async_support/okx.py +67 -1
  41. ccxt/async_support/poloniex.py +5 -0
  42. ccxt/base/exchange.py +21 -1
  43. ccxt/base/types.py +9 -0
  44. ccxt/bigone.py +3 -0
  45. ccxt/binance.py +183 -63
  46. ccxt/bitfinex.py +4 -0
  47. ccxt/bitflyer.py +57 -1
  48. ccxt/bitget.py +73 -1
  49. ccxt/bitrue.py +3 -0
  50. ccxt/bybit.py +76 -3
  51. ccxt/cex.py +1246 -1322
  52. ccxt/coinbase.py +1 -1
  53. ccxt/coinbaseexchange.py +3 -0
  54. ccxt/coincatch.py +4955 -0
  55. ccxt/coinex.py +60 -1
  56. ccxt/cryptocom.py +1 -1
  57. ccxt/gate.py +97 -2
  58. ccxt/htx.py +1 -5
  59. ccxt/hyperliquid.py +10 -8
  60. ccxt/kucoin.py +27 -57
  61. ccxt/latoken.py +6 -0
  62. ccxt/mexc.py +1 -1
  63. ccxt/oceanex.py +2 -0
  64. ccxt/okcoin.py +1 -0
  65. ccxt/okx.py +67 -1
  66. ccxt/poloniex.py +5 -0
  67. ccxt/pro/__init__.py +3 -1
  68. ccxt/pro/coincatch.py +1429 -0
  69. ccxt/test/tests_async.py +19 -5
  70. ccxt/test/tests_sync.py +19 -5
  71. ccxt-4.4.23.dist-info/METADATA +636 -0
  72. {ccxt-4.4.21.dist-info → ccxt-4.4.23.dist-info}/RECORD +75 -71
  73. ccxt-4.4.21.dist-info/METADATA +0 -635
  74. {ccxt-4.4.21.dist-info → ccxt-4.4.23.dist-info}/LICENSE.txt +0 -0
  75. {ccxt-4.4.21.dist-info → ccxt-4.4.23.dist-info}/WHEEL +0 -0
  76. {ccxt-4.4.21.dist-info → ccxt-4.4.23.dist-info}/top_level.txt +0 -0
ccxt/async_support/cex.py CHANGED
@@ -5,20 +5,15 @@
5
5
 
6
6
  from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.cex import ImplicitAPI
8
+ import asyncio
8
9
  import hashlib
9
- import json
10
- from ccxt.base.types import Balances, Currencies, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees
10
+ from ccxt.base.types import Account, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, TradingFees, Transaction, TransferEntry
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
- from ccxt.base.errors import AuthenticationError
13
+ from ccxt.base.errors import PermissionDenied
14
14
  from ccxt.base.errors import ArgumentsRequired
15
- from ccxt.base.errors import BadSymbol
15
+ from ccxt.base.errors import BadRequest
16
16
  from ccxt.base.errors import InsufficientFunds
17
- from ccxt.base.errors import InvalidOrder
18
- from ccxt.base.errors import OrderNotFound
19
- from ccxt.base.errors import DDoSProtection
20
- from ccxt.base.errors import RateLimitExceeded
21
- from ccxt.base.errors import InvalidNonce
22
17
  from ccxt.base.errors import NullResponse
23
18
  from ccxt.base.decimal_to_precision import TICK_SIZE
24
19
  from ccxt.base.precise import Precise
@@ -31,161 +26,91 @@ class cex(Exchange, ImplicitAPI):
31
26
  'id': 'cex',
32
27
  'name': 'CEX.IO',
33
28
  'countries': ['GB', 'EU', 'CY', 'RU'],
34
- 'rateLimit': 1500,
29
+ 'rateLimit': 1667, # 100 req/min
35
30
  'pro': True,
36
31
  'has': {
37
32
  'CORS': None,
38
33
  'spot': True,
39
- 'margin': False, # has but not through api
34
+ 'margin': False, # has, but not through api
40
35
  'swap': False,
41
36
  'future': False,
42
37
  'option': False,
43
- 'addMargin': False,
44
38
  'cancelAllOrders': True,
45
39
  'cancelOrder': True,
46
- 'cancelOrders': False,
47
- 'createDepositAddress': False,
48
- 'createMarketBuyOrderWithCost': True,
49
- 'createMarketOrderWithCost': False,
50
- 'createMarketSellOrderWithCost': False,
51
40
  'createOrder': True,
52
- 'createStopLimitOrder': False,
53
- 'createStopMarketOrder': False,
54
- 'createStopOrder': False,
55
- 'editOrder': True,
41
+ 'fetchAccounts': True,
56
42
  'fetchBalance': True,
57
43
  'fetchClosedOrders': True,
58
44
  'fetchCurrencies': True,
59
- 'fetchDeposit': False,
60
45
  'fetchDepositAddress': True,
61
- 'fetchDepositAddresses': False,
62
- 'fetchDepositAddressesByNetwork': False,
63
- 'fetchDeposits': False,
64
- 'fetchDepositsWithdrawals': False,
46
+ 'fetchDepositsWithdrawals': True,
65
47
  'fetchFundingHistory': False,
66
48
  'fetchFundingRate': False,
67
49
  'fetchFundingRateHistory': False,
68
50
  'fetchFundingRates': False,
69
- 'fetchIndexOHLCV': False,
70
- 'fetchMarginMode': False,
51
+ 'fetchLedger': True,
71
52
  'fetchMarkets': True,
72
- 'fetchMarkOHLCV': False,
73
53
  'fetchOHLCV': True,
74
- 'fetchOpenInterestHistory': False,
75
54
  'fetchOpenOrders': True,
76
- 'fetchOrder': True,
77
55
  'fetchOrderBook': True,
78
- 'fetchOrders': True,
79
- 'fetchPosition': False,
80
- 'fetchPositionHistory': False,
81
- 'fetchPositionMode': False,
82
- 'fetchPositions': False,
83
- 'fetchPositionsForSymbol': False,
84
- 'fetchPositionsHistory': False,
85
- 'fetchPositionsRisk': False,
86
- 'fetchPremiumIndexOHLCV': False,
87
56
  'fetchTicker': True,
88
57
  'fetchTickers': True,
58
+ 'fetchTime': True,
89
59
  'fetchTrades': True,
90
- 'fetchTradingFee': False,
91
60
  'fetchTradingFees': True,
92
- 'fetchTransactions': False,
93
- 'fetchTransfer': False,
94
- 'fetchTransfers': False,
95
- 'fetchWithdrawal': False,
96
- 'fetchWithdrawals': False,
97
- 'fetchWithdrawalWhitelist': False,
98
- 'reduceMargin': False,
99
- 'setLeverage': False,
100
- 'setMargin': False,
101
- 'setMarginMode': False,
102
- 'transfer': False,
103
- 'withdraw': False,
104
- },
105
- 'timeframes': {
106
- '1m': '1m',
107
- '1h': '1h',
108
- '1d': '1d',
61
+ 'transfer': True,
109
62
  },
110
63
  'urls': {
111
64
  'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
112
65
  'api': {
113
- 'rest': 'https://cex.io/api',
66
+ 'public': 'https://trade.cex.io/api/spot/rest-public',
67
+ 'private': 'https://trade.cex.io/api/spot/rest',
114
68
  },
115
69
  'www': 'https://cex.io',
116
- 'doc': 'https://cex.io/cex-api',
70
+ 'doc': 'https://trade.cex.io/docs/',
117
71
  'fees': [
118
72
  'https://cex.io/fee-schedule',
119
73
  'https://cex.io/limits-commissions',
120
74
  ],
121
75
  'referral': 'https://cex.io/r/0/up105393824/0/',
122
76
  },
123
- 'requiredCredentials': {
124
- 'apiKey': True,
125
- 'secret': True,
126
- 'uid': True,
127
- },
128
77
  'api': {
129
78
  'public': {
130
- 'get': [
131
- 'currency_profile',
132
- 'currency_limits/',
133
- 'last_price/{pair}/',
134
- 'last_prices/{currencies}/',
135
- 'ohlcv/hd/{yyyymmdd}/{pair}',
136
- 'order_book/{pair}/',
137
- 'ticker/{pair}/',
138
- 'tickers/{currencies}/',
139
- 'trade_history/{pair}/',
140
- ],
141
- 'post': [
142
- 'convert/{pair}',
143
- 'price_stats/{pair}',
144
- ],
79
+ 'get': {},
80
+ 'post': {
81
+ 'get_server_time': 1,
82
+ 'get_pairs_info': 1,
83
+ 'get_currencies_info': 1,
84
+ 'get_processing_info': 10,
85
+ 'get_ticker': 1,
86
+ 'get_trade_history': 1,
87
+ 'get_order_book': 1,
88
+ 'get_candles': 1,
89
+ },
145
90
  },
146
91
  'private': {
147
- 'post': [
148
- 'active_orders_status/',
149
- 'archived_orders/{pair}/',
150
- 'balance/',
151
- 'cancel_order/',
152
- 'cancel_orders/{pair}/',
153
- 'cancel_replace_order/{pair}/',
154
- 'close_position/{pair}/',
155
- 'get_address/',
156
- 'get_crypto_address',
157
- 'get_myfee/',
158
- 'get_order/',
159
- 'get_order_tx/',
160
- 'open_orders/{pair}/',
161
- 'open_orders/',
162
- 'open_position/{pair}/',
163
- 'open_positions/{pair}/',
164
- 'place_order/{pair}/',
165
- 'raw_tx_history',
166
- ],
167
- },
168
- },
169
- 'fees': {
170
- 'trading': {
171
- 'maker': self.parse_number('0.0016'),
172
- 'taker': self.parse_number('0.0025'),
173
- },
174
- 'funding': {
175
- 'withdraw': {},
176
- 'deposit': {
177
- # 'USD': amount => amount * 0.035 + 0.25,
178
- # 'EUR': amount => amount * 0.035 + 0.24,
179
- # 'RUB': amount => amount * 0.05 + 15.57,
180
- # 'GBP': amount => amount * 0.035 + 0.2,
181
- 'BTC': 0.0,
182
- 'ETH': 0.0,
183
- 'BCH': 0.0,
184
- 'DASH': 0.0,
185
- 'BTG': 0.0,
186
- 'ZEC': 0.0,
187
- 'XRP': 0.0,
188
- 'XLM': 0.0,
92
+ 'get': {},
93
+ 'post': {
94
+ 'get_my_current_fee': 5,
95
+ 'get_fee_strategy': 1,
96
+ 'get_my_volume': 5,
97
+ 'do_create_account': 1,
98
+ 'get_my_account_status_v3': 5,
99
+ 'get_my_wallet_balance': 5,
100
+ 'get_my_orders': 5,
101
+ 'do_my_new_order': 1,
102
+ 'do_cancel_my_order': 1,
103
+ 'do_cancel_all_orders': 5,
104
+ 'get_order_book': 1,
105
+ 'get_candles': 1,
106
+ 'get_trade_history': 1,
107
+ 'get_my_transaction_history': 1,
108
+ 'get_my_funding_history': 5,
109
+ 'do_my_internal_transfer': 1,
110
+ 'get_processing_info': 10,
111
+ 'get_deposit_address': 5,
112
+ 'do_deposit_funds_from_wallet': 1,
113
+ 'do_withdrawal_funds_to_wallet': 1,
189
114
  },
190
115
  },
191
116
  },
@@ -193,1415 +118,1415 @@ class cex(Exchange, ImplicitAPI):
193
118
  'exceptions': {
194
119
  'exact': {},
195
120
  'broad': {
121
+ 'You have negative balance on following accounts': InsufficientFunds,
122
+ 'Mandatory parameter side should be one of BUY,SELL': BadRequest,
123
+ 'API orders from Main account are not allowed': BadRequest,
124
+ 'check failed': BadRequest,
196
125
  'Insufficient funds': InsufficientFunds,
197
- 'Nonce must be incremented': InvalidNonce,
198
- 'Invalid Order': InvalidOrder,
199
- 'Order not found': OrderNotFound,
200
- 'limit exceeded': RateLimitExceeded, # {"error":"rate limit exceeded"}
201
- 'Invalid API key': AuthenticationError,
202
- 'There was an error while placing your order': InvalidOrder,
203
- 'Sorry, too many clients already': DDoSProtection,
204
- 'Invalid Symbols Pair': BadSymbol,
205
- 'Wrong currency pair': BadSymbol, # {"error":"There was an error while placing your order: Wrong currency pair.","safe":true}
126
+ 'Get deposit address for main account is not allowed': PermissionDenied,
127
+ 'Market Trigger orders are not allowed': BadRequest, # for some reason, triggerPrice does not work for market orders
206
128
  },
207
129
  },
130
+ 'timeframes': {
131
+ '1m': '1m',
132
+ '5m': '5m',
133
+ '15m': '15m',
134
+ '30m': '30m',
135
+ '1h': '1h',
136
+ '2h': '2h',
137
+ '4h': '4h',
138
+ '1d': '1d',
139
+ },
208
140
  'options': {
209
- 'fetchOHLCVWarning': True,
210
- 'createMarketBuyOrderRequiresPrice': True,
211
- 'order': {
212
- 'status': {
213
- 'c': 'canceled',
214
- 'd': 'closed',
215
- 'cd': 'canceled',
216
- 'a': 'open',
217
- },
218
- },
219
- 'defaultNetwork': 'ERC20',
220
- 'defaultNetworks': {
221
- 'USDT': 'TRC20',
222
- },
223
141
  'networks': {
224
- 'ERC20': 'Ethereum',
225
- 'BTC': 'BTC',
226
- 'BEP20': 'Binance Smart Chain',
227
- 'TRC20': 'Tron',
142
+ 'BTC': 'bitcoin',
143
+ 'ERC20': 'ERC20',
144
+ 'BSC20': 'binancesmartchain',
145
+ 'DOGE': 'dogecoin',
146
+ 'ALGO': 'algorand',
147
+ 'XLM': 'stellar',
148
+ 'ATOM': 'cosmos',
149
+ 'LTC': 'litecoin',
150
+ 'XRP': 'ripple',
151
+ 'FTM': 'fantom',
152
+ 'MINA': 'mina',
153
+ 'THETA': 'theta',
154
+ 'XTZ': 'tezos',
155
+ 'TIA': 'celestia',
156
+ 'CRONOS': 'cronos', # CRC20
157
+ 'MATIC': 'polygon',
158
+ 'TON': 'ton',
159
+ 'TRC20': 'tron',
160
+ 'SOLANA': 'solana',
161
+ 'SGB': 'songbird',
162
+ 'DYDX': 'dydx',
163
+ 'DASH': 'dash',
164
+ 'ZIL': 'zilliqa',
165
+ 'EOS': 'eos',
166
+ 'AVALANCHEC': 'avalanche',
167
+ 'ETHPOW': 'ethereumpow',
168
+ 'NEAR': 'near',
169
+ 'ARB': 'arbitrum',
170
+ 'DOT': 'polkadot',
171
+ 'OPT': 'optimism',
172
+ 'INJ': 'injective',
173
+ 'ADA': 'cardano',
174
+ 'ONT': 'ontology',
175
+ 'ICP': 'icp',
176
+ 'KAVA': 'kava',
177
+ 'KSM': 'kusama',
178
+ 'SEI': 'sei',
179
+ # 'OSM': 'osmosis',
180
+ 'NEO': 'neo',
181
+ 'NEO3': 'neo3',
182
+ # 'TERRAOLD': 'terra', # tbd
183
+ # 'TERRA': 'terra2', # tbd
184
+ # 'EVER': 'everscale', # tbd
185
+ 'XDC': 'xdc',
228
186
  },
229
187
  },
230
188
  })
231
189
 
232
- async def fetch_currencies_from_cache(self, params={}):
233
- # self method is now redundant
234
- # currencies are now fetched before markets
235
- options = self.safe_value(self.options, 'fetchCurrencies', {})
236
- timestamp = self.safe_integer(options, 'timestamp')
237
- expires = self.safe_integer(options, 'expires', 1000)
238
- now = self.milliseconds()
239
- if (timestamp is None) or ((now - timestamp) > expires):
240
- response = await self.publicGetCurrencyProfile(params)
241
- self.options['fetchCurrencies'] = self.extend(options, {
242
- 'response': response,
243
- 'timestamp': now,
244
- })
245
- return self.safe_value(self.options['fetchCurrencies'], 'response')
246
-
247
190
  async def fetch_currencies(self, params={}) -> Currencies:
248
191
  """
249
192
  fetches all available currencies on an exchange
193
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-currencies-info
250
194
  :param dict [params]: extra parameters specific to the exchange API endpoint
251
195
  :returns dict: an associative dictionary of currencies
252
196
  """
253
- response = await self.fetch_currencies_from_cache(params)
254
- self.options['currencies'] = {
255
- 'timestamp': self.milliseconds(),
256
- 'response': response,
257
- }
197
+ promises = []
198
+ promises.append(self.publicPostGetCurrenciesInfo(params))
258
199
  #
259
- # {
260
- # "e":"currency_profile",
261
- # "ok":"ok",
262
- # "data":{
263
- # "symbols":[
264
- # {
265
- # "code":"GHS",
266
- # "contract":true,
267
- # "commodity":true,
268
- # "fiat":false,
269
- # "description":"CEX.IO doesn't provide cloud mining services anymore.",
270
- # "precision":8,
271
- # "scale":0,
272
- # "minimumCurrencyAmount":"0.00000001",
273
- # "minimalWithdrawalAmount":-1
274
- # },
275
- # {
276
- # "code":"BTC",
277
- # "contract":false,
278
- # "commodity":false,
279
- # "fiat":false,
280
- # "description":"",
281
- # "precision":8,
282
- # "scale":0,
283
- # "minimumCurrencyAmount":"0.00000001",
284
- # "minimalWithdrawalAmount":0.002
285
- # },
286
- # {
287
- # "code":"ETH",
288
- # "contract":false,
289
- # "commodity":false,
290
- # "fiat":false,
291
- # "description":"",
292
- # "precision":8,
293
- # "scale":2,
294
- # "minimumCurrencyAmount":"0.00000100",
295
- # "minimalWithdrawalAmount":0.01
296
- # }
297
- # ],
298
- # "pairs":[
299
- # {
300
- # "symbol1":"BTC",
301
- # "symbol2":"USD",
302
- # "pricePrecision":1,
303
- # "priceScale":"/1000000",
304
- # "minLotSize":0.002,
305
- # "minLotSizeS2":20
306
- # },
307
- # {
308
- # "symbol1":"ETH",
309
- # "symbol2":"USD",
310
- # "pricePrecision":2,
311
- # "priceScale":"/10000",
312
- # "minLotSize":0.1,
313
- # "minLotSizeS2":20
314
- # }
315
- # ]
316
- # }
317
- # }
200
+ # {
201
+ # "ok": "ok",
202
+ # "data": [
203
+ # {
204
+ # "currency": "ZAP",
205
+ # "fiat": False,
206
+ # "precision": "8",
207
+ # "walletPrecision": "6",
208
+ # "walletDeposit": True,
209
+ # "walletWithdrawal": True
210
+ # },
211
+ # ...
318
212
  #
319
- data = self.safe_value(response, 'data', [])
320
- currencies = self.safe_value(data, 'symbols', [])
321
- result: dict = {}
322
- for i in range(0, len(currencies)):
323
- currency = currencies[i]
324
- id = self.safe_string(currency, 'code')
325
- code = self.safe_currency_code(id)
326
- active = True
327
- result[code] = {
328
- 'id': id,
329
- 'code': code,
330
- 'name': id,
331
- 'active': active,
332
- 'deposit': None,
333
- 'withdraw': None,
334
- 'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'precision'))),
335
- 'fee': None,
213
+ promises.append(self.publicPostGetProcessingInfo(params))
214
+ #
215
+ # {
216
+ # "ok": "ok",
217
+ # "data": {
218
+ # "ADA": {
219
+ # "name": "Cardano",
220
+ # "blockchains": {
221
+ # "cardano": {
222
+ # "type": "coin",
223
+ # "deposit": "enabled",
224
+ # "minDeposit": "1",
225
+ # "withdrawal": "enabled",
226
+ # "minWithdrawal": "5",
227
+ # "withdrawalFee": "1",
228
+ # "withdrawalFeePercent": "0",
229
+ # "depositConfirmations": "15"
230
+ # }
231
+ # }
232
+ # },
233
+ # ...
234
+ #
235
+ responses = await asyncio.gather(*promises)
236
+ dataCurrencies = self.safe_list(responses[0], 'data', [])
237
+ dataNetworks = self.safe_dict(responses[1], 'data', {})
238
+ currenciesIndexed = self.index_by(dataCurrencies, 'currency')
239
+ data = self.deep_extend(currenciesIndexed, dataNetworks)
240
+ return self.parse_currencies(self.to_array(data))
241
+
242
+ def parse_currency(self, rawCurrency: dict) -> Currency:
243
+ id = self.safe_string(rawCurrency, 'currency')
244
+ code = self.safe_currency_code(id)
245
+ type = 'fiat' if self.safe_bool(rawCurrency, 'fiat') else 'crypto'
246
+ currencyDepositEnabled = self.safe_bool(rawCurrency, 'walletDeposit')
247
+ currencyWithdrawEnabled = self.safe_bool(rawCurrency, 'walletWithdrawal')
248
+ currencyPrecision = self.parse_number(self.parse_precision(self.safe_string(rawCurrency, 'precision')))
249
+ networks: dict = {}
250
+ rawNetworks = self.safe_dict(rawCurrency, 'blockchains', {})
251
+ keys = list(rawNetworks.keys())
252
+ for j in range(0, len(keys)):
253
+ networkId = keys[j]
254
+ rawNetwork = rawNetworks[networkId]
255
+ networkCode = self.network_id_to_code(networkId)
256
+ deposit = self.safe_string(rawNetwork, 'deposit') == 'enabled'
257
+ withdraw = self.safe_string(rawNetwork, 'withdrawal') == 'enabled'
258
+ networks[networkCode] = {
259
+ 'id': networkId,
260
+ 'network': networkCode,
261
+ 'margin': None,
262
+ 'deposit': deposit,
263
+ 'withdraw': withdraw,
264
+ 'fee': self.safe_number(rawNetwork, 'withdrawalFee'),
265
+ 'precision': currencyPrecision,
336
266
  'limits': {
337
- 'amount': {
338
- 'min': self.safe_number(currency, 'minimumCurrencyAmount'),
267
+ 'deposit': {
268
+ 'min': self.safe_number(rawNetwork, 'minDeposit'),
339
269
  'max': None,
340
270
  },
341
271
  'withdraw': {
342
- 'min': self.safe_number(currency, 'minimalWithdrawalAmount'),
272
+ 'min': self.safe_number(rawNetwork, 'minWithdrawal'),
343
273
  'max': None,
344
274
  },
345
275
  },
346
- 'info': currency,
276
+ 'info': rawNetwork,
347
277
  }
348
- return result
278
+ return self.safe_currency_structure({
279
+ 'id': id,
280
+ 'code': code,
281
+ 'name': None,
282
+ 'type': type,
283
+ 'active': None,
284
+ 'deposit': currencyDepositEnabled,
285
+ 'withdraw': currencyWithdrawEnabled,
286
+ 'fee': None,
287
+ 'precision': currencyPrecision,
288
+ 'limits': {
289
+ 'amount': {
290
+ 'min': None,
291
+ 'max': None,
292
+ },
293
+ 'withdraw': {
294
+ 'min': None,
295
+ 'max': None,
296
+ },
297
+ },
298
+ 'networks': networks,
299
+ 'info': rawCurrency,
300
+ })
349
301
 
350
302
  async def fetch_markets(self, params={}) -> List[Market]:
351
303
  """
352
- retrieves data on all markets for cex
304
+ retrieves data on all markets for ace
305
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-pairs-info
353
306
  :param dict [params]: extra parameters specific to the exchange API endpoint
354
307
  :returns dict[]: an array of objects representing market data
355
308
  """
356
- currenciesResponse = await self.fetch_currencies_from_cache(params)
357
- currenciesData = self.safe_value(currenciesResponse, 'data', {})
358
- currencies = self.safe_value(currenciesData, 'symbols', [])
359
- currenciesById = self.index_by(currencies, 'code')
360
- pairs = self.safe_value(currenciesData, 'pairs', [])
361
- response = await self.publicGetCurrencyLimits(params)
309
+ response = await self.publicPostGetPairsInfo(params)
362
310
  #
363
- # {
364
- # "e":"currency_limits",
365
- # "ok":"ok",
366
- # "data": {
367
- # "pairs":[
368
- # {
369
- # "symbol1":"BTC",
370
- # "symbol2":"USD",
371
- # "minLotSize":0.002,
372
- # "minLotSizeS2":20,
373
- # "maxLotSize":30,
374
- # "minPrice":"1500",
375
- # "maxPrice":"35000"
376
- # },
377
- # {
378
- # "symbol1":"BCH",
379
- # "symbol2":"EUR",
380
- # "minLotSize":0.1,
381
- # "minLotSizeS2":20,
382
- # "maxLotSize":null,
383
- # "minPrice":"25",
384
- # "maxPrice":"8192"
385
- # }
386
- # ]
387
- # }
388
- # }
311
+ # {
312
+ # "ok": "ok",
313
+ # "data": [
314
+ # {
315
+ # "base": "AI",
316
+ # "quote": "USD",
317
+ # "baseMin": "30",
318
+ # "baseMax": "2516000",
319
+ # "baseLotSize": "0.000001",
320
+ # "quoteMin": "10",
321
+ # "quoteMax": "1000000",
322
+ # "quoteLotSize": "0.01000000",
323
+ # "basePrecision": "6",
324
+ # "quotePrecision": "8",
325
+ # "pricePrecision": "4",
326
+ # "minPrice": "0.0377",
327
+ # "maxPrice": "19.5000"
328
+ # },
329
+ # ...
389
330
  #
390
- result = []
391
- markets = self.safe_value(response['data'], 'pairs')
392
- for i in range(0, len(markets)):
393
- market = markets[i]
394
- baseId = self.safe_string(market, 'symbol1')
395
- quoteId = self.safe_string(market, 'symbol2')
396
- base = self.safe_currency_code(baseId)
397
- quote = self.safe_currency_code(quoteId)
398
- baseCurrency = self.safe_value(currenciesById, baseId, {})
399
- quoteCurrency = self.safe_value(currenciesById, quoteId, {})
400
- pricePrecisionString = self.safe_string(quoteCurrency, 'precision', '8')
401
- for j in range(0, len(pairs)):
402
- pair = pairs[j]
403
- if (pair['symbol1'] == baseId) and (pair['symbol2'] == quoteId):
404
- # we might need to account for `priceScale` here
405
- pricePrecisionString = self.safe_string(pair, 'pricePrecision', pricePrecisionString)
406
- baseCurrencyPrecision = self.safe_string(baseCurrency, 'precision', '8')
407
- baseCurrencyScale = self.safe_string(baseCurrency, 'scale', '0')
408
- amountPrecisionString = Precise.string_sub(baseCurrencyPrecision, baseCurrencyScale)
409
- result.append({
410
- 'id': baseId + '/' + quoteId,
411
- 'symbol': base + '/' + quote,
412
- 'base': base,
413
- 'quote': quote,
414
- 'settle': None,
415
- 'baseId': baseId,
416
- 'quoteId': quoteId,
417
- 'settleId': None,
418
- 'type': 'spot',
419
- 'spot': True,
420
- 'margin': None,
421
- 'swap': False,
422
- 'future': False,
423
- 'option': False,
424
- 'active': None,
425
- 'contract': False,
426
- 'linear': None,
427
- 'inverse': None,
428
- 'contractSize': None,
429
- 'expiry': None,
430
- 'expiryDatetime': None,
431
- 'strike': None,
432
- 'optionType': None,
433
- 'precision': {
434
- 'amount': self.parse_number(self.parse_precision(amountPrecisionString)),
435
- 'price': self.parse_number(self.parse_precision(pricePrecisionString)),
331
+ data = self.safe_list(response, 'data', [])
332
+ return self.parse_markets(data)
333
+
334
+ def parse_market(self, market: dict) -> Market:
335
+ baseId = self.safe_string(market, 'base')
336
+ base = self.safe_currency_code(baseId)
337
+ quoteId = self.safe_string(market, 'quote')
338
+ quote = self.safe_currency_code(quoteId)
339
+ id = base + '-' + quote # not actual id, but for self exchange we can use self abbreviation, because e.g. tickers have hyphen in between
340
+ symbol = base + '/' + quote
341
+ return self.safe_market_structure({
342
+ 'id': id,
343
+ 'symbol': symbol,
344
+ 'base': base,
345
+ 'baseId': baseId,
346
+ 'quote': quote,
347
+ 'quoteId': quoteId,
348
+ 'settle': None,
349
+ 'settleId': None,
350
+ 'type': 'spot',
351
+ 'spot': True,
352
+ 'margin': False,
353
+ 'swap': False,
354
+ 'future': False,
355
+ 'option': False,
356
+ 'contract': False,
357
+ 'linear': None,
358
+ 'inverse': None,
359
+ 'contractSize': None,
360
+ 'expiry': None,
361
+ 'expiryDatetime': None,
362
+ 'strike': None,
363
+ 'optionType': None,
364
+ 'limits': {
365
+ 'amount': {
366
+ 'min': self.safe_number(market, 'baseMin'),
367
+ 'max': self.safe_number(market, 'baseMax'),
436
368
  },
437
- 'limits': {
438
- 'leverage': {
439
- 'min': None,
440
- 'max': None,
441
- },
442
- 'amount': {
443
- 'min': self.safe_number(market, 'minLotSize'),
444
- 'max': self.safe_number(market, 'maxLotSize'),
445
- },
446
- 'price': {
447
- 'min': self.safe_number(market, 'minPrice'),
448
- 'max': self.safe_number(market, 'maxPrice'),
449
- },
450
- 'cost': {
451
- 'min': self.safe_number(market, 'minLotSizeS2'),
452
- 'max': None,
453
- },
369
+ 'price': {
370
+ 'min': self.safe_number(market, 'minPrice'),
371
+ 'max': self.safe_number(market, 'maxPrice'),
454
372
  },
455
- 'created': None,
456
- 'info': market,
457
- })
458
- return result
459
-
460
- def parse_balance(self, response) -> Balances:
461
- result: dict = {'info': response}
462
- ommited = ['username', 'timestamp']
463
- balances = self.omit(response, ommited)
464
- currencyIds = list(balances.keys())
465
- for i in range(0, len(currencyIds)):
466
- currencyId = currencyIds[i]
467
- balance = self.safe_value(balances, currencyId, {})
468
- account = self.account()
469
- account['free'] = self.safe_string(balance, 'available')
470
- # https://github.com/ccxt/ccxt/issues/5484
471
- account['used'] = self.safe_string(balance, 'orders', '0')
472
- code = self.safe_currency_code(currencyId)
473
- result[code] = account
474
- return self.safe_balance(result)
373
+ 'cost': {
374
+ 'min': self.safe_number(market, 'quoteMin'),
375
+ 'max': self.safe_number(market, 'quoteMax'),
376
+ },
377
+ 'leverage': {
378
+ 'min': None,
379
+ 'max': None,
380
+ },
381
+ },
382
+ 'precision': {
383
+ 'amount': self.safe_string(market, 'baseLotSize'),
384
+ 'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
385
+ # 'cost': self.parse_number(self.parse_precision(self.safe_string(market, 'quoteLotSize'))), # buggy, doesn't reflect their documentation
386
+ 'base': self.parse_number(self.parse_precision(self.safe_string(market, 'basePrecision'))),
387
+ 'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
388
+ },
389
+ 'active': None,
390
+ 'created': None,
391
+ 'info': market,
392
+ })
475
393
 
476
- async def fetch_balance(self, params={}) -> Balances:
394
+ async def fetch_time(self, params={}):
477
395
  """
478
- :see: https://docs.cex.io/#account-balance
479
- query for balance and get the amount of funds available for trading or funds locked in orders
396
+ fetches the current integer timestamp in milliseconds from the exchange server
480
397
  :param dict [params]: extra parameters specific to the exchange API endpoint
481
- :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
398
+ :returns int: the current integer timestamp in milliseconds from the exchange server
482
399
  """
483
- await self.load_markets()
484
- response = await self.privatePostBalance(params)
485
- return self.parse_balance(response)
400
+ response = await self.publicPostGetServerTime(params)
401
+ #
402
+ # {
403
+ # "ok": "ok",
404
+ # "data": {
405
+ # "timestamp": "1728472063472",
406
+ # "ISODate": "2024-10-09T11:07:43.472Z"
407
+ # }
408
+ # }
409
+ #
410
+ data = self.safe_dict(response, 'data')
411
+ timestamp = self.safe_integer(data, 'timestamp')
412
+ return timestamp
486
413
 
487
- async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
414
+ async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
488
415
  """
489
- :see: https://docs.cex.io/#orderbook
490
- fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
491
- :param str symbol: unified symbol of the market to fetch the order book for
492
- :param int [limit]: the maximum amount of order book entries to return
416
+ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
417
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-ticker
418
+ :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
493
419
  :param dict [params]: extra parameters specific to the exchange API endpoint
494
- :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
420
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
495
421
  """
496
422
  await self.load_markets()
497
- market = self.market(symbol)
498
- request: dict = {
499
- 'pair': market['id'],
500
- }
501
- if limit is not None:
502
- request['depth'] = limit
503
- response = await self.publicGetOrderBookPair(self.extend(request, params))
504
- timestamp = self.safe_timestamp(response, 'timestamp')
505
- return self.parse_order_book(response, market['symbol'], timestamp)
506
-
507
- def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
508
- #
509
- # [
510
- # 1591403940,
511
- # 0.024972,
512
- # 0.024972,
513
- # 0.024969,
514
- # 0.024969,
515
- # 0.49999900
516
- # ]
517
- #
518
- return [
519
- self.safe_timestamp(ohlcv, 0),
520
- self.safe_number(ohlcv, 1),
521
- self.safe_number(ohlcv, 2),
522
- self.safe_number(ohlcv, 3),
523
- self.safe_number(ohlcv, 4),
524
- self.safe_number(ohlcv, 5),
525
- ]
423
+ response = await self.fetch_tickers([symbol], params)
424
+ return self.safe_dict(response, symbol, {})
526
425
 
527
- async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
426
+ async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
528
427
  """
529
- :see: https://docs.cex.io/#historical-ohlcv-chart
530
- fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
531
- :param str symbol: unified symbol of the market to fetch OHLCV data for
532
- :param str timeframe: the length of time each candle represents
533
- :param int [since]: timestamp in ms of the earliest candle to fetch
534
- :param int [limit]: the maximum amount of candles to fetch
428
+ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
429
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-ticker
430
+ :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
535
431
  :param dict [params]: extra parameters specific to the exchange API endpoint
536
- :returns int[][]: A list of candles ordered, open, high, low, close, volume
432
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
537
433
  """
538
434
  await self.load_markets()
539
- market = self.market(symbol)
540
- if since is None:
541
- since = self.milliseconds() - 86400000 # yesterday
542
- else:
543
- if self.options['fetchOHLCVWarning']:
544
- raise ExchangeError(self.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, self might produce an empty or None reply. Set exchange.options['fetchOHLCVWarning'] = False or add({'options': {'fetchOHLCVWarning': False}}) to constructor params to suppress self warning message.")
545
- request: dict = {
546
- 'pair': market['id'],
547
- 'yyyymmdd': self.yyyymmdd(since, ''),
548
- }
549
- try:
550
- response = await self.publicGetOhlcvHdYyyymmddPair(self.extend(request, params))
551
- #
552
- # {
553
- # "time":20200606,
554
- # "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]",
555
- # }
556
- #
557
- key = 'data' + self.safe_string(self.timeframes, timeframe, timeframe)
558
- data = self.safe_string(response, key)
559
- ohlcvs = json.loads(data)
560
- return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
561
- except Exception as e:
562
- if isinstance(e, NullResponse):
563
- return []
564
- return None
435
+ request = {}
436
+ if symbols is not None:
437
+ request['pairs'] = self.market_ids(symbols)
438
+ response = await self.publicPostGetTicker(self.extend(request, params))
439
+ #
440
+ # {
441
+ # "ok": "ok",
442
+ # "data": {
443
+ # "AI-USD": {
444
+ # "bestBid": "0.3917",
445
+ # "bestAsk": "0.3949",
446
+ # "bestBidChange": "0.0035",
447
+ # "bestBidChangePercentage": "0.90",
448
+ # "bestAskChange": "0.0038",
449
+ # "bestAskChangePercentage": "0.97",
450
+ # "low": "0.3787",
451
+ # "high": "0.3925",
452
+ # "volume30d": "2945.722277",
453
+ # "lastTradeDateISO": "2024-10-11T06:18:42.077Z",
454
+ # "volume": "120.736000",
455
+ # "quoteVolume": "46.65654070",
456
+ # "lastTradeVolume": "67.914000",
457
+ # "volumeUSD": "46.65",
458
+ # "last": "0.3949",
459
+ # "lastTradePrice": "0.3925",
460
+ # "priceChange": "0.0038",
461
+ # "priceChangePercentage": "0.97"
462
+ # },
463
+ # ...
464
+ #
465
+ data = self.safe_dict(response, 'data', {})
466
+ return self.parse_tickers(data, symbols)
565
467
 
566
468
  def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
567
- timestamp = self.safe_timestamp(ticker, 'timestamp')
568
- volume = self.safe_string(ticker, 'volume')
569
- high = self.safe_string(ticker, 'high')
570
- low = self.safe_string(ticker, 'low')
571
- bid = self.safe_string(ticker, 'bid')
572
- ask = self.safe_string(ticker, 'ask')
573
- last = self.safe_string(ticker, 'last')
574
- symbol = self.safe_symbol(None, market)
469
+ marketId = self.safe_string(ticker, 'id')
470
+ symbol = self.safe_symbol(marketId, market)
575
471
  return self.safe_ticker({
576
472
  'symbol': symbol,
577
- 'timestamp': timestamp,
578
- 'datetime': self.iso8601(timestamp),
579
- 'high': high,
580
- 'low': low,
581
- 'bid': bid,
473
+ 'timestamp': None,
474
+ 'datetime': None,
475
+ 'high': self.safe_number(ticker, 'high'),
476
+ 'low': self.safe_number(ticker, 'low'),
477
+ 'bid': self.safe_number(ticker, 'bestBid'),
582
478
  'bidVolume': None,
583
- 'ask': ask,
479
+ 'ask': self.safe_number(ticker, 'bestAsk'),
584
480
  'askVolume': None,
585
481
  'vwap': None,
586
482
  'open': None,
587
- 'close': last,
588
- 'last': last,
483
+ 'close': self.safe_string(ticker, 'lastTradePrice'),
589
484
  'previousClose': None,
590
- 'change': None,
591
- 'percentage': None,
485
+ 'change': self.safe_number(ticker, 'priceChange'),
486
+ 'percentage': self.safe_number(ticker, 'priceChangePercentage'),
592
487
  'average': None,
593
- 'baseVolume': volume,
594
- 'quoteVolume': None,
488
+ 'baseVolume': self.safe_string(ticker, 'volume'),
489
+ 'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
595
490
  'info': ticker,
596
491
  }, market)
597
492
 
598
- async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
599
- """
600
- fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
601
- :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
602
- :param dict [params]: extra parameters specific to the exchange API endpoint
603
- :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
604
- """
605
- await self.load_markets()
606
- symbols = self.market_symbols(symbols)
607
- currencies = list(self.currencies.keys())
608
- request: dict = {
609
- 'currencies': '/'.join(currencies),
610
- }
611
- response = await self.publicGetTickersCurrencies(self.extend(request, params))
612
- tickers = self.safe_value(response, 'data', [])
613
- result: dict = {}
614
- for t in range(0, len(tickers)):
615
- ticker = tickers[t]
616
- marketId = self.safe_string(ticker, 'pair')
617
- market = self.safe_market(marketId, None, ':')
618
- symbol = market['symbol']
619
- result[symbol] = self.parse_ticker(ticker, market)
620
- return self.filter_by_array_tickers(result, 'symbol', symbols)
621
-
622
- async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
493
+ async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
623
494
  """
624
- :see: https://docs.cex.io/#ticker
625
- fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
626
- :param str symbol: unified symbol of the market to fetch the ticker for
495
+ get the list of most recent trades for a particular symbol
496
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-trade-history
497
+ :param str symbol: unified symbol of the market to fetch trades for
498
+ :param int [since]: timestamp in ms of the earliest trade to fetch
499
+ :param int [limit]: the maximum amount of trades to fetch
627
500
  :param dict [params]: extra parameters specific to the exchange API endpoint
628
- :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
501
+ :param int [params.until]: timestamp in ms of the latest entry
502
+ :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
629
503
  """
630
504
  await self.load_markets()
631
505
  market = self.market(symbol)
632
506
  request: dict = {
633
507
  'pair': market['id'],
634
508
  }
635
- ticker = await self.publicGetTickerPair(self.extend(request, params))
636
- return self.parse_ticker(ticker, market)
509
+ if since is not None:
510
+ request['fromDateISO'] = self.iso8601(since)
511
+ until = None
512
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
513
+ if until is not None:
514
+ request['toDateISO'] = self.iso8601(until)
515
+ if limit is not None:
516
+ request['pageSize'] = min(limit, 10000) # has a bug, still returns more trades
517
+ response = await self.publicPostGetTradeHistory(self.extend(request, params))
518
+ #
519
+ # {
520
+ # "ok": "ok",
521
+ # "data": {
522
+ # "pageSize": "10",
523
+ # "trades": [
524
+ # {
525
+ # "tradeId": "1728630559823-0",
526
+ # "dateISO": "2024-10-11T07:09:19.823Z",
527
+ # "side": "SELL",
528
+ # "price": "60879.5",
529
+ # "amount": "0.00165962"
530
+ # },
531
+ # ... followed by older trades
532
+ #
533
+ data = self.safe_dict(response, 'data', {})
534
+ trades = self.safe_list(data, 'trades', [])
535
+ return self.parse_trades(trades, market, since, limit)
637
536
 
638
537
  def parse_trade(self, trade: dict, market: Market = None) -> Trade:
639
538
  #
640
- # fetchTrades(public)
539
+ # public fetchTrades
641
540
  #
642
- # {
643
- # "type": "sell",
644
- # "date": "1638401878",
645
- # "amount": "0.401000",
646
- # "price": "249",
647
- # "tid": "11922"
648
- # }
541
+ # {
542
+ # "tradeId": "1728630559823-0",
543
+ # "dateISO": "2024-10-11T07:09:19.823Z",
544
+ # "side": "SELL",
545
+ # "price": "60879.5",
546
+ # "amount": "0.00165962"
547
+ # },
649
548
  #
650
- timestamp = self.safe_timestamp(trade, 'date')
651
- id = self.safe_string(trade, 'tid')
652
- type = None
653
- side = self.safe_string(trade, 'type')
654
- priceString = self.safe_string(trade, 'price')
655
- amountString = self.safe_string(trade, 'amount')
549
+ dateStr = self.safe_string(trade, 'dateISO')
550
+ timestamp = self.parse8601(dateStr)
656
551
  market = self.safe_market(None, market)
657
552
  return self.safe_trade({
658
553
  'info': trade,
659
- 'id': id,
660
554
  'timestamp': timestamp,
661
555
  'datetime': self.iso8601(timestamp),
662
556
  'symbol': market['symbol'],
663
- 'type': type,
664
- 'side': side,
557
+ 'id': self.safe_string(trade, 'tradeId'),
665
558
  'order': None,
559
+ 'type': None,
666
560
  'takerOrMaker': None,
667
- 'price': priceString,
668
- 'amount': amountString,
561
+ 'side': self.safe_string_lower(trade, 'side'),
562
+ 'price': self.safe_string(trade, 'price'),
563
+ 'amount': self.safe_string(trade, 'amount'),
669
564
  'cost': None,
670
565
  'fee': None,
671
566
  }, market)
672
567
 
673
- async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
568
+ async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
674
569
  """
675
- :see: https://docs.cex.io/#trade-history
676
- get the list of most recent trades for a particular symbol
677
- :param str symbol: unified symbol of the market to fetch trades for
678
- :param int [since]: timestamp in ms of the earliest trade to fetch
679
- :param int [limit]: the maximum amount of trades to fetch
570
+ fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
571
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-order-book
572
+ :param str symbol: unified symbol of the market to fetch the order book for
573
+ :param int [limit]: the maximum amount of order book entries to return
680
574
  :param dict [params]: extra parameters specific to the exchange API endpoint
681
- :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
575
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
682
576
  """
683
577
  await self.load_markets()
684
578
  market = self.market(symbol)
685
579
  request: dict = {
686
580
  'pair': market['id'],
687
581
  }
688
- response = await self.publicGetTradeHistoryPair(self.extend(request, params))
689
- return self.parse_trades(response, market, since, limit)
582
+ response = await self.publicPostGetOrderBook(self.extend(request, params))
583
+ #
584
+ # {
585
+ # "ok": "ok",
586
+ # "data": {
587
+ # "timestamp": "1728636922648",
588
+ # "currency1": "BTC",
589
+ # "currency2": "USDT",
590
+ # "bids": [
591
+ # [
592
+ # "60694.1",
593
+ # "13.12849761"
594
+ # ],
595
+ # [
596
+ # "60694.0",
597
+ # "0.71829244"
598
+ # ],
599
+ # ...
600
+ #
601
+ orderBook = self.safe_dict(response, 'data', {})
602
+ timestamp = self.safe_integer(orderBook, 'timestamp')
603
+ return self.parse_order_book(orderBook, market['symbol'], timestamp)
604
+
605
+ async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
606
+ """
607
+ fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
608
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-candles
609
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
610
+ :param str timeframe: the length of time each candle represents
611
+ :param int [since]: timestamp in ms of the earliest candle to fetch
612
+ :param int [limit]: the maximum amount of candles to fetch
613
+ :param dict [params]: extra parameters specific to the exchange API endpoint
614
+ :param int [params.until]: timestamp in ms of the latest entry
615
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
616
+ """
617
+ dataType = None
618
+ dataType, params = self.handle_option_and_params(params, 'fetchOHLCV', 'dataType')
619
+ if dataType is None:
620
+ raise ArgumentsRequired(self.id + ' fetchOHLCV requires a parameter "dataType" to be either "bestBid" or "bestAsk"')
621
+ await self.load_markets()
622
+ market = self.market(symbol)
623
+ request: dict = {
624
+ 'pair': market['id'],
625
+ 'resolution': self.timeframes[timeframe],
626
+ 'dataType': dataType,
627
+ }
628
+ if since is not None:
629
+ request['fromISO'] = self.iso8601(since)
630
+ until = None
631
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
632
+ if until is not None:
633
+ request['toISO'] = self.iso8601(until)
634
+ elif since is None:
635
+ # exchange still requires that we provide one of them
636
+ request['toISO'] = self.iso8601(self.milliseconds())
637
+ if since is not None and until is not None and limit is not None:
638
+ raise ArgumentsRequired(self.id + ' fetchOHLCV does not support fetching candles with both a limit and since/until')
639
+ elif (since is not None or until is not None) and limit is None:
640
+ raise ArgumentsRequired(self.id + ' fetchOHLCV requires a limit parameter when fetching candles with since or until')
641
+ if limit is not None:
642
+ request['limit'] = limit
643
+ response = await self.publicPostGetCandles(self.extend(request, params))
644
+ #
645
+ # {
646
+ # "ok": "ok",
647
+ # "data": [
648
+ # {
649
+ # "timestamp": "1728643320000",
650
+ # "open": "61061",
651
+ # "high": "61095.1",
652
+ # "low": "61048.5",
653
+ # "close": "61087.8",
654
+ # "volume": "0",
655
+ # "resolution": "1m",
656
+ # "isClosed": True,
657
+ # "timestampISO": "2024-10-11T10:42:00.000Z"
658
+ # },
659
+ # ...
660
+ #
661
+ data = self.safe_list(response, 'data', [])
662
+ return self.parse_ohlcvs(data, market, timeframe, since, limit)
663
+
664
+ def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
665
+ return [
666
+ self.safe_integer(ohlcv, 'timestamp'),
667
+ self.safe_number(ohlcv, 'open'),
668
+ self.safe_number(ohlcv, 'high'),
669
+ self.safe_number(ohlcv, 'low'),
670
+ self.safe_number(ohlcv, 'close'),
671
+ self.safe_number(ohlcv, 'volume'),
672
+ ]
690
673
 
691
674
  async def fetch_trading_fees(self, params={}) -> TradingFees:
692
675
  """
693
- :see: https://docs.cex.io/#get-my-fee
694
676
  fetch the trading fees for multiple markets
677
+ :see: https://trade.cex.io/docs/#rest-public-api-calls-candles
695
678
  :param dict [params]: extra parameters specific to the exchange API endpoint
696
679
  :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
697
680
  """
698
681
  await self.load_markets()
699
- response = await self.privatePostGetMyfee(params)
682
+ response = await self.privatePostGetMyCurrentFee(params)
700
683
  #
701
- # {
702
- # "e": "get_myfee",
703
- # "ok": "ok",
704
- # "data": {
705
- # 'BTC:USD': {buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: "0.15"},
706
- # 'ETH:USD': {buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: "0.15"},
707
- # ..
708
- # }
709
- # }
684
+ # {
685
+ # "ok": "ok",
686
+ # "data": {
687
+ # "tradingFee": {
688
+ # "AI-USD": {
689
+ # "percent": "0.25"
690
+ # },
691
+ # ...
710
692
  #
711
- data = self.safe_value(response, 'data', {})
693
+ data = self.safe_dict(response, 'data', {})
694
+ fees = self.safe_dict(data, 'tradingFee', {})
695
+ return self.parse_trading_fees(fees, True)
696
+
697
+ def parse_trading_fees(self, response, useKeyAsId=False) -> TradingFees:
712
698
  result: dict = {}
699
+ keys = list(response.keys())
700
+ for i in range(0, len(keys)):
701
+ key = keys[i]
702
+ market = None
703
+ if useKeyAsId:
704
+ market = self.safe_market(key)
705
+ parsed = self.parse_trading_fee(response[key], market)
706
+ result[parsed['symbol']] = parsed
713
707
  for i in range(0, len(self.symbols)):
714
708
  symbol = self.symbols[i]
715
- market = self.market(symbol)
716
- fee = self.safe_value(data, market['id'], {})
717
- makerString = self.safe_string(fee, 'buyMaker')
718
- takerString = self.safe_string(fee, 'buy')
719
- maker = self.parse_number(Precise.string_div(makerString, '100'))
720
- taker = self.parse_number(Precise.string_div(takerString, '100'))
721
- result[symbol] = {
722
- 'info': fee,
723
- 'symbol': symbol,
724
- 'maker': maker,
725
- 'taker': taker,
726
- 'percentage': True,
727
- }
709
+ if not (symbol in result):
710
+ market = self.market(symbol)
711
+ result[symbol] = self.parse_trading_fee(response, market)
728
712
  return result
729
713
 
714
+ def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
715
+ return {
716
+ 'info': fee,
717
+ 'symbol': self.safe_string(market, 'symbol'),
718
+ 'maker': self.safe_number(fee, 'percent'),
719
+ 'taker': self.safe_number(fee, 'percent'),
720
+ 'percentage': None,
721
+ 'tierBased': None,
722
+ }
723
+
724
+ async def fetch_accounts(self, params={}) -> List[Account]:
725
+ await self.load_markets()
726
+ response = await self.privatePostGetMyAccountStatusV3(params)
727
+ #
728
+ # {
729
+ # "ok": "ok",
730
+ # "data": {
731
+ # "convertedCurrency": "USD",
732
+ # "balancesPerAccounts": {
733
+ # "": {
734
+ # "AI": {
735
+ # "balance": "0.000000",
736
+ # "balanceOnHold": "0.000000"
737
+ # },
738
+ # "USDT": {
739
+ # "balance": "0.00000000",
740
+ # "balanceOnHold": "0.00000000"
741
+ # }
742
+ # }
743
+ # }
744
+ # }
745
+ # }
746
+ #
747
+ data = self.safe_dict(response, 'data', {})
748
+ balances = self.safe_dict(data, 'balancesPerAccounts', {})
749
+ arrays = self.to_array(balances)
750
+ return self.parse_accounts(arrays, params)
751
+
752
+ def parse_account(self, account: dict) -> Account:
753
+ return {
754
+ 'id': None,
755
+ 'type': None,
756
+ 'code': None,
757
+ 'info': account,
758
+ }
759
+
760
+ async def fetch_balance(self, params={}) -> Balances:
761
+ """
762
+ query for balance and get the amount of funds available for trading or funds locked in orders
763
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-account-status-v3
764
+ :param dict [params]: extra parameters specific to the exchange API endpoint
765
+ :param dict [params.method]: 'privatePostGetMyWalletBalance' or 'privatePostGetMyAccountStatusV3'
766
+ :param dict [params.account]: in case 'privatePostGetMyAccountStatusV3' is chosen, self can specify the account name(default is empty string)
767
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
768
+ """
769
+ accountName = None
770
+ accountName, params = self.handle_param_string(params, 'account', '') # default is empty string
771
+ method = None
772
+ method, params = self.handle_param_string(params, 'method', 'privatePostGetMyWalletBalance')
773
+ accountBalance = None
774
+ if method == 'privatePostGetMyAccountStatusV3':
775
+ response = await self.privatePostGetMyAccountStatusV3(params)
776
+ #
777
+ # {
778
+ # "ok": "ok",
779
+ # "data": {
780
+ # "convertedCurrency": "USD",
781
+ # "balancesPerAccounts": {
782
+ # "": {
783
+ # "AI": {
784
+ # "balance": "0.000000",
785
+ # "balanceOnHold": "0.000000"
786
+ # },
787
+ # ....
788
+ #
789
+ data = self.safe_dict(response, 'data', {})
790
+ balances = self.safe_dict(data, 'balancesPerAccounts', {})
791
+ accountBalance = self.safe_dict(balances, accountName, {})
792
+ else:
793
+ response = await self.privatePostGetMyWalletBalance(params)
794
+ #
795
+ # {
796
+ # "ok": "ok",
797
+ # "data": {
798
+ # "AI": {
799
+ # "balance": "25.606429"
800
+ # },
801
+ # "USDT": {
802
+ # "balance": "7.935449"
803
+ # },
804
+ # ...
805
+ #
806
+ accountBalance = self.safe_dict(response, 'data', {})
807
+ return self.parse_balance(accountBalance)
808
+
809
+ def parse_balance(self, response) -> Balances:
810
+ result: dict = {
811
+ 'info': response,
812
+ }
813
+ keys = list(response.keys())
814
+ for i in range(0, len(keys)):
815
+ key = keys[i]
816
+ balance = self.safe_dict(response, key, {})
817
+ code = self.safe_currency_code(key)
818
+ account: dict = {
819
+ 'used': self.safe_string(balance, 'balanceOnHold'),
820
+ 'free': self.safe_string(balance, 'balance'),
821
+ }
822
+ result[code] = account
823
+ return self.safe_balance(result)
824
+
825
+ async def fetch_orders_by_status(self, status: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
826
+ """
827
+ fetches information on multiple orders made by the user
828
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-orders
829
+ :param str symbol: unified market symbol of the market orders were made in
830
+ :param int [since]: the earliest time in ms to fetch orders for
831
+ :param int [limit]: the maximum number of order structures to retrieve
832
+ :param dict [params]: extra parameters specific to the exchange API endpoint
833
+ :param int [params.until]: timestamp in ms of the latest entry
834
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
835
+ """
836
+ await self.load_markets()
837
+ request: dict = {}
838
+ isClosedOrders = (status == 'closed')
839
+ if isClosedOrders:
840
+ request['archived'] = True
841
+ market = None
842
+ if symbol is not None:
843
+ market = self.market(symbol)
844
+ request['pair'] = market['id']
845
+ if limit is not None:
846
+ request['pageSize'] = limit
847
+ if since is not None:
848
+ request['serverCreateTimestampFrom'] = since
849
+ elif isClosedOrders:
850
+ # exchange requires a `since` parameter for closed orders, so set default to allowed 365
851
+ request['serverCreateTimestampFrom'] = self.milliseconds() - 364 * 24 * 60 * 60 * 1000
852
+ until = None
853
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
854
+ if until is not None:
855
+ request['serverCreateTimestampTo'] = until
856
+ response = await self.privatePostGetMyOrders(self.extend(request, params))
857
+ #
858
+ # if called without `pair`
859
+ #
860
+ # {
861
+ # "ok": "ok",
862
+ # "data": [
863
+ # {
864
+ # "orderId": "1313003",
865
+ # "clientOrderId": "037F0AFEB93A",
866
+ # "clientId": "up421412345",
867
+ # "accountId": null,
868
+ # "status": "FILLED",
869
+ # "statusIsFinal": True,
870
+ # "currency1": "AI",
871
+ # "currency2": "USDT",
872
+ # "side": "BUY",
873
+ # "orderType": "Market",
874
+ # "timeInForce": "IOC",
875
+ # "comment": null,
876
+ # "rejectCode": null,
877
+ # "rejectReason": null,
878
+ # "initialOnHoldAmountCcy1": null,
879
+ # "initialOnHoldAmountCcy2": "10.23456700",
880
+ # "executedAmountCcy1": "25.606429",
881
+ # "executedAmountCcy2": "10.20904439",
882
+ # "requestedAmountCcy1": null,
883
+ # "requestedAmountCcy2": "10.20904439",
884
+ # "originalAmountCcy2": "10.23456700",
885
+ # "feeAmount": "0.02552261",
886
+ # "feeCurrency": "USDT",
887
+ # "price": null,
888
+ # "averagePrice": "0.3986",
889
+ # "clientCreateTimestamp": "1728474625320",
890
+ # "serverCreateTimestamp": "1728474624956",
891
+ # "lastUpdateTimestamp": "1728474628015",
892
+ # "expireTime": null,
893
+ # "effectiveTime": null
894
+ # },
895
+ # ...
896
+ #
897
+ data = self.safe_value(response, 'data', [])
898
+ return self.parse_orders(data, market, since, limit)
899
+
900
+ async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
901
+ """
902
+ fetches information on multiple canceled orders made by the user
903
+ :param str symbol: unified market symbol of the market orders were made in
904
+ :param int [since]: timestamp in ms of the earliest order, default is None
905
+ :param int [limit]: max number of orders to return, default is None
906
+ :param dict [params]: extra parameters specific to the exchange API endpoint
907
+ :returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
908
+ """
909
+ return await self.fetch_orders_by_status('closed', symbol, since, limit, params)
910
+
911
+ async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
912
+ """
913
+ fetches information on multiple canceled orders made by the user
914
+ :param str symbol: unified market symbol of the market orders were made in
915
+ :param int [since]: timestamp in ms of the earliest order, default is None
916
+ :param int [limit]: max number of orders to return, default is None
917
+ :param dict [params]: extra parameters specific to the exchange API endpoint
918
+ :returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
919
+ """
920
+ return await self.fetch_orders_by_status('open', symbol, since, limit, params)
921
+
922
+ def parse_order_status(self, status: Str):
923
+ statuses: dict = {
924
+ 'FILLED': 'closed',
925
+ 'CANCELLED': 'canceled',
926
+ }
927
+ return self.safe_string(statuses, status, None)
928
+
929
+ def parse_order(self, order: dict, market: Market = None) -> Order:
930
+ #
931
+ # "orderId": "1313003",
932
+ # "clientOrderId": "037F0AFEB93A",
933
+ # "clientId": "up421412345",
934
+ # "accountId": null,
935
+ # "status": "FILLED",
936
+ # "statusIsFinal": True,
937
+ # "currency1": "AI",
938
+ # "currency2": "USDT",
939
+ # "side": "BUY",
940
+ # "orderType": "Market",
941
+ # "timeInForce": "IOC",
942
+ # "comment": null,
943
+ # "rejectCode": null,
944
+ # "rejectReason": null,
945
+ # "initialOnHoldAmountCcy1": null,
946
+ # "initialOnHoldAmountCcy2": "10.23456700",
947
+ # "executedAmountCcy1": "25.606429",
948
+ # "executedAmountCcy2": "10.20904439",
949
+ # "requestedAmountCcy1": null,
950
+ # "requestedAmountCcy2": "10.20904439",
951
+ # "originalAmountCcy2": "10.23456700",
952
+ # "feeAmount": "0.02552261",
953
+ # "feeCurrency": "USDT",
954
+ # "price": null,
955
+ # "averagePrice": "0.3986",
956
+ # "clientCreateTimestamp": "1728474625320",
957
+ # "serverCreateTimestamp": "1728474624956",
958
+ # "lastUpdateTimestamp": "1728474628015",
959
+ # "expireTime": null,
960
+ # "effectiveTime": null
961
+ #
962
+ currency1 = self.safe_string(order, 'currency1')
963
+ currency2 = self.safe_string(order, 'currency2')
964
+ marketId = None
965
+ if currency1 is not None and currency2 is not None:
966
+ marketId = currency1 + '-' + currency2
967
+ market = self.safe_market(marketId, market)
968
+ symbol = market['symbol']
969
+ status = self.parse_order_status(self.safe_string(order, 'status'))
970
+ fee = {}
971
+ feeAmount = self.safe_number(order, 'feeAmount')
972
+ if feeAmount is not None:
973
+ currencyId = self.safe_string(order, 'feeCurrency')
974
+ feeCode = self.safe_currency_code(currencyId)
975
+ fee['currency'] = feeCode
976
+ fee['fee'] = feeAmount
977
+ timestamp = self.safe_integer(order, 'serverCreateTimestamp')
978
+ requestedBase = self.safe_number(order, 'requestedAmountCcy1')
979
+ executedBase = self.safe_number(order, 'executedAmountCcy1')
980
+ # requestedQuote = self.safe_number(order, 'requestedAmountCcy2')
981
+ executedQuote = self.safe_number(order, 'executedAmountCcy2')
982
+ return self.safe_order({
983
+ 'id': self.safe_string(order, 'orderId'),
984
+ 'clientOrderId': self.safe_string(order, 'clientOrderId'),
985
+ 'timestamp': timestamp,
986
+ 'datetime': self.iso8601(timestamp),
987
+ 'lastUpdateTimestamp': self.safe_integer(order, 'lastUpdateTimestamp'),
988
+ 'lastTradeTimestamp': None,
989
+ 'symbol': symbol,
990
+ 'type': self.safe_string_lower(order, 'orderType'),
991
+ 'timeInForce': self.safe_string(order, 'timeInForce'),
992
+ 'postOnly': None,
993
+ 'side': self.safe_string_lower(order, 'side'),
994
+ 'price': self.safe_number(order, 'price'),
995
+ 'stopPrice': self.safe_number(order, 'stopPrice'),
996
+ 'amount': requestedBase,
997
+ 'cost': executedQuote,
998
+ 'average': self.safe_number(order, 'averagePrice'),
999
+ 'filled': executedBase,
1000
+ 'remaining': None,
1001
+ 'status': status,
1002
+ 'fee': fee,
1003
+ 'trades': None,
1004
+ 'info': order,
1005
+ }, market)
1006
+
730
1007
  async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
731
1008
  """
732
- :see: https://docs.cex.io/#place-order
733
1009
  create a trade order
734
- :see: https://cex.io/rest-api#place-order
1010
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-new-order
735
1011
  :param str symbol: unified symbol of the market to create an order in
736
1012
  :param str type: 'market' or 'limit'
737
1013
  :param str side: 'buy' or 'sell'
738
1014
  :param float amount: how much of currency you want to trade in units of base currency
739
1015
  :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
740
1016
  :param dict [params]: extra parameters specific to the exchange API endpoint
741
- :param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders
1017
+ :param str [params.accountId]: account-id to use(default is empty string)
742
1018
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
743
1019
  """
1020
+ accountId = None
1021
+ accountId, params = self.handle_option_and_params(params, 'createOrder', 'accountId')
1022
+ if accountId is None:
1023
+ raise ArgumentsRequired(self.id + ' createOrder() : API trading is now allowed from main account, set params["accountId"] or .options["createOrder"]["accountId"] to the name of your sub-account')
744
1024
  await self.load_markets()
745
1025
  market = self.market(symbol)
746
1026
  request: dict = {
747
- 'pair': market['id'],
748
- 'type': side,
1027
+ 'clientOrderId': self.uuid(),
1028
+ 'currency1': market['baseId'],
1029
+ 'currency2': market['quoteId'],
1030
+ 'accountId': accountId,
1031
+ 'orderType': self.capitalize(type.lower()),
1032
+ 'side': side.upper(),
1033
+ 'timestamp': self.milliseconds(),
1034
+ 'amountCcy1': self.amount_to_precision(symbol, amount),
749
1035
  }
750
- # for market buy it requires the amount of quote currency to spend
751
- if (type == 'market') and (side == 'buy'):
752
- quoteAmount = None
753
- createMarketBuyOrderRequiresPrice = True
754
- createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
755
- cost = self.safe_string(params, 'cost')
756
- params = self.omit(params, 'cost')
757
- if cost is not None:
758
- quoteAmount = self.cost_to_precision(symbol, cost)
759
- elif createMarketBuyOrderRequiresPrice:
760
- if price is None:
761
- raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument')
762
- else:
763
- amountString = self.number_to_string(amount)
764
- priceString = self.number_to_string(price)
765
- costRequest = Precise.string_mul(amountString, priceString)
766
- quoteAmount = self.cost_to_precision(symbol, costRequest)
767
- else:
768
- quoteAmount = self.cost_to_precision(symbol, amount)
769
- request['amount'] = quoteAmount
770
- else:
771
- request['amount'] = self.amount_to_precision(symbol, amount)
1036
+ timeInForce = None
1037
+ timeInForce, params = self.handle_option_and_params(params, 'createOrder', 'timeInForce', 'GTC')
772
1038
  if type == 'limit':
773
- request['price'] = self.number_to_string(price)
774
- else:
775
- request['order_type'] = type
776
- response = await self.privatePostPlaceOrderPair(self.extend(request, params))
1039
+ request['price'] = self.price_to_precision(symbol, price)
1040
+ request['timeInForce'] = timeInForce
1041
+ triggerPrice = None
1042
+ triggerPrice, params = self.handle_param_string(params, 'triggerPrice')
1043
+ if triggerPrice is not None:
1044
+ request['type'] = 'Stop Limit'
1045
+ request['stopPrice'] = triggerPrice
1046
+ response = await self.privatePostDoMyNewOrder(self.extend(request, params))
777
1047
  #
778
- # {
779
- # "id": "12978363524",
780
- # "time": 1586610022259,
781
- # "type": "buy",
782
- # "price": "0.033934",
783
- # "amount": "0.10722802",
784
- # "pending": "0.10722802",
785
- # "complete": False
786
- # }
1048
+ # on success
787
1049
  #
788
- placedAmount = self.safe_string(response, 'amount')
789
- remaining = self.safe_string(response, 'pending')
790
- timestamp = self.safe_value(response, 'time')
791
- complete = self.safe_value(response, 'complete')
792
- status = 'closed' if complete else 'open'
793
- filled = None
794
- if (placedAmount is not None) and (remaining is not None):
795
- filled = Precise.string_max(Precise.string_sub(placedAmount, remaining), '0')
796
- return self.safe_order({
797
- 'id': self.safe_string(response, 'id'),
798
- 'info': response,
799
- 'clientOrderId': None,
800
- 'timestamp': timestamp,
801
- 'datetime': self.iso8601(timestamp),
802
- 'lastTradeTimestamp': None,
803
- 'type': type,
804
- 'side': self.safe_string(response, 'type'),
805
- 'symbol': market['symbol'],
806
- 'status': status,
807
- 'price': self.safe_string(response, 'price'),
808
- 'amount': placedAmount,
809
- 'cost': None,
810
- 'average': None,
811
- 'remaining': remaining,
812
- 'filled': filled,
813
- 'fee': None,
814
- 'trades': None,
815
- })
1050
+ # {
1051
+ # "ok": "ok",
1052
+ # "data": {
1053
+ # "messageType": "executionReport",
1054
+ # "clientId": "up132245425",
1055
+ # "orderId": "1318485",
1056
+ # "clientOrderId": "b5b6cd40-154c-4c1c-bd51-4a442f3d50b9",
1057
+ # "accountId": "sub1",
1058
+ # "status": "FILLED",
1059
+ # "currency1": "LTC",
1060
+ # "currency2": "USDT",
1061
+ # "side": "BUY",
1062
+ # "executedAmountCcy1": "0.23000000",
1063
+ # "executedAmountCcy2": "15.09030000",
1064
+ # "requestedAmountCcy1": "0.23000000",
1065
+ # "requestedAmountCcy2": null,
1066
+ # "orderType": "Market",
1067
+ # "timeInForce": null,
1068
+ # "comment": null,
1069
+ # "executionType": "Trade",
1070
+ # "executionId": "1726747124624_101_41116",
1071
+ # "transactTime": "2024-10-15T15:08:12.794Z",
1072
+ # "expireTime": null,
1073
+ # "effectiveTime": null,
1074
+ # "averagePrice": "65.61",
1075
+ # "lastQuantity": "0.23000000",
1076
+ # "lastAmountCcy1": "0.23000000",
1077
+ # "lastAmountCcy2": "15.09030000",
1078
+ # "lastPrice": "65.61",
1079
+ # "feeAmount": "0.03772575",
1080
+ # "feeCurrency": "USDT",
1081
+ # "clientCreateTimestamp": "1729004892014",
1082
+ # "serverCreateTimestamp": "1729004891628",
1083
+ # "lastUpdateTimestamp": "1729004892786"
1084
+ # }
1085
+ # }
1086
+ #
1087
+ # on failure, there are extra fields
1088
+ #
1089
+ # "status": "REJECTED",
1090
+ # "requestedAmountCcy1": null,
1091
+ # "orderRejectReason": "{\\" code \\ ":405,\\" reason \\ ":\\" Either AmountCcy1(OrderQty)or AmountCcy2(CashOrderQty)should be specified for market order not both \\ "}",
1092
+ # "rejectCode": 405,
1093
+ # "rejectReason": "Either AmountCcy1(OrderQty) or AmountCcy2(CashOrderQty) should be specified for market order not both",
1094
+ #
1095
+ data = self.safe_dict(response, 'data')
1096
+ return self.parse_order(data, market)
816
1097
 
817
1098
  async def cancel_order(self, id: str, symbol: Str = None, params={}):
818
1099
  """
819
- :see: https://docs.cex.io/#cancel-order
820
1100
  cancels an open order
1101
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-cancel-order
821
1102
  :param str id: order id
822
- :param str symbol: not used by cex cancelOrder()
1103
+ :param str symbol: unified symbol of the market the order was made in
823
1104
  :param dict [params]: extra parameters specific to the exchange API endpoint
824
1105
  :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
825
1106
  """
826
1107
  await self.load_markets()
827
1108
  request: dict = {
828
- 'id': id,
1109
+ 'orderId': int(id),
1110
+ 'cancelRequestId': 'c_' + str((self.milliseconds())),
1111
+ 'timestamp': self.milliseconds(),
829
1112
  }
830
- response = await self.privatePostCancelOrder(self.extend(request, params))
831
- # 'true'
832
- return self.extend(self.parse_order({}), {'info': response, 'type': None, 'id': id, 'status': 'canceled'})
1113
+ response = await self.privatePostDoCancelMyOrder(self.extend(request, params))
1114
+ #
1115
+ # {"ok":"ok","data":{}}
1116
+ #
1117
+ data = self.safe_dict(response, 'data', {})
1118
+ return self.parse_order(data)
833
1119
 
834
1120
  async def cancel_all_orders(self, symbol: Str = None, params={}):
835
1121
  """
836
- :see: https://docs.cex.io/#cancel-all-orders-for-given-pair
837
1122
  cancel all open orders in a market
838
- :param str symbol: unified market symbol of the market to cancel orders in
839
- :param dict [params]: extra parameters specific to the cex api endpoint
840
- :param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
1123
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-cancel-all-orders
1124
+ :param str symbol: alpaca cancelAllOrders cannot setting symbol, it will cancel all open orders
1125
+ :param dict [params]: extra parameters specific to the exchange API endpoint
841
1126
  :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
842
1127
  """
843
- if symbol is None:
844
- raise ArgumentsRequired(self.id + ' cancelAllOrders requires a symbol.')
845
1128
  await self.load_markets()
846
- market = self.market(symbol)
847
- request: dict = {
848
- 'pair': market['id'],
849
- }
850
- orders = await self.privatePostCancelOrdersPair(self.extend(request, params))
1129
+ response = await self.privatePostDoCancelAllOrders(params)
851
1130
  #
852
- # {
853
- # "e":"cancel_orders",
854
- # "ok":"ok",
855
- # "data":[
856
- # ]
857
- # }
1131
+ # {
1132
+ # "ok": "ok",
1133
+ # "data": {
1134
+ # "clientOrderIds": [
1135
+ # "3AF77B67109F"
1136
+ # ]
1137
+ # }
1138
+ # }
858
1139
  #
859
- return orders
1140
+ data = self.safe_dict(response, 'data', {})
1141
+ ids = self.safe_list(data, 'clientOrderIds', [])
1142
+ orders = []
1143
+ for i in range(0, len(ids)):
1144
+ id = ids[i]
1145
+ orders.append({'clientOrderId': id})
1146
+ return self.parse_orders(orders)
860
1147
 
861
- def parse_order(self, order: dict, market: Market = None) -> Order:
862
- # Depending on the call, 'time' can be a unix int, unix string or ISO string
863
- # Yes, really
864
- timestamp = self.safe_value(order, 'time')
865
- if isinstance(timestamp, str) and timestamp.find('T') >= 0:
866
- # ISO8601 string
867
- timestamp = self.parse8601(timestamp)
868
- elif timestamp is not None:
869
- # either integer or string integer
870
- timestamp = int(timestamp)
871
- symbol = None
872
- baseId = self.safe_string(order, 'symbol1')
873
- quoteId = self.safe_string(order, 'symbol2')
874
- if market is None and baseId is not None and quoteId is not None:
875
- base = self.safe_currency_code(baseId)
876
- quote = self.safe_currency_code(quoteId)
877
- if (base is not None) and (quote is not None):
878
- symbol = base + '/' + quote
879
- if symbol in self.markets:
880
- market = self.market(symbol)
881
- status = self.parse_order_status(self.safe_string(order, 'status'))
882
- price = self.safe_string(order, 'price')
883
- amount = self.omit_zero(self.safe_string(order, 'amount'))
884
- # sell orders can have a negative amount
885
- # https://github.com/ccxt/ccxt/issues/5338
886
- if amount is not None:
887
- amount = Precise.string_abs(amount)
888
- elif market is not None:
889
- amountKey = 'a:' + market['base'] + 'cds:'
890
- amount = Precise.string_abs(self.safe_string(order, amountKey))
891
- remaining = self.safe_string_2(order, 'pending', 'remains')
892
- filled = Precise.string_sub(amount, remaining)
893
- fee = None
894
- cost = None
895
- if market is not None:
896
- symbol = market['symbol']
897
- taCost = self.safe_string(order, 'ta:' + market['quote'])
898
- ttaCost = self.safe_string(order, 'tta:' + market['quote'])
899
- cost = Precise.string_add(taCost, ttaCost)
900
- baseFee = 'fa:' + market['base']
901
- baseTakerFee = 'tfa:' + market['base']
902
- quoteFee = 'fa:' + market['quote']
903
- quoteTakerFee = 'tfa:' + market['quote']
904
- feeRate = self.safe_string(order, 'tradingFeeMaker')
905
- if not feeRate:
906
- feeRate = self.safe_string(order, 'tradingFeeTaker', feeRate)
907
- if feeRate:
908
- feeRate = Precise.string_div(feeRate, '100') # convert to mathematically-correct percentage coefficients: 1.0 = 100%
909
- if (baseFee in order) or (baseTakerFee in order):
910
- baseFeeCost = self.safe_number_2(order, baseFee, baseTakerFee)
911
- fee = {
912
- 'currency': market['base'],
913
- 'rate': self.parse_number(feeRate),
914
- 'cost': baseFeeCost,
915
- }
916
- elif (quoteFee in order) or (quoteTakerFee in order):
917
- quoteFeeCost = self.safe_number_2(order, quoteFee, quoteTakerFee)
918
- fee = {
919
- 'currency': market['quote'],
920
- 'rate': self.parse_number(feeRate),
921
- 'cost': quoteFeeCost,
922
- }
923
- if not cost:
924
- cost = Precise.string_mul(price, filled)
925
- side = self.safe_string(order, 'type')
926
- trades = None
927
- orderId = self.safe_string(order, 'id')
928
- if 'vtx' in order:
929
- trades = []
930
- for i in range(0, len(order['vtx'])):
931
- item = order['vtx'][i]
932
- tradeSide = self.safe_string(item, 'type')
933
- if tradeSide == 'cancel':
934
- # looks like self might represent the cancelled part of an order
935
- # {"id": "4426729543",
936
- # "type": "cancel",
937
- # "time": "2017-09-22T00:24:30.476Z",
938
- # "user": "up106404164",
939
- # "c": "user:up106404164:a:BCH",
940
- # "d": "order:4426728375:a:BCH",
941
- # "a": "0.09935956",
942
- # "amount": "0.09935956",
943
- # "balance": "0.42580261",
944
- # "symbol": "BCH",
945
- # "order": "4426728375",
946
- # "buy": null,
947
- # "sell": null,
948
- # "pair": null,
949
- # "pos": null,
950
- # "cs": "0.42580261",
951
- # "ds": 0}
952
- continue
953
- tradePrice = self.safe_string(item, 'price')
954
- if tradePrice is None:
955
- # self represents the order
956
- # {
957
- # "a": "0.47000000",
958
- # "c": "user:up106404164:a:EUR",
959
- # "d": "order:6065499239:a:EUR",
960
- # "cs": "1432.93",
961
- # "ds": "476.72",
962
- # "id": "6065499249",
963
- # "buy": null,
964
- # "pos": null,
965
- # "pair": null,
966
- # "sell": null,
967
- # "time": "2018-04-22T13:07:22.152Z",
968
- # "type": "buy",
969
- # "user": "up106404164",
970
- # "order": "6065499239",
971
- # "amount": "-715.97000000",
972
- # "symbol": "EUR",
973
- # "balance": "1432.93000000"}
974
- continue
975
- # todo: deal with these
976
- if tradeSide == 'costsNothing':
977
- continue
978
- # --
979
- # if side != tradeSide:
980
- # raise Error(json.dumps(order, null, 2))
981
- # if orderId != item['order']:
982
- # raise Error(json.dumps(order, null, 2))
983
- # --
984
- # partial buy trade
985
- # {
986
- # "a": "0.01589885",
987
- # "c": "user:up106404164:a:BTC",
988
- # "d": "order:6065499239:a:BTC",
989
- # "cs": "0.36300000",
990
- # "ds": 0,
991
- # "id": "6067991213",
992
- # "buy": "6065499239",
993
- # "pos": null,
994
- # "pair": null,
995
- # "sell": "6067991206",
996
- # "time": "2018-04-22T23:09:11.773Z",
997
- # "type": "buy",
998
- # "user": "up106404164",
999
- # "order": "6065499239",
1000
- # "price": 7146.5,
1001
- # "amount": "0.01589885",
1002
- # "symbol": "BTC",
1003
- # "balance": "0.36300000",
1004
- # "symbol2": "EUR",
1005
- # "fee_amount": "0.19"}
1006
- # --
1007
- # trade with zero amount, but non-zero fee
1008
- # {
1009
- # "a": "0.00000000",
1010
- # "c": "user:up106404164:a:EUR",
1011
- # "d": "order:5840654423:a:EUR",
1012
- # "cs": 559744,
1013
- # "ds": 0,
1014
- # "id": "5840654429",
1015
- # "buy": "5807238573",
1016
- # "pos": null,
1017
- # "pair": null,
1018
- # "sell": "5840654423",
1019
- # "time": "2018-03-15T03:20:14.010Z",
1020
- # "type": "sell",
1021
- # "user": "up106404164",
1022
- # "order": "5840654423",
1023
- # "price": 730,
1024
- # "amount": "0.00000000",
1025
- # "symbol": "EUR",
1026
- # "balance": "5597.44000000",
1027
- # "symbol2": "BCH",
1028
- # "fee_amount": "0.01"}
1029
- # --
1030
- # trade which should have an amount of exactly 0.002BTC
1031
- # {
1032
- # "a": "16.70000000",
1033
- # "c": "user:up106404164:a:GBP",
1034
- # "d": "order:9927386681:a:GBP",
1035
- # "cs": "86.90",
1036
- # "ds": 0,
1037
- # "id": "9927401610",
1038
- # "buy": "9927401601",
1039
- # "pos": null,
1040
- # "pair": null,
1041
- # "sell": "9927386681",
1042
- # "time": "2019-08-21T15:25:37.777Z",
1043
- # "type": "sell",
1044
- # "user": "up106404164",
1045
- # "order": "9927386681",
1046
- # "price": 8365,
1047
- # "amount": "16.70000000",
1048
- # "office": "UK",
1049
- # "symbol": "GBP",
1050
- # "balance": "86.90000000",
1051
- # "symbol2": "BTC",
1052
- # "fee_amount": "0.03"
1053
- # }
1054
- tradeTimestamp = self.parse8601(self.safe_string(item, 'time'))
1055
- tradeAmount = self.safe_string(item, 'amount')
1056
- feeCost = self.safe_string(item, 'fee_amount')
1057
- absTradeAmount = Precise.string_abs(tradeAmount)
1058
- tradeCost = None
1059
- if tradeSide == 'sell':
1060
- tradeCost = absTradeAmount
1061
- absTradeAmount = Precise.string_div(Precise.string_add(feeCost, tradeCost), tradePrice)
1062
- else:
1063
- tradeCost = Precise.string_mul(absTradeAmount, tradePrice)
1064
- trades.append({
1065
- 'id': self.safe_string(item, 'id'),
1066
- 'timestamp': tradeTimestamp,
1067
- 'datetime': self.iso8601(tradeTimestamp),
1068
- 'order': orderId,
1069
- 'symbol': symbol,
1070
- 'price': self.parse_number(tradePrice),
1071
- 'amount': self.parse_number(absTradeAmount),
1072
- 'cost': self.parse_number(tradeCost),
1073
- 'side': tradeSide,
1074
- 'fee': {
1075
- 'cost': self.parse_number(feeCost),
1076
- 'currency': market['quote'],
1077
- },
1078
- 'info': item,
1079
- 'type': None,
1080
- 'takerOrMaker': None,
1081
- })
1082
- return self.safe_order({
1083
- 'info': order,
1084
- 'id': orderId,
1085
- 'clientOrderId': None,
1086
- 'datetime': self.iso8601(timestamp),
1087
- 'timestamp': timestamp,
1088
- 'lastTradeTimestamp': None,
1089
- 'status': status,
1090
- 'symbol': symbol,
1091
- 'type': 'market' if (price is None) else 'limit',
1092
- 'timeInForce': None,
1093
- 'postOnly': None,
1094
- 'side': side,
1095
- 'price': price,
1096
- 'stopPrice': None,
1097
- 'triggerPrice': None,
1098
- 'cost': cost,
1099
- 'amount': amount,
1100
- 'filled': filled,
1101
- 'remaining': remaining,
1102
- 'trades': trades,
1103
- 'fee': fee,
1104
- 'average': None,
1105
- })
1106
-
1107
- async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1148
+ async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
1108
1149
  """
1109
- :see: https://docs.cex.io/#open-orders
1110
- fetch all unfilled currently open orders
1111
- :param str symbol: unified market symbol
1112
- :param int [since]: the earliest time in ms to fetch open orders for
1113
- :param int [limit]: the maximum number of open orders structures to retrieve
1150
+ fetch the history of changes, actions done by the user or operations that altered the balance of the user
1151
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-transaction-history
1152
+ :param str [code]: unified currency code
1153
+ :param int [since]: timestamp in ms of the earliest ledger entry
1154
+ :param int [limit]: max number of ledger entries to return
1114
1155
  :param dict [params]: extra parameters specific to the exchange API endpoint
1115
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1156
+ :param int [params.until]: timestamp in ms of the latest ledger entry
1157
+ :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger-structure>`
1116
1158
  """
1117
1159
  await self.load_markets()
1160
+ currency = None
1118
1161
  request: dict = {}
1119
- market = None
1120
- orders = None
1121
- if symbol is not None:
1122
- market = self.market(symbol)
1123
- request['pair'] = market['id']
1124
- orders = await self.privatePostOpenOrdersPair(self.extend(request, params))
1162
+ if code is not None:
1163
+ currency = self.currency(code)
1164
+ request['currency'] = currency['id']
1165
+ if since is not None:
1166
+ request['dateFrom'] = since
1167
+ if limit is not None:
1168
+ request['pageSize'] = limit
1169
+ until = None
1170
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
1171
+ if until is not None:
1172
+ request['dateTo'] = until
1173
+ response = await self.privatePostGetMyTransactionHistory(self.extend(request, params))
1174
+ #
1175
+ # {
1176
+ # "ok": "ok",
1177
+ # "data": [
1178
+ # {
1179
+ # "transactionId": "30367722",
1180
+ # "timestamp": "2024-10-14T14:08:49.987Z",
1181
+ # "accountId": "",
1182
+ # "type": "withdraw",
1183
+ # "amount": "-12.39060600",
1184
+ # "details": "Withdraw fundingId=1235039 clientId=up421412345 walletTxId=76337154166",
1185
+ # "currency": "USDT"
1186
+ # },
1187
+ # ...
1188
+ #
1189
+ data = self.safe_list(response, 'data', [])
1190
+ return self.parse_ledger(data, currency, since, limit)
1191
+
1192
+ def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
1193
+ amount = self.safe_string(item, 'amount')
1194
+ direction = None
1195
+ if Precise.string_le(amount, '0'):
1196
+ direction = 'out'
1197
+ amount = Precise.string_mul('-1', amount)
1125
1198
  else:
1126
- orders = await self.privatePostOpenOrders(self.extend(request, params))
1127
- for i in range(0, len(orders)):
1128
- orders[i] = self.extend(orders[i], {'status': 'open'})
1129
- return self.parse_orders(orders, market, since, limit)
1199
+ direction = 'in'
1200
+ currencyId = self.safe_string(item, 'currency')
1201
+ currency = self.safe_currency(currencyId, currency)
1202
+ code = self.safe_currency_code(currencyId, currency)
1203
+ timestampString = self.safe_string(item, 'timestamp')
1204
+ timestamp = self.parse8601(timestampString)
1205
+ type = self.safe_string(item, 'type')
1206
+ return self.safe_ledger_entry({
1207
+ 'info': item,
1208
+ 'id': self.safe_string(item, 'transactionId'),
1209
+ 'direction': direction,
1210
+ 'account': self.safe_string(item, 'accountId', ''),
1211
+ 'referenceAccount': None,
1212
+ 'referenceId': None,
1213
+ 'type': self.parse_ledger_entry_type(type),
1214
+ 'currency': code,
1215
+ 'amount': self.parse_number(amount),
1216
+ 'timestamp': timestamp,
1217
+ 'datetime': self.iso8601(timestamp),
1218
+ 'before': None,
1219
+ 'after': None,
1220
+ 'status': None,
1221
+ 'fee': None,
1222
+ }, currency)
1130
1223
 
1131
- async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1224
+ def parse_ledger_entry_type(self, type):
1225
+ ledgerType: dict = {
1226
+ 'deposit': 'deposit',
1227
+ 'withdraw': 'withdrawal',
1228
+ 'commission': 'fee',
1229
+ }
1230
+ return self.safe_string(ledgerType, type, type)
1231
+
1232
+ async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1132
1233
  """
1133
- :see: https://docs.cex.io/#archived-orders
1134
- fetches information on multiple closed orders made by the user
1135
- :param str symbol: unified market symbol of the market orders were made in
1136
- :param int [since]: the earliest time in ms to fetch orders for
1137
- :param int [limit]: the maximum number of order structures to retrieve
1234
+ fetch history of deposits and withdrawals
1235
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-funding-history
1236
+ :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
1237
+ :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
1238
+ :param int [limit]: max number of deposit/withdrawals to return, default is None
1138
1239
  :param dict [params]: extra parameters specific to the exchange API endpoint
1139
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1240
+ :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
1140
1241
  """
1141
- if symbol is None:
1142
- raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
1143
1242
  await self.load_markets()
1144
- market = self.market(symbol)
1145
- request: dict = {'pair': market['id']}
1146
- response = await self.privatePostArchivedOrdersPair(self.extend(request, params))
1147
- return self.parse_orders(response, market, since, limit)
1243
+ request: dict = {}
1244
+ currency = None
1245
+ if code is not None:
1246
+ currency = self.currency(code)
1247
+ if since is not None:
1248
+ request['dateFrom'] = since
1249
+ if limit is not None:
1250
+ request['pageSize'] = limit
1251
+ until = None
1252
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
1253
+ if until is not None:
1254
+ request['dateTo'] = until
1255
+ response = await self.privatePostGetMyFundingHistory(self.extend(request, params))
1256
+ #
1257
+ # {
1258
+ # "ok": "ok",
1259
+ # "data": [
1260
+ # {
1261
+ # "clientId": "up421412345",
1262
+ # "accountId": "",
1263
+ # "currency": "USDT",
1264
+ # "direction": "withdraw",
1265
+ # "amount": "12.39060600",
1266
+ # "commissionAmount": "0.00000000",
1267
+ # "status": "approved",
1268
+ # "updatedAt": "2024-10-14T14:08:50.013Z",
1269
+ # "txId": "30367718",
1270
+ # "details": {}
1271
+ # },
1272
+ # ...
1273
+ #
1274
+ data = self.safe_list(response, 'data', [])
1275
+ return self.parse_transactions(data, currency, since, limit)
1276
+
1277
+ def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
1278
+ currencyId = self.safe_string(transaction, 'currency')
1279
+ direction = self.safe_string(transaction, 'direction')
1280
+ type = 'withdrawal' if (direction == 'withdraw') else 'deposit'
1281
+ code = self.safe_currency_code(currencyId, currency)
1282
+ updatedAt = self.safe_string(transaction, 'updatedAt')
1283
+ timestamp = self.parse8601(updatedAt)
1284
+ return {
1285
+ 'info': transaction,
1286
+ 'id': self.safe_string(transaction, 'txId'),
1287
+ 'txid': None,
1288
+ 'type': type,
1289
+ 'currency': code,
1290
+ 'network': None,
1291
+ 'amount': self.safe_number(transaction, 'amount'),
1292
+ 'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
1293
+ 'timestamp': timestamp,
1294
+ 'datetime': self.iso8601(timestamp),
1295
+ 'address': None,
1296
+ 'addressFrom': None,
1297
+ 'addressTo': None,
1298
+ 'tag': None,
1299
+ 'tagFrom': None,
1300
+ 'tagTo': None,
1301
+ 'updated': None,
1302
+ 'comment': None,
1303
+ 'fee': {
1304
+ 'currency': code,
1305
+ 'cost': self.safe_number(transaction, 'commissionAmount'),
1306
+ },
1307
+ 'internal': None,
1308
+ }
1148
1309
 
1149
- async def fetch_order(self, id: str, symbol: Str = None, params={}):
1310
+ def parse_transaction_status(self, status: Str):
1311
+ statuses: dict = {
1312
+ 'rejected': 'rejected',
1313
+ 'pending': 'pending',
1314
+ 'approved': 'ok',
1315
+ }
1316
+ return self.safe_string(statuses, status, status)
1317
+
1318
+ async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1150
1319
  """
1151
- :see: https://docs.cex.io/?python#get-order-details
1152
- fetches information on an order made by the user
1153
- :param str id: the order id
1154
- :param str symbol: not used by cex fetchOrder
1320
+ transfer currency internally between wallets on the same account
1321
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-internal-transfer
1322
+ :param str code: unified currency code
1323
+ :param float amount: amount to transfer
1324
+ :param str fromAccount: 'SPOT', 'FUND', or 'CONTRACT'
1325
+ :param str toAccount: 'SPOT', 'FUND', or 'CONTRACT'
1155
1326
  :param dict [params]: extra parameters specific to the exchange API endpoint
1156
- :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1327
+ :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
1157
1328
  """
1329
+ transfer = None
1330
+ if toAccount != '' and fromAccount != '':
1331
+ transfer = await self.transfer_between_sub_accounts(code, amount, fromAccount, toAccount, params)
1332
+ else:
1333
+ transfer = await self.transfer_between_main_and_sub_account(code, amount, fromAccount, toAccount, params)
1334
+ fillResponseFromRequest = self.handle_option('transfer', 'fillResponseFromRequest', True)
1335
+ if fillResponseFromRequest:
1336
+ transfer['fromAccount'] = fromAccount
1337
+ transfer['toAccount'] = toAccount
1338
+ return transfer
1339
+
1340
+ async def transfer_between_main_and_sub_account(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1158
1341
  await self.load_markets()
1342
+ currency = self.currency(code)
1343
+ fromMain = (fromAccount == '')
1344
+ targetAccount = toAccount if fromMain else fromAccount
1345
+ guid = self.safe_string(params, 'guid', self.uuid())
1159
1346
  request: dict = {
1160
- 'id': str(id),
1347
+ 'currency': currency['id'],
1348
+ 'amount': self.currency_to_precision(code, amount),
1349
+ 'accountId': targetAccount,
1350
+ 'clientTxId': guid,
1161
1351
  }
1162
- response = await self.privatePostGetOrderTx(self.extend(request, params))
1163
- data = self.safe_value(response, 'data', {})
1352
+ response = None
1353
+ if fromMain:
1354
+ response = await self.privatePostDoDepositFundsFromWallet(self.extend(request, params))
1355
+ else:
1356
+ response = await self.privatePostDoWithdrawalFundsToWallet(self.extend(request, params))
1357
+ # both endpoints return the same structure, the only difference is that
1358
+ # the "accountId" is filled with the "subAccount"
1164
1359
  #
1165
1360
  # {
1166
- # "id": "5442731603",
1167
- # "type": "sell",
1168
- # "time": 1516132358071,
1169
- # "lastTxTime": 1516132378452,
1170
- # "lastTx": "5442734452",
1171
- # "pos": null,
1172
- # "user": "up106404164",
1173
- # "status": "d",
1174
- # "symbol1": "ETH",
1175
- # "symbol2": "EUR",
1176
- # "amount": "0.50000000",
1177
- # "kind": "api",
1178
- # "price": "923.3386",
1179
- # "tfacf": "1",
1180
- # "fa:EUR": "0.55",
1181
- # "ta:EUR": "369.77",
1182
- # "remains": "0.00000000",
1183
- # "tfa:EUR": "0.22",
1184
- # "tta:EUR": "91.95",
1185
- # "a:ETH:cds": "0.50000000",
1186
- # "a:EUR:cds": "461.72",
1187
- # "f:EUR:cds": "0.77",
1188
- # "tradingFeeMaker": "0.15",
1189
- # "tradingFeeTaker": "0.23",
1190
- # "tradingFeeStrategy": "userVolumeAmount",
1191
- # "tradingFeeUserVolumeAmount": "2896912572",
1192
- # "orderId": "5442731603",
1193
- # "next": False,
1194
- # "vtx": [
1195
- # {
1196
- # "id": "5442734452",
1197
- # "type": "sell",
1198
- # "time": "2018-01-16T19:52:58.452Z",
1199
- # "user": "up106404164",
1200
- # "c": "user:up106404164:a:EUR",
1201
- # "d": "order:5442731603:a:EUR",
1202
- # "a": "104.53000000",
1203
- # "amount": "104.53000000",
1204
- # "balance": "932.71000000",
1205
- # "symbol": "EUR",
1206
- # "order": "5442731603",
1207
- # "buy": "5442734443",
1208
- # "sell": "5442731603",
1209
- # "pair": null,
1210
- # "pos": null,
1211
- # "office": null,
1212
- # "cs": "932.71",
1213
- # "ds": 0,
1214
- # "price": 923.3386,
1215
- # "symbol2": "ETH",
1216
- # "fee_amount": "0.16"
1217
- # },
1218
- # {
1219
- # "id": "5442731609",
1220
- # "type": "sell",
1221
- # "time": "2018-01-16T19:52:38.071Z",
1222
- # "user": "up106404164",
1223
- # "c": "user:up106404164:a:EUR",
1224
- # "d": "order:5442731603:a:EUR",
1225
- # "a": "91.73000000",
1226
- # "amount": "91.73000000",
1227
- # "balance": "563.49000000",
1228
- # "symbol": "EUR",
1229
- # "order": "5442731603",
1230
- # "buy": "5442618127",
1231
- # "sell": "5442731603",
1232
- # "pair": null,
1233
- # "pos": null,
1234
- # "office": null,
1235
- # "cs": "563.49",
1236
- # "ds": 0,
1237
- # "price": 924.0092,
1238
- # "symbol2": "ETH",
1239
- # "fee_amount": "0.22"
1240
- # },
1241
- # {
1242
- # "id": "5442731604",
1243
- # "type": "sell",
1244
- # "time": "2018-01-16T19:52:38.071Z",
1245
- # "user": "up106404164",
1246
- # "c": "order:5442731603:a:ETH",
1247
- # "d": "user:up106404164:a:ETH",
1248
- # "a": "0.50000000",
1249
- # "amount": "-0.50000000",
1250
- # "balance": "15.80995000",
1251
- # "symbol": "ETH",
1252
- # "order": "5442731603",
1253
- # "buy": null,
1254
- # "sell": null,
1255
- # "pair": null,
1256
- # "pos": null,
1257
- # "office": null,
1258
- # "cs": "0.50000000",
1259
- # "ds": "15.80995000"
1260
- # }
1261
- # ]
1361
+ # "ok": "ok",
1362
+ # "data": {
1363
+ # "accountId": "sub1",
1364
+ # "clientTxId": "27ba8284-67cf-4386-9ec7-80b3871abd45",
1365
+ # "currency": "USDT",
1366
+ # "status": "approved"
1367
+ # }
1262
1368
  # }
1263
1369
  #
1264
- return self.parse_order(data)
1370
+ data = self.safe_dict(response, 'data', {})
1371
+ return self.parse_transfer(data, currency)
1265
1372
 
1266
- async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1267
- """
1268
- :see: https://docs.cex.io/#archived-orders
1269
- fetches information on multiple orders made by the user
1270
- :param str symbol: unified market symbol of the market orders were made in
1271
- :param int [since]: the earliest time in ms to fetch orders for
1272
- :param int [limit]: the maximum number of order structures to retrieve
1273
- :param dict [params]: extra parameters specific to the exchange API endpoint
1274
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1275
- """
1373
+ async def transfer_between_sub_accounts(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1276
1374
  await self.load_markets()
1277
- market = self.market(symbol)
1375
+ currency = self.currency(code)
1278
1376
  request: dict = {
1279
- 'limit': limit,
1280
- 'pair': market['id'],
1281
- 'dateFrom': since,
1377
+ 'currency': currency['id'],
1378
+ 'amount': self.currency_to_precision(code, amount),
1379
+ 'fromAccountId': fromAccount,
1380
+ 'toAccountId': toAccount,
1282
1381
  }
1283
- response = await self.privatePostArchivedOrdersPair(self.extend(request, params))
1284
- results = []
1285
- for i in range(0, len(response)):
1286
- # cancelled(unfilled):
1287
- # {"id": "4005785516",
1288
- # "type": "sell",
1289
- # "time": "2017-07-18T19:08:34.223Z",
1290
- # "lastTxTime": "2017-07-18T19:08:34.396Z",
1291
- # "lastTx": "4005785522",
1292
- # "pos": null,
1293
- # "status": "c",
1294
- # "symbol1": "ETH",
1295
- # "symbol2": "GBP",
1296
- # "amount": "0.20000000",
1297
- # "price": "200.5625",
1298
- # "remains": "0.20000000",
1299
- # 'a:ETH:cds': "0.20000000",
1300
- # "tradingFeeMaker": "0",
1301
- # "tradingFeeTaker": "0.16",
1302
- # "tradingFeeUserVolumeAmount": "10155061217",
1303
- # "orderId": "4005785516"}
1304
- # --
1305
- # cancelled(partially filled buy):
1306
- # {"id": "4084911657",
1307
- # "type": "buy",
1308
- # "time": "2017-08-05T03:18:39.596Z",
1309
- # "lastTxTime": "2019-03-19T17:37:46.404Z",
1310
- # "lastTx": "8459265833",
1311
- # "pos": null,
1312
- # "status": "cd",
1313
- # "symbol1": "BTC",
1314
- # "symbol2": "GBP",
1315
- # "amount": "0.05000000",
1316
- # "price": "2241.4692",
1317
- # "tfacf": "1",
1318
- # "remains": "0.03910535",
1319
- # 'tfa:GBP': "0.04",
1320
- # 'tta:GBP': "24.39",
1321
- # 'a:BTC:cds': "0.01089465",
1322
- # 'a:GBP:cds': "112.26",
1323
- # 'f:GBP:cds': "0.04",
1324
- # "tradingFeeMaker": "0",
1325
- # "tradingFeeTaker": "0.16",
1326
- # "tradingFeeUserVolumeAmount": "13336396963",
1327
- # "orderId": "4084911657"}
1328
- # --
1329
- # cancelled(partially filled sell):
1330
- # {"id": "4426728375",
1331
- # "type": "sell",
1332
- # "time": "2017-09-22T00:24:20.126Z",
1333
- # "lastTxTime": "2017-09-22T00:24:30.476Z",
1334
- # "lastTx": "4426729543",
1335
- # "pos": null,
1336
- # "status": "cd",
1337
- # "symbol1": "BCH",
1338
- # "symbol2": "BTC",
1339
- # "amount": "0.10000000",
1340
- # "price": "0.11757182",
1341
- # "tfacf": "1",
1342
- # "remains": "0.09935956",
1343
- # 'tfa:BTC': "0.00000014",
1344
- # 'tta:BTC': "0.00007537",
1345
- # 'a:BCH:cds': "0.10000000",
1346
- # 'a:BTC:cds': "0.00007537",
1347
- # 'f:BTC:cds': "0.00000014",
1348
- # "tradingFeeMaker": "0",
1349
- # "tradingFeeTaker": "0.18",
1350
- # "tradingFeeUserVolumeAmount": "3466715450",
1351
- # "orderId": "4426728375"}
1352
- # --
1353
- # filled:
1354
- # {"id": "5342275378",
1355
- # "type": "sell",
1356
- # "time": "2018-01-04T00:28:12.992Z",
1357
- # "lastTxTime": "2018-01-04T00:28:12.992Z",
1358
- # "lastTx": "5342275393",
1359
- # "pos": null,
1360
- # "status": "d",
1361
- # "symbol1": "BCH",
1362
- # "symbol2": "BTC",
1363
- # "amount": "0.10000000",
1364
- # "kind": "api",
1365
- # "price": "0.17",
1366
- # "remains": "0.00000000",
1367
- # 'tfa:BTC': "0.00003902",
1368
- # 'tta:BTC': "0.01699999",
1369
- # 'a:BCH:cds': "0.10000000",
1370
- # 'a:BTC:cds': "0.01699999",
1371
- # 'f:BTC:cds': "0.00003902",
1372
- # "tradingFeeMaker": "0.15",
1373
- # "tradingFeeTaker": "0.23",
1374
- # "tradingFeeUserVolumeAmount": "1525951128",
1375
- # "orderId": "5342275378"}
1376
- # --
1377
- # market order(buy):
1378
- # {"id": "6281946200",
1379
- # "pos": null,
1380
- # "time": "2018-05-23T11:55:43.467Z",
1381
- # "type": "buy",
1382
- # "amount": "0.00000000",
1383
- # "lastTx": "6281946210",
1384
- # "status": "d",
1385
- # "amount2": "20.00",
1386
- # "orderId": "6281946200",
1387
- # "remains": "0.00000000",
1388
- # "symbol1": "ETH",
1389
- # "symbol2": "EUR",
1390
- # "tfa:EUR": "0.05",
1391
- # "tta:EUR": "19.94",
1392
- # "a:ETH:cds": "0.03764100",
1393
- # "a:EUR:cds": "20.00",
1394
- # "f:EUR:cds": "0.05",
1395
- # "lastTxTime": "2018-05-23T11:55:43.467Z",
1396
- # "tradingFeeTaker": "0.25",
1397
- # "tradingFeeUserVolumeAmount": "55998097"}
1398
- # --
1399
- # market order(sell):
1400
- # {"id": "6282200948",
1401
- # "pos": null,
1402
- # "time": "2018-05-23T12:42:58.315Z",
1403
- # "type": "sell",
1404
- # "amount": "-0.05000000",
1405
- # "lastTx": "6282200958",
1406
- # "status": "d",
1407
- # "orderId": "6282200948",
1408
- # "remains": "0.00000000",
1409
- # "symbol1": "ETH",
1410
- # "symbol2": "EUR",
1411
- # "tfa:EUR": "0.07",
1412
- # "tta:EUR": "26.49",
1413
- # "a:ETH:cds": "0.05000000",
1414
- # "a:EUR:cds": "26.49",
1415
- # "f:EUR:cds": "0.07",
1416
- # "lastTxTime": "2018-05-23T12:42:58.315Z",
1417
- # "tradingFeeTaker": "0.25",
1418
- # "tradingFeeUserVolumeAmount": "56294576"}
1419
- order = response[i]
1420
- status = self.parse_order_status(self.safe_string(order, 'status'))
1421
- baseId = self.safe_string(order, 'symbol1')
1422
- quoteId = self.safe_string(order, 'symbol2')
1423
- base = self.safe_currency_code(baseId)
1424
- quote = self.safe_currency_code(quoteId)
1425
- symbolInner = base + '/' + quote
1426
- side = self.safe_string(order, 'type')
1427
- baseAmount = self.safe_number(order, 'a:' + baseId + ':cds')
1428
- quoteAmount = self.safe_number(order, 'a:' + quoteId + ':cds')
1429
- fee = self.safe_number(order, 'f:' + quoteId + ':cds')
1430
- amount = self.safe_string(order, 'amount')
1431
- price = self.safe_string(order, 'price')
1432
- remaining = self.safe_string(order, 'remains')
1433
- filled = Precise.string_sub(amount, remaining)
1434
- orderAmount = None
1435
- cost = None
1436
- average = None
1437
- type = None
1438
- if not price:
1439
- type = 'market'
1440
- orderAmount = baseAmount
1441
- cost = quoteAmount
1442
- average = Precise.string_div(orderAmount, cost)
1443
- else:
1444
- ta = self.safe_string(order, 'ta:' + quoteId, '0')
1445
- tta = self.safe_string(order, 'tta:' + quoteId, '0')
1446
- fa = self.safe_string(order, 'fa:' + quoteId, '0')
1447
- tfa = self.safe_string(order, 'tfa:' + quoteId, '0')
1448
- if side == 'sell':
1449
- cost = Precise.string_add(Precise.string_add(ta, tta), Precise.string_add(fa, tfa))
1450
- else:
1451
- cost = Precise.string_sub(Precise.string_add(ta, tta), Precise.string_add(fa, tfa))
1452
- type = 'limit'
1453
- orderAmount = amount
1454
- average = Precise.string_div(cost, filled)
1455
- time = self.safe_string(order, 'time')
1456
- lastTxTime = self.safe_string(order, 'lastTxTime')
1457
- timestamp = self.parse8601(time)
1458
- safeOrder = self.safe_order({
1459
- 'info': order,
1460
- 'id': self.safe_string(order, 'id'),
1461
- 'timestamp': timestamp,
1462
- 'datetime': self.iso8601(timestamp),
1463
- 'lastUpdated': self.parse8601(lastTxTime),
1464
- 'status': status,
1465
- 'symbol': symbolInner,
1466
- 'side': side,
1467
- 'price': price,
1468
- 'amount': orderAmount,
1469
- 'average': average,
1470
- 'type': type,
1471
- 'filled': filled,
1472
- 'cost': cost,
1473
- 'remaining': remaining,
1474
- 'fee': {
1475
- 'cost': fee,
1476
- 'currency': quote,
1477
- },
1478
- })
1479
- results.append(safeOrder)
1480
- return results
1481
-
1482
- def parse_order_status(self, status: Str):
1483
- return self.safe_string(self.options['order']['status'], status, status)
1382
+ response = await self.privatePostDoMyInternalTransfer(self.extend(request, params))
1383
+ #
1384
+ # {
1385
+ # "ok": "ok",
1386
+ # "data": {
1387
+ # "transactionId": "30225415"
1388
+ # }
1389
+ # }
1390
+ #
1391
+ data = self.safe_dict(response, 'data', {})
1392
+ return self.parse_transfer(data, currency)
1484
1393
 
1485
- async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
1486
- """
1487
- edit a trade order
1488
- :see: https://docs.cex.io/#cancel-replace-order
1489
- :param str id: order id
1490
- :param str symbol: unified symbol of the market to create an order in
1491
- :param str type: 'market' or 'limit'
1492
- :param str side: 'buy' or 'sell'
1493
- :param float amount: how much of the currency you want to trade in units of the base currency
1494
- :param float|None [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
1495
- :param dict [params]: extra parameters specific to the cex api endpoint
1496
- :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
1497
- """
1498
- if amount is None:
1499
- raise ArgumentsRequired(self.id + ' editOrder() requires a amount argument')
1500
- if price is None:
1501
- raise ArgumentsRequired(self.id + ' editOrder() requires a price argument')
1502
- await self.load_markets()
1503
- market = self.market(symbol)
1504
- # see: https://cex.io/rest-api#/definitions/CancelReplaceOrderRequest
1505
- request: dict = {
1506
- 'pair': market['id'],
1507
- 'type': side,
1508
- 'amount': amount,
1509
- 'price': price,
1510
- 'order_id': id,
1394
+ def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
1395
+ #
1396
+ # transferBetweenSubAccounts
1397
+ #
1398
+ # {
1399
+ # "ok": "ok",
1400
+ # "data": {
1401
+ # "transactionId": "30225415"
1402
+ # }
1403
+ # }
1404
+ #
1405
+ # transfer between main/sub
1406
+ #
1407
+ # {
1408
+ # "ok": "ok",
1409
+ # "data": {
1410
+ # "accountId": "sub1",
1411
+ # "clientTxId": "27ba8284-67cf-4386-9ec7-80b3871abd45",
1412
+ # "currency": "USDT",
1413
+ # "status": "approved"
1414
+ # }
1415
+ # }
1416
+ #
1417
+ currencyId = self.safe_string(transfer, 'currency')
1418
+ currencyCode = self.safe_currency_code(currencyId, currency)
1419
+ return {
1420
+ 'info': transfer,
1421
+ 'id': self.safe_string_2(transfer, 'transactionId', 'clientTxId'),
1422
+ 'timestamp': None,
1423
+ 'datetime': None,
1424
+ 'currency': currencyCode,
1425
+ 'amount': None,
1426
+ 'fromAccount': None,
1427
+ 'toAccount': None,
1428
+ 'status': self.parse_transaction_status(self.safe_string(transfer, 'status')),
1511
1429
  }
1512
- response = await self.privatePostCancelReplaceOrderPair(self.extend(request, params))
1513
- return self.parse_order(response, market)
1514
1430
 
1515
1431
  async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
1516
1432
  """
1517
- :see: https://docs.cex.io/#get-crypto-address
1518
1433
  fetch the deposit address for a currency associated with self account
1434
+ :see: https://trade.cex.io/docs/#rest-private-api-calls-deposit-address
1519
1435
  :param str code: unified currency code
1520
1436
  :param dict [params]: extra parameters specific to the exchange API endpoint
1437
+ :param str [params.accountId]: account-id(default to empty string) to refer to(at self moment, only sub-accounts allowed by exchange)
1521
1438
  :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
1522
1439
  """
1440
+ accountId = None
1441
+ accountId, params = self.handle_option_and_params(params, 'createOrder', 'accountId')
1442
+ if accountId is None:
1443
+ raise ArgumentsRequired(self.id + ' fetchDepositAddress() : main account is not allowed to fetch deposit address from api, set params["accountId"] or .options["createOrder"]["accountId"] to the name of your sub-account')
1523
1444
  await self.load_markets()
1445
+ networkCode = None
1446
+ networkCode, params = self.handle_network_code_and_params(params)
1524
1447
  currency = self.currency(code)
1525
1448
  request: dict = {
1526
- 'currency': currency['id'],
1449
+ 'accountId': accountId,
1450
+ 'currency': currency['id'], # documentation is wrong about self param
1451
+ 'blockchain': self.network_code_to_id(networkCode),
1527
1452
  }
1528
- networkCode, query = self.handle_network_code_and_params(params)
1529
- # atm, cex doesn't support network in the request
1530
- response = await self.privatePostGetCryptoAddress(self.extend(request, query))
1453
+ response = await self.privatePostGetDepositAddress(self.extend(request, params))
1531
1454
  #
1532
1455
  # {
1533
- # "e": "get_crypto_address",
1534
- # "ok": "ok",
1535
- # "data": {
1536
- # "name": "BTC",
1537
- # "addresses": [
1538
- # {
1539
- # "blockchain": "Bitcoin",
1540
- # "address": "2BvKwe1UwrdTjq2nzhscFYXwqCjCaaHCeq"
1541
- #
1542
- # # for others coins(i.e. XRP, XLM) other keys are present:
1543
- # # "destination": "rF1sdh25BJX3qFwneeTBwaq3zPEWYcwjp2",
1544
- # # "destinationTag": "7519113655",
1545
- # # "memo": "XLM-memo12345",
1546
- # }
1547
- # ]
1548
- # }
1549
- # }
1456
+ # "ok": "ok",
1457
+ # "data": {
1458
+ # "address": "TCr..................1AE",
1459
+ # "accountId": "sub1",
1460
+ # "currency": "USDT",
1461
+ # "blockchain": "tron"
1462
+ # }
1463
+ # }
1550
1464
  #
1551
- data = self.safe_value(response, 'data', {})
1552
- addresses = self.safe_value(data, 'addresses', [])
1553
- chainsIndexedById = self.index_by(addresses, 'blockchain')
1554
- selectedNetworkId = self.select_network_id_from_raw_networks(code, networkCode, chainsIndexedById)
1555
- addressObject = self.safe_value(chainsIndexedById, selectedNetworkId, {})
1556
- address = self.safe_string_2(addressObject, 'address', 'destination')
1465
+ data = self.safe_dict(response, 'data', {})
1466
+ return self.parse_deposit_address(data, currency)
1467
+
1468
+ def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
1469
+ address = self.safe_string(depositAddress, 'address')
1470
+ currencyId = self.safe_string(depositAddress, 'currency')
1471
+ currency = self.safe_currency(currencyId, currency)
1557
1472
  self.check_address(address)
1558
1473
  return {
1559
- 'info': data,
1560
- 'currency': code,
1561
- 'network': self.network_id_to_code(selectedNetworkId),
1474
+ 'info': depositAddress,
1475
+ 'currency': currency['code'],
1476
+ 'network': self.network_id_to_code(self.safe_string(depositAddress, 'blockchain')),
1562
1477
  'address': address,
1563
- 'tag': self.safe_string_2(addressObject, 'destinationTag', 'memo'),
1478
+ 'tag': None,
1564
1479
  }
1565
1480
 
1566
- def nonce(self):
1567
- return self.milliseconds()
1568
-
1569
1481
  def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
1570
- url = self.urls['api']['rest'] + '/' + self.implode_params(path, params)
1482
+ url = self.urls['api'][api] + '/' + self.implode_params(path, params)
1571
1483
  query = self.omit(params, self.extract_params(path))
1572
1484
  if api == 'public':
1573
- if query:
1574
- url += '?' + self.urlencode(query)
1485
+ if method == 'GET':
1486
+ if query:
1487
+ url += '?' + self.urlencode(query)
1488
+ else:
1489
+ body = self.json(query)
1490
+ headers = {
1491
+ 'Content-Type': 'application/json',
1492
+ }
1575
1493
  else:
1576
1494
  self.check_required_credentials()
1577
- nonce = str(self.nonce())
1578
- auth = nonce + self.uid + self.apiKey
1579
- signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
1580
- body = self.json(self.extend({
1581
- 'key': self.apiKey,
1582
- 'signature': signature.upper(),
1583
- 'nonce': nonce,
1584
- }, query))
1495
+ seconds = str(self.seconds())
1496
+ body = self.json(query)
1497
+ auth = path + seconds + body
1498
+ signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
1585
1499
  headers = {
1586
1500
  'Content-Type': 'application/json',
1501
+ 'X-AGGR-KEY': self.apiKey,
1502
+ 'X-AGGR-TIMESTAMP': seconds,
1503
+ 'X-AGGR-SIGNATURE': signature,
1587
1504
  }
1588
1505
  return {'url': url, 'method': method, 'body': body, 'headers': headers}
1589
1506
 
1590
1507
  def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
1591
- if isinstance(response, list):
1592
- return response # public endpoints may return []-arrays
1593
- if body == 'true':
1594
- return None
1508
+ # in some cases, like from createOrder, exchange returns nested escaped JSON string:
1509
+ # {"ok":"ok","data":{"messageType":"executionReport", "orderRejectReason":"{\"code\":405}"}}
1510
+ # and because of `.parseJson` bug, we need extra fix
1595
1511
  if response is None:
1596
- raise NullResponse(self.id + ' returned ' + self.json(response))
1597
- if 'e' in response:
1598
- if 'ok' in response:
1599
- if response['ok'] == 'ok':
1600
- return None
1601
- if 'error' in response:
1602
- message = self.safe_string(response, 'error')
1512
+ if body is None:
1513
+ raise NullResponse(self.id + ' returned empty response')
1514
+ elif body[0] == '{':
1515
+ fixed = self.fix_stringified_json_members(body)
1516
+ response = self.parse_json(fixed)
1517
+ else:
1518
+ raise NullResponse(self.id + ' returned unparsed response: ' + body)
1519
+ error = self.safe_string(response, 'error')
1520
+ if error is not None:
1603
1521
  feedback = self.id + ' ' + body
1604
- self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
1605
- self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
1522
+ self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
1523
+ self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
1606
1524
  raise ExchangeError(feedback)
1525
+ # check errors in order-engine(the responses are not standard, so we parse here)
1526
+ if url.find('do_my_new_order') >= 0:
1527
+ data = self.safe_dict(response, 'data', {})
1528
+ rejectReason = self.safe_string(data, 'rejectReason')
1529
+ if rejectReason is not None:
1530
+ self.throw_broadly_matched_exception(self.exceptions['broad'], rejectReason, rejectReason)
1531
+ raise ExchangeError(self.id + ' createOrder() ' + rejectReason)
1607
1532
  return None