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