ccxt 4.4.21__py2.py3-none-any.whl → 4.4.23__py2.py3-none-any.whl

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