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