ccxt 4.1.54__py2.py3-none-any.whl → 4.1.56__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.
Potentially problematic release.
This version of ccxt might be problematic. Click here for more details.
- ccxt/__init__.py +2 -2
- ccxt/abstract/binance.py +1 -0
- ccxt/abstract/binancecoinm.py +1 -0
- ccxt/abstract/binanceus.py +1 -0
- ccxt/abstract/binanceusdm.py +1 -0
- ccxt/abstract/bitbank.py +1 -0
- ccxt/abstract/coinbase.py +2 -0
- ccxt/abstract/htx.py +3 -0
- ccxt/abstract/huobi.py +3 -0
- ccxt/abstract/huobipro.py +3 -0
- ccxt/abstract/okex.py +3 -1
- ccxt/abstract/okex5.py +3 -1
- ccxt/abstract/okx.py +3 -1
- ccxt/ace.py +23 -23
- ccxt/alpaca.py +8 -8
- ccxt/ascendex.py +26 -26
- ccxt/async_support/__init__.py +2 -2
- ccxt/async_support/ace.py +23 -23
- ccxt/async_support/alpaca.py +8 -8
- ccxt/async_support/ascendex.py +26 -26
- ccxt/async_support/base/exchange.py +4 -2216
- ccxt/async_support/bigone.py +21 -24
- ccxt/async_support/binance.py +61 -54
- ccxt/async_support/bingx.py +28 -28
- ccxt/async_support/bit2c.py +9 -9
- ccxt/async_support/bitbank.py +11 -10
- ccxt/async_support/bitbns.py +11 -11
- ccxt/async_support/bitfinex.py +15 -15
- ccxt/async_support/bitfinex2.py +22 -22
- ccxt/async_support/bitflyer.py +13 -13
- ccxt/async_support/bitforex.py +10 -10
- ccxt/async_support/bitget.py +44 -44
- ccxt/async_support/bithumb.py +9 -9
- ccxt/async_support/bitmart.py +85 -104
- ccxt/async_support/bitmex.py +27 -27
- ccxt/async_support/bitopro.py +18 -18
- ccxt/async_support/bitpanda.py +18 -18
- ccxt/async_support/bitrue.py +14 -14
- ccxt/async_support/bitso.py +17 -17
- ccxt/async_support/bitstamp.py +17 -17
- ccxt/async_support/bittrex.py +22 -24
- ccxt/async_support/bitvavo.py +15 -15
- ccxt/async_support/bl3p.py +4 -4
- ccxt/async_support/blockchaincom.py +17 -17
- ccxt/async_support/btcalpha.py +14 -14
- ccxt/async_support/btcbox.py +9 -9
- ccxt/async_support/btcmarkets.py +17 -17
- ccxt/async_support/btcturk.py +9 -9
- ccxt/async_support/bybit.py +46 -46
- ccxt/async_support/cex.py +10 -10
- ccxt/async_support/coinbase.py +69 -25
- ccxt/async_support/coinbasepro.py +19 -19
- ccxt/async_support/coincheck.py +10 -10
- ccxt/async_support/coinex.py +57 -66
- ccxt/async_support/coinlist.py +22 -22
- ccxt/async_support/coinmate.py +10 -10
- ccxt/async_support/coinone.py +10 -10
- ccxt/async_support/coinsph.py +17 -17
- ccxt/async_support/coinspot.py +5 -5
- ccxt/async_support/cryptocom.py +27 -27
- ccxt/async_support/currencycom.py +18 -18
- ccxt/async_support/delta.py +21 -21
- ccxt/async_support/deribit.py +24 -24
- ccxt/async_support/digifinex.py +35 -35
- ccxt/async_support/exmo.py +19 -19
- ccxt/async_support/gate.py +38 -38
- ccxt/async_support/gemini.py +11 -11
- ccxt/async_support/hitbtc.py +27 -27
- ccxt/async_support/hollaex.py +19 -19
- ccxt/async_support/htx.py +47 -44
- ccxt/async_support/huobijp.py +22 -22
- ccxt/async_support/idex.py +20 -20
- ccxt/async_support/independentreserve.py +9 -9
- ccxt/async_support/indodax.py +10 -10
- ccxt/async_support/kraken.py +25 -25
- ccxt/async_support/krakenfutures.py +17 -17
- ccxt/async_support/kucoin.py +27 -27
- ccxt/async_support/kucoinfutures.py +20 -20
- ccxt/async_support/kuna.py +19 -19
- ccxt/async_support/latoken.py +14 -14
- ccxt/async_support/lbank.py +18 -18
- ccxt/async_support/luno.py +14 -14
- ccxt/async_support/lykke.py +12 -12
- ccxt/async_support/mercado.py +11 -11
- ccxt/async_support/mexc.py +36 -36
- ccxt/async_support/ndax.py +18 -18
- ccxt/async_support/novadax.py +17 -17
- ccxt/async_support/oceanex.py +12 -12
- ccxt/async_support/okcoin.py +19 -19
- ccxt/async_support/okx.py +48 -45
- ccxt/async_support/p2b.py +6 -6
- ccxt/async_support/paymium.py +6 -6
- ccxt/async_support/phemex.py +57 -57
- ccxt/async_support/poloniex.py +31 -30
- ccxt/async_support/poloniexfutures.py +16 -16
- ccxt/async_support/probit.py +22 -22
- ccxt/async_support/tidex.py +15 -15
- ccxt/async_support/timex.py +20 -20
- ccxt/async_support/tokocrypto.py +16 -16
- ccxt/async_support/upbit.py +15 -15
- ccxt/async_support/wavesexchange.py +12 -12
- ccxt/async_support/wazirx.py +13 -13
- ccxt/async_support/whitebit.py +26 -26
- ccxt/async_support/woo.py +47 -47
- ccxt/async_support/yobit.py +8 -8
- ccxt/async_support/zaif.py +10 -10
- ccxt/async_support/zonda.py +16 -16
- ccxt/base/errors.py +17 -16
- ccxt/base/exchange.py +57 -97
- ccxt/base/types.py +138 -139
- ccxt/bigone.py +21 -24
- ccxt/binance.py +61 -54
- ccxt/bingx.py +28 -28
- ccxt/bit2c.py +9 -9
- ccxt/bitbank.py +11 -10
- ccxt/bitbns.py +11 -11
- ccxt/bitfinex.py +15 -15
- ccxt/bitfinex2.py +22 -22
- ccxt/bitflyer.py +13 -13
- ccxt/bitforex.py +10 -10
- ccxt/bitget.py +44 -44
- ccxt/bithumb.py +9 -9
- ccxt/bitmart.py +85 -104
- ccxt/bitmex.py +27 -27
- ccxt/bitopro.py +18 -18
- ccxt/bitpanda.py +18 -18
- ccxt/bitrue.py +14 -14
- ccxt/bitso.py +17 -17
- ccxt/bitstamp.py +17 -17
- ccxt/bittrex.py +22 -24
- ccxt/bitvavo.py +15 -15
- ccxt/bl3p.py +4 -4
- ccxt/blockchaincom.py +17 -17
- ccxt/btcalpha.py +14 -14
- ccxt/btcbox.py +9 -9
- ccxt/btcmarkets.py +17 -17
- ccxt/btcturk.py +9 -9
- ccxt/bybit.py +46 -46
- ccxt/cex.py +10 -10
- ccxt/coinbase.py +69 -25
- ccxt/coinbasepro.py +19 -19
- ccxt/coincheck.py +10 -10
- ccxt/coinex.py +57 -66
- ccxt/coinlist.py +22 -22
- ccxt/coinmate.py +10 -10
- ccxt/coinone.py +10 -10
- ccxt/coinsph.py +17 -17
- ccxt/coinspot.py +5 -5
- ccxt/cryptocom.py +27 -27
- ccxt/currencycom.py +18 -18
- ccxt/delta.py +21 -21
- ccxt/deribit.py +24 -24
- ccxt/digifinex.py +35 -35
- ccxt/exmo.py +19 -19
- ccxt/gate.py +38 -38
- ccxt/gemini.py +11 -11
- ccxt/hitbtc.py +27 -27
- ccxt/hollaex.py +19 -19
- ccxt/htx.py +47 -44
- ccxt/huobijp.py +22 -22
- ccxt/idex.py +20 -20
- ccxt/independentreserve.py +9 -9
- ccxt/indodax.py +10 -10
- ccxt/kraken.py +25 -25
- ccxt/krakenfutures.py +17 -17
- ccxt/kucoin.py +27 -27
- ccxt/kucoinfutures.py +20 -20
- ccxt/kuna.py +19 -19
- ccxt/latoken.py +14 -14
- ccxt/lbank.py +18 -18
- ccxt/luno.py +14 -14
- ccxt/lykke.py +12 -12
- ccxt/mercado.py +11 -11
- ccxt/mexc.py +36 -36
- ccxt/ndax.py +18 -18
- ccxt/novadax.py +17 -17
- ccxt/oceanex.py +12 -12
- ccxt/okcoin.py +19 -19
- ccxt/okx.py +48 -45
- ccxt/p2b.py +6 -6
- ccxt/paymium.py +6 -6
- ccxt/phemex.py +57 -57
- ccxt/poloniex.py +31 -30
- ccxt/poloniexfutures.py +16 -16
- ccxt/pro/__init__.py +1 -1
- ccxt/pro/alpaca.py +3 -3
- ccxt/pro/ascendex.py +2 -2
- ccxt/pro/binance.py +9 -9
- ccxt/pro/bingx.py +3 -3
- ccxt/pro/bitfinex.py +3 -3
- ccxt/pro/bitfinex2.py +3 -3
- ccxt/pro/bitget.py +3 -3
- ccxt/pro/bitmart.py +2 -2
- ccxt/pro/bitmex.py +3 -3
- ccxt/pro/bitpanda.py +3 -3
- ccxt/pro/bitrue.py +2 -2
- ccxt/pro/bitstamp.py +2 -2
- ccxt/pro/bittrex.py +3 -3
- ccxt/pro/bitvavo.py +3 -3
- ccxt/pro/blockchaincom.py +2 -2
- ccxt/pro/bybit.py +4 -4
- ccxt/pro/cex.py +3 -3
- ccxt/pro/coinbasepro.py +3 -3
- ccxt/pro/coinex.py +2 -2
- ccxt/pro/cryptocom.py +5 -5
- ccxt/pro/deribit.py +3 -3
- ccxt/pro/exmo.py +2 -2
- ccxt/pro/gate.py +3 -3
- ccxt/pro/gemini.py +2 -2
- ccxt/pro/hitbtc.py +4 -4
- ccxt/pro/hollaex.py +3 -3
- ccxt/pro/htx.py +3 -3
- ccxt/pro/idex.py +3 -3
- ccxt/pro/kraken.py +7 -7
- ccxt/pro/krakenfutures.py +4 -4
- ccxt/pro/kucoin.py +3 -3
- ccxt/pro/kucoinfutures.py +3 -3
- ccxt/pro/mexc.py +3 -3
- ccxt/pro/okcoin.py +2 -2
- ccxt/pro/okx.py +6 -6
- ccxt/pro/phemex.py +3 -3
- ccxt/pro/poloniex.py +3 -3
- ccxt/pro/poloniexfutures.py +3 -3
- ccxt/pro/probit.py +3 -3
- ccxt/pro/wazirx.py +3 -3
- ccxt/pro/whitebit.py +3 -3
- ccxt/pro/woo.py +2 -2
- ccxt/probit.py +22 -22
- ccxt/test/base/test_shared_methods.py +3 -3
- ccxt/test/test_async.py +543 -535
- ccxt/test/test_sync.py +542 -534
- ccxt/tidex.py +15 -15
- ccxt/timex.py +20 -20
- ccxt/tokocrypto.py +16 -16
- ccxt/upbit.py +15 -15
- ccxt/wavesexchange.py +12 -12
- ccxt/wazirx.py +13 -13
- ccxt/whitebit.py +26 -26
- ccxt/woo.py +47 -47
- ccxt/yobit.py +8 -8
- ccxt/zaif.py +10 -10
- ccxt/zonda.py +16 -16
- {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/METADATA +10 -8
- ccxt-4.1.56.dist-info/RECORD +449 -0
- ccxt/async_support/bitstamp1.py +0 -402
- ccxt/async_support/lbank2.py +0 -2620
- ccxt/bitstamp1.py +0 -402
- ccxt/lbank2.py +0 -2619
- ccxt-4.1.54.dist-info/RECORD +0 -453
- {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/WHEEL +0 -0
- {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/top_level.txt +0 -0
ccxt/test/test_async.py
CHANGED
@@ -21,11 +21,22 @@ sys.path.append(root)
|
|
21
21
|
import ccxt.async_support as ccxt # noqa: E402
|
22
22
|
|
23
23
|
# ------------------------------------------------------------------------------
|
24
|
+
import asyncio
|
25
|
+
# from typing import Optional
|
26
|
+
# from typing import List
|
27
|
+
from ccxt.base.errors import NotSupported
|
28
|
+
from ccxt.base.errors import NetworkError
|
29
|
+
from ccxt.base.errors import ExchangeNotAvailable
|
30
|
+
from ccxt.base.errors import OnMaintenance
|
31
|
+
from ccxt.base.errors import AuthenticationError
|
24
32
|
|
33
|
+
# ------------------------------------------------------------------------------
|
25
34
|
|
26
35
|
class Argv(object):
|
27
|
-
|
28
|
-
|
36
|
+
id_tests = False
|
37
|
+
static_tests = False
|
38
|
+
request_tests = False
|
39
|
+
response_tests = False
|
29
40
|
token_bucket = False
|
30
41
|
sandbox = False
|
31
42
|
privateOnly = False
|
@@ -48,6 +59,8 @@ parser.add_argument('--verbose', action='store_true', help='enable verbose outpu
|
|
48
59
|
parser.add_argument('--info', action='store_true', help='enable info output')
|
49
60
|
parser.add_argument('--static', action='store_true', help='run static tests')
|
50
61
|
parser.add_argument('--idTests', action='store_true', help='run brokerId tests')
|
62
|
+
parser.add_argument('--responseTests', action='store_true', help='run response tests')
|
63
|
+
parser.add_argument('--requestTests', action='store_true', help='run response tests')
|
51
64
|
parser.add_argument('--nonce', type=int, help='integer')
|
52
65
|
parser.add_argument('exchange', type=str, help='exchange id in lowercase', nargs='?')
|
53
66
|
parser.add_argument('symbol', type=str, help='symbol in uppercase', nargs='?')
|
@@ -81,17 +94,6 @@ sys.excepthook = handle_all_unhandled_exceptions
|
|
81
94
|
|
82
95
|
# non-transpiled part, but shared names among langs
|
83
96
|
|
84
|
-
|
85
|
-
class baseMainTestClass():
|
86
|
-
lang = 'PY'
|
87
|
-
staticTestsFailed = False
|
88
|
-
skippedMethods = {}
|
89
|
-
checkedPublicTests = {}
|
90
|
-
testFiles = {}
|
91
|
-
publicTests = {}
|
92
|
-
pass
|
93
|
-
|
94
|
-
|
95
97
|
is_synchronous = 'async' not in os.path.basename(__file__)
|
96
98
|
|
97
99
|
rootDir = current_dir + '/../../../'
|
@@ -101,6 +103,22 @@ LOG_CHARS_LENGTH = 10000
|
|
101
103
|
ext = 'py'
|
102
104
|
|
103
105
|
|
106
|
+
class baseMainTestClass():
|
107
|
+
lang = 'PY'
|
108
|
+
request_tests_failed = False
|
109
|
+
response_tests_failed = False
|
110
|
+
response_tests = False
|
111
|
+
skipped_methods = {}
|
112
|
+
check_public_tests = {}
|
113
|
+
test_files = {}
|
114
|
+
public_tests = {}
|
115
|
+
root_dir = rootDir
|
116
|
+
env_vars = envVars
|
117
|
+
ext = ext
|
118
|
+
root_dir_for_skips = rootDirForSkips
|
119
|
+
pass
|
120
|
+
|
121
|
+
|
104
122
|
def dump(*args):
|
105
123
|
print(' '.join([str(arg) for arg in args]))
|
106
124
|
|
@@ -153,6 +171,9 @@ async def call_method(testFiles, methodName, exchange, skippedProperties, args):
|
|
153
171
|
async def call_exchange_method_dynamically(exchange, methodName, args):
|
154
172
|
return await getattr(exchange, methodName)(*args)
|
155
173
|
|
174
|
+
async def call_overriden_method(exchange, methodName, args):
|
175
|
+
# needed for php
|
176
|
+
return await call_exchange_method_dynamically(exchange, methodName, args)
|
156
177
|
|
157
178
|
def exception_message(exc):
|
158
179
|
message = '[' + type(exc).__name__ + '] ' + "".join(format_exception(type(exc), exc, exc.__traceback__, limit=6))
|
@@ -197,196 +218,196 @@ async def close(exchange):
|
|
197
218
|
if (hasattr(exchange, 'close')):
|
198
219
|
await exchange.close()
|
199
220
|
|
200
|
-
|
201
|
-
|
202
|
-
# -*- coding: utf-8 -*-
|
203
|
-
|
204
|
-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
205
|
-
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
206
|
-
|
207
|
-
|
208
|
-
import asyncio
|
209
|
-
from typing import List
|
210
|
-
from ccxt.base.errors import NotSupported
|
211
|
-
from ccxt.base.errors import NetworkError
|
212
|
-
from ccxt.base.errors import ExchangeNotAvailable
|
213
|
-
from ccxt.base.errors import OnMaintenance
|
214
|
-
from ccxt.base.errors import AuthenticationError
|
221
|
+
def is_null_value(value):
|
222
|
+
return value is None
|
215
223
|
|
224
|
+
def set_fetch_response(exchange: ccxt.Exchange, data):
|
225
|
+
async def fetch(url, method='GET', headers=None, body=None):
|
226
|
+
return data
|
227
|
+
exchange.fetch = fetch
|
228
|
+
return exchange
|
216
229
|
|
230
|
+
# *********************************
|
231
|
+
# ***** AUTO-TRANSPILER-START *****
|
217
232
|
class testMainClass(baseMainTestClass):
|
218
|
-
|
219
233
|
def parse_cli_args(self):
|
220
|
-
self.
|
221
|
-
self.
|
234
|
+
self.response_tests = get_cli_arg_value('--responseTests')
|
235
|
+
self.id_tests = get_cli_arg_value('--idTests')
|
236
|
+
self.request_tests = get_cli_arg_value('--requestTests')
|
222
237
|
self.info = get_cli_arg_value('--info')
|
223
238
|
self.verbose = get_cli_arg_value('--verbose')
|
224
239
|
self.debug = get_cli_arg_value('--debug')
|
225
|
-
self.
|
226
|
-
self.
|
240
|
+
self.private_test = get_cli_arg_value('--private')
|
241
|
+
self.private_test_only = get_cli_arg_value('--privateOnly')
|
227
242
|
self.sandbox = get_cli_arg_value('--sandbox')
|
228
243
|
|
229
|
-
async def init(self,
|
244
|
+
async def init(self, exchange_id, symbol):
|
230
245
|
self.parse_cli_args()
|
231
|
-
if self.
|
232
|
-
await self.
|
246
|
+
if self.response_tests:
|
247
|
+
await self.run_static_response_tests(exchange_id, symbol)
|
233
248
|
return
|
234
|
-
if self.
|
249
|
+
if self.request_tests:
|
250
|
+
await self.run_static_request_tests(exchange_id, symbol) # symbol here is the testname
|
251
|
+
return
|
252
|
+
if self.id_tests:
|
235
253
|
await self.run_broker_id_tests()
|
236
254
|
return
|
237
|
-
|
238
|
-
dump('\nTESTING ', ext, {
|
239
|
-
|
255
|
+
symbol_str = symbol if symbol is not None else 'all'
|
256
|
+
dump('\nTESTING ', self.ext, {
|
257
|
+
'exchange': exchange_id,
|
258
|
+
'symbol': symbol_str,
|
259
|
+
}, '\n')
|
260
|
+
exchange_args = {
|
240
261
|
'verbose': self.verbose,
|
241
262
|
'debug': self.debug,
|
242
263
|
'enableRateLimit': True,
|
243
264
|
'timeout': 30000,
|
244
265
|
}
|
245
|
-
exchange = init_exchange(
|
266
|
+
exchange = init_exchange(exchange_id, exchange_args)
|
246
267
|
await self.import_files(exchange)
|
247
268
|
self.expand_settings(exchange, symbol)
|
248
269
|
await self.start_test(exchange, symbol)
|
249
270
|
|
250
271
|
async def import_files(self, exchange):
|
251
272
|
# exchange tests
|
252
|
-
self.
|
273
|
+
self.test_files = {}
|
253
274
|
properties = list(exchange.has.keys())
|
254
275
|
properties.append('loadMarkets')
|
255
276
|
await set_test_files(self, properties)
|
256
277
|
|
257
278
|
def expand_settings(self, exchange, symbol):
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
if
|
268
|
-
|
269
|
-
for i in range(0, len(
|
270
|
-
key =
|
271
|
-
if
|
272
|
-
|
273
|
-
if isinstance(
|
279
|
+
exchange_id = exchange.id
|
280
|
+
keys_global = self.root_dir + 'keys.json'
|
281
|
+
keys_local = self.root_dir + 'keys.local.json'
|
282
|
+
keys_global_exists = io_file_exists(keys_global)
|
283
|
+
keys_local_exists = io_file_exists(keys_local)
|
284
|
+
global_settings = io_file_read(keys_global) if keys_global_exists else {}
|
285
|
+
local_settings = io_file_read(keys_local) if keys_local_exists else {}
|
286
|
+
all_settings = exchange.deep_extend(global_settings, local_settings)
|
287
|
+
exchange_settings = exchange.safe_value(all_settings, exchange_id, {})
|
288
|
+
if exchange_settings:
|
289
|
+
setting_keys = list(exchange_settings.keys())
|
290
|
+
for i in range(0, len(setting_keys)):
|
291
|
+
key = setting_keys[i]
|
292
|
+
if exchange_settings[key]:
|
293
|
+
final_value = None
|
294
|
+
if isinstance(exchange_settings[key], dict):
|
274
295
|
existing = get_exchange_prop(exchange, key, {})
|
275
|
-
|
296
|
+
final_value = exchange.deep_extend(existing, exchange_settings[key])
|
276
297
|
else:
|
277
|
-
|
278
|
-
set_exchange_prop(exchange, key,
|
298
|
+
final_value = exchange_settings[key]
|
299
|
+
set_exchange_prop(exchange, key, final_value)
|
279
300
|
# credentials
|
280
|
-
|
281
|
-
objkeys = list(
|
301
|
+
req_creds = get_exchange_prop(exchange, 're' + 'quiredCredentials') # dont glue the r-e-q-u-i-r-e phrase, because leads to messed up transpilation
|
302
|
+
objkeys = list(req_creds.keys())
|
282
303
|
for i in range(0, len(objkeys)):
|
283
304
|
credential = objkeys[i]
|
284
|
-
|
285
|
-
if
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
if
|
290
|
-
set_exchange_prop(exchange, credential,
|
305
|
+
is_required = req_creds[credential]
|
306
|
+
if is_required and get_exchange_prop(exchange, credential) is None:
|
307
|
+
full_key = exchange_id + '_' + credential
|
308
|
+
credential_env_name = full_key.upper() # example: KRAKEN_APIKEY
|
309
|
+
credential_value = self.env_vars[credential_env_name] if (credential_env_name in self.env_vars) else None
|
310
|
+
if credential_value:
|
311
|
+
set_exchange_prop(exchange, credential, credential_value)
|
291
312
|
# skipped tests
|
292
|
-
|
293
|
-
|
294
|
-
|
313
|
+
skipped_file = self.root_dir_for_skips + 'skip-tests.json'
|
314
|
+
skipped_settings = io_file_read(skipped_file)
|
315
|
+
skipped_settings_for_exchange = exchange.safe_value(skipped_settings, exchange_id, {})
|
295
316
|
# others
|
296
|
-
timeout = exchange.safe_value(
|
317
|
+
timeout = exchange.safe_value(skipped_settings_for_exchange, 'timeout')
|
297
318
|
if timeout is not None:
|
298
319
|
exchange.timeout = timeout
|
299
|
-
exchange.
|
300
|
-
self.
|
301
|
-
self.
|
320
|
+
exchange.https_proxy = exchange.safe_string(skipped_settings_for_exchange, 'httpsProxy')
|
321
|
+
self.skipped_methods = exchange.safe_value(skipped_settings_for_exchange, 'skipMethods', {})
|
322
|
+
self.checked_public_tests = {}
|
302
323
|
|
303
324
|
def add_padding(self, message, size):
|
304
325
|
# has to be transpilable
|
305
326
|
res = ''
|
306
|
-
|
307
|
-
|
308
|
-
|
327
|
+
message_length = len(message) # avoid php transpilation issue
|
328
|
+
missing_space = size - message_length - 0 # - 0 is added just to trick transpile to treat the .length as a string for php
|
329
|
+
if missing_space > 0:
|
330
|
+
for i in range(0, missing_space):
|
309
331
|
res += ' '
|
310
332
|
return message + res
|
311
333
|
|
312
|
-
async def test_method(self,
|
313
|
-
|
314
|
-
|
315
|
-
# if
|
316
|
-
if not
|
334
|
+
async def test_method(self, method_name, exchange, args, is_public):
|
335
|
+
is_load_markets = (method_name == 'loadMarkets')
|
336
|
+
method_name_in_test = get_test_name(method_name)
|
337
|
+
# if this is a private test, and the implementation was already tested in public, then no need to re-test it in private test (exception is fetchCurrencies, because our approach in base exchange)
|
338
|
+
if not is_public and (method_name_in_test in self.checked_public_tests) and (method_name != 'fetchCurrencies'):
|
317
339
|
return
|
318
|
-
|
319
|
-
if not
|
320
|
-
|
321
|
-
elif (
|
322
|
-
|
323
|
-
elif not (
|
324
|
-
|
325
|
-
# exceptionally for `loadMarkets` call, we call it before it's even checked for "skip" need it to be called anyway(but can skip "test.loadMarket" for it)
|
326
|
-
if
|
340
|
+
skip_message = None
|
341
|
+
if not is_load_markets and (not (method_name in exchange.has) or not exchange.has[method_name]):
|
342
|
+
skip_message = '[INFO:UNSUPPORTED_TEST]' # keep it aligned with the longest message
|
343
|
+
elif (method_name in self.skipped_methods) and (isinstance(self.skipped_methods[method_name], str)):
|
344
|
+
skip_message = '[INFO:SKIPPED_TEST]'
|
345
|
+
elif not (method_name_in_test in self.test_files):
|
346
|
+
skip_message = '[INFO:UNIMPLEMENTED_TEST]'
|
347
|
+
# exceptionally for `loadMarkets` call, we call it before it's even checked for "skip" as we need it to be called anyway (but can skip "test.loadMarket" for it)
|
348
|
+
if is_load_markets:
|
327
349
|
await exchange.load_markets(True)
|
328
|
-
if
|
350
|
+
if skip_message:
|
329
351
|
if self.info:
|
330
|
-
dump(self.add_padding(
|
352
|
+
dump(self.add_padding(skip_message, 25), exchange.id, method_name_in_test)
|
331
353
|
return
|
332
354
|
if self.info:
|
333
|
-
|
334
|
-
dump(self.add_padding('[INFO:TESTING]', 25), exchange.id,
|
335
|
-
|
336
|
-
await call_method(self.
|
355
|
+
args_stringified = '(' + ','.join(args) + ')'
|
356
|
+
dump(self.add_padding('[INFO:TESTING]', 25), exchange.id, method_name_in_test, args_stringified)
|
357
|
+
skipped_properties = exchange.safe_value(self.skipped_methods, method_name, {})
|
358
|
+
await call_method(self.test_files, method_name_in_test, exchange, skipped_properties, args)
|
337
359
|
# if it was passed successfully, add to the list of successfull tests
|
338
|
-
if
|
339
|
-
self.
|
360
|
+
if is_public:
|
361
|
+
self.checked_public_tests[method_name_in_test] = True
|
340
362
|
|
341
|
-
async def test_safe(self,
|
342
|
-
# `testSafe` method does not
|
343
|
-
# The reason we mute the thrown exceptions here is because if
|
363
|
+
async def test_safe(self, method_name, exchange, args=[], is_public=False):
|
364
|
+
# `testSafe` method does not throw an exception, instead mutes it.
|
365
|
+
# The reason we mute the thrown exceptions here is because if this test is part
|
344
366
|
# of "runPublicTests", then we don't want to stop the whole test if any single
|
345
367
|
# test-method fails. For example, if "fetchOrderBook" public test fails, we still
|
346
|
-
# want to run "fetchTickers" and other methods. However, independently
|
347
|
-
# from those test-methods we still echo-out(console.log/print...) the exception
|
368
|
+
# want to run "fetchTickers" and other methods. However, independently this fact,
|
369
|
+
# from those test-methods we still echo-out (console.log/print...) the exception
|
348
370
|
# messages with specific formatted message "[TEST_FAILURE] ..." and that output is
|
349
371
|
# then regex-parsed by run-tests.js, so the exceptions are still printed out to
|
350
372
|
# console from there. So, even if some public tests fail, the script will continue
|
351
|
-
# doing other things(testing other spot/swap or private tests ...)
|
352
|
-
|
353
|
-
|
354
|
-
for i in range(0,
|
373
|
+
# doing other things (testing other spot/swap or private tests ...)
|
374
|
+
max_retries = 3
|
375
|
+
args_stringified = exchange.json(args) # args.join() breaks when we provide a list of symbols | "args.toString()" breaks bcz of "array to string conversion"
|
376
|
+
for i in range(0, max_retries):
|
355
377
|
try:
|
356
|
-
await self.test_method(
|
378
|
+
await self.test_method(method_name, exchange, args, is_public)
|
357
379
|
return True
|
358
380
|
except Exception as e:
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
if
|
366
|
-
# if last retry was gone with same `tempFailure` error, then let's eventually return
|
367
|
-
if i ==
|
368
|
-
dump('[TEST_WARNING]', 'Method could not be tested due to a repeated Network/Availability issues', ' | ', exchange.id,
|
381
|
+
is_auth_error = (isinstance(e, AuthenticationError))
|
382
|
+
is_not_supported = (isinstance(e, NotSupported))
|
383
|
+
is_network_error = (isinstance(e, NetworkError)) # includes "DDoSProtection", "RateLimitExceeded", "RequestTimeout", "ExchangeNotAvailable", "isOperationFailed", "InvalidNonce", ...
|
384
|
+
is_exchange_not_available = (isinstance(e, ExchangeNotAvailable))
|
385
|
+
is_on_maintenance = (isinstance(e, OnMaintenance))
|
386
|
+
temp_failure = is_network_error and (not is_exchange_not_available or is_on_maintenance) # we do not mute specifically "ExchangeNotAvailable" excetpion (but its subtype "OnMaintenance" can be muted)
|
387
|
+
if temp_failure:
|
388
|
+
# if last retry was gone with same `tempFailure` error, then let's eventually return false
|
389
|
+
if i == max_retries - 1:
|
390
|
+
dump('[TEST_WARNING]', 'Method could not be tested due to a repeated Network/Availability issues', ' | ', exchange.id, method_name, args_stringified)
|
369
391
|
else:
|
370
392
|
# wait and retry again
|
371
393
|
await exchange.sleep(i * 1000) # increase wait seconds on every retry
|
372
394
|
continue
|
373
395
|
elif isinstance(e, OnMaintenance):
|
374
|
-
# in case of maintenance, skip exchange(don't fail the test)
|
396
|
+
# in case of maintenance, skip exchange (don't fail the test)
|
375
397
|
dump('[TEST_WARNING] Exchange is on maintenance', exchange.id)
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
dump('[TEST_WARNING]', 'Exchange can not be tested, because of authentication problems during loadMarkets', exception_message(e), exchange.id, methodName, argsStringified)
|
398
|
+
elif is_public and is_auth_error:
|
399
|
+
# in case of loadMarkets, it means that "tester" (developer or travis) does not have correct authentication, so it does not have a point to proceed at all
|
400
|
+
if method_name == 'loadMarkets':
|
401
|
+
dump('[TEST_WARNING]', 'Exchange can not be tested, because of authentication problems during loadMarkets', exception_message(e), exchange.id, method_name, args_stringified)
|
381
402
|
if self.info:
|
382
|
-
dump('[TEST_WARNING]', 'Authentication problem for public method', exception_message(e), exchange.id,
|
403
|
+
dump('[TEST_WARNING]', 'Authentication problem for public method', exception_message(e), exchange.id, method_name, args_stringified)
|
383
404
|
else:
|
384
|
-
# if not a temporary connectivity issue, then mark test(no need to re-try)
|
385
|
-
if
|
386
|
-
dump('[NOT_SUPPORTED]', exchange.id,
|
387
|
-
return True
|
405
|
+
# if not a temporary connectivity issue, then mark test as failed (no need to re-try)
|
406
|
+
if is_not_supported:
|
407
|
+
dump('[NOT_SUPPORTED]', exchange.id, method_name, args_stringified)
|
408
|
+
return True # why consider not supported as a failed test?
|
388
409
|
else:
|
389
|
-
dump('[TEST_FAILURE]', exception_message(e), exchange.id,
|
410
|
+
dump('[TEST_FAILURE]', exception_message(e), exchange.id, method_name, args_stringified)
|
390
411
|
return False
|
391
412
|
|
392
413
|
async def run_public_tests(self, exchange, symbol):
|
@@ -404,8 +425,8 @@ class testMainClass(baseMainTestClass):
|
|
404
425
|
'fetchTime': [],
|
405
426
|
}
|
406
427
|
market = exchange.market(symbol)
|
407
|
-
|
408
|
-
if
|
428
|
+
is_spot = market['spot']
|
429
|
+
if is_spot:
|
409
430
|
tests['fetchCurrencies'] = []
|
410
431
|
else:
|
411
432
|
tests['fetchFundingRates'] = [symbol]
|
@@ -414,76 +435,54 @@ class testMainClass(baseMainTestClass):
|
|
414
435
|
tests['fetchIndexOHLCV'] = [symbol]
|
415
436
|
tests['fetchMarkOHLCV'] = [symbol]
|
416
437
|
tests['fetchPremiumIndexOHLCV'] = [symbol]
|
417
|
-
self.
|
418
|
-
|
438
|
+
self.public_tests = tests
|
439
|
+
test_names = list(tests.keys())
|
419
440
|
promises = []
|
420
|
-
for i in range(0, len(
|
421
|
-
|
422
|
-
|
423
|
-
promises.append(self.test_safe(
|
441
|
+
for i in range(0, len(test_names)):
|
442
|
+
test_name = test_names[i]
|
443
|
+
test_args = tests[test_name]
|
444
|
+
promises.append(self.test_safe(test_name, exchange, test_args, True))
|
424
445
|
# todo - not yet ready in other langs too
|
425
|
-
# promises.
|
446
|
+
# promises.push (testThrottle ());
|
426
447
|
results = await asyncio.gather(*promises)
|
427
448
|
# now count which test-methods retuned `false` from "testSafe" and dump that info below
|
428
449
|
if self.info:
|
429
450
|
errors = []
|
430
|
-
for i in range(0, len(
|
451
|
+
for i in range(0, len(test_names)):
|
431
452
|
if not results[i]:
|
432
|
-
errors.append(
|
433
|
-
# we don't
|
434
|
-
|
453
|
+
errors.append(test_names[i])
|
454
|
+
# we don't throw exception for public-tests, see comments under 'testSafe' method
|
455
|
+
errors_in_message = ''
|
435
456
|
if errors:
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
dump(
|
457
|
+
failed_msg = ', '.join(errors)
|
458
|
+
errors_in_message = ' | Failed methods : ' + failed_msg
|
459
|
+
message_content = '[INFO:PUBLIC_TESTS_END] ' + market['type'] + errors_in_message
|
460
|
+
message_with_padding = self.add_padding(message_content, 25)
|
461
|
+
dump(message_with_padding, exchange.id)
|
441
462
|
|
442
463
|
async def load_exchange(self, exchange):
|
443
464
|
result = await self.test_safe('loadMarkets', exchange, [], True)
|
444
465
|
if not result:
|
445
466
|
return False
|
446
|
-
symbols = [
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
'BTC/ETH',
|
452
|
-
'ETH/BTC',
|
453
|
-
'BTC/JPY',
|
454
|
-
'ETH/EUR',
|
455
|
-
'ETH/JPY',
|
456
|
-
'ETH/CNY',
|
457
|
-
'ETH/USD',
|
458
|
-
'LTC/CNY',
|
459
|
-
'DASH/BTC',
|
460
|
-
'DOGE/BTC',
|
461
|
-
'BTC/AUD',
|
462
|
-
'BTC/PLN',
|
463
|
-
'USD/SLL',
|
464
|
-
'BTC/RUB',
|
465
|
-
'BTC/UAH',
|
466
|
-
'LTC/BTC',
|
467
|
-
'EUR/USD',
|
468
|
-
]
|
469
|
-
resultSymbols = []
|
470
|
-
exchangeSpecificSymbols = exchange.symbols
|
471
|
-
for i in range(0, len(exchangeSpecificSymbols)):
|
472
|
-
symbol = exchangeSpecificSymbols[i]
|
467
|
+
symbols = ['BTC/CNY', 'BTC/USD', 'BTC/USDT', 'BTC/EUR', 'BTC/ETH', 'ETH/BTC', 'BTC/JPY', 'ETH/EUR', 'ETH/JPY', 'ETH/CNY', 'ETH/USD', 'LTC/CNY', 'DASH/BTC', 'DOGE/BTC', 'BTC/AUD', 'BTC/PLN', 'USD/SLL', 'BTC/RUB', 'BTC/UAH', 'LTC/BTC', 'EUR/USD']
|
468
|
+
result_symbols = []
|
469
|
+
exchange_specific_symbols = exchange.symbols
|
470
|
+
for i in range(0, len(exchange_specific_symbols)):
|
471
|
+
symbol = exchange_specific_symbols[i]
|
473
472
|
if exchange.in_array(symbol, symbols):
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
if
|
479
|
-
if
|
480
|
-
|
473
|
+
result_symbols.append(symbol)
|
474
|
+
result_msg = ''
|
475
|
+
result_length = len(result_symbols)
|
476
|
+
exchange_symbols_length = len(exchange.symbols)
|
477
|
+
if result_length > 0:
|
478
|
+
if exchange_symbols_length > result_length:
|
479
|
+
result_msg = ', '.join(result_symbols) + ' + more...'
|
481
480
|
else:
|
482
|
-
|
483
|
-
dump('Exchange loaded',
|
481
|
+
result_msg = ', '.join(result_symbols)
|
482
|
+
dump('Exchange loaded', exchange_symbols_length, 'symbols', result_msg)
|
484
483
|
return True
|
485
484
|
|
486
|
-
def get_test_symbol(self, exchange,
|
485
|
+
def get_test_symbol(self, exchange, is_spot, symbols):
|
487
486
|
symbol = None
|
488
487
|
for i in range(0, len(symbols)):
|
489
488
|
s = symbols[i]
|
@@ -518,140 +517,86 @@ class testMainClass(baseMainTestClass):
|
|
518
517
|
return res
|
519
518
|
|
520
519
|
def get_valid_symbol(self, exchange, spot=True):
|
521
|
-
|
522
|
-
codes = [
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
'BCH',
|
528
|
-
'EOS',
|
529
|
-
'BNB',
|
530
|
-
'BSV',
|
531
|
-
'USDT',
|
532
|
-
'ATOM',
|
533
|
-
'BAT',
|
534
|
-
'BTG',
|
535
|
-
'DASH',
|
536
|
-
'DOGE',
|
537
|
-
'ETC',
|
538
|
-
'IOTA',
|
539
|
-
'LSK',
|
540
|
-
'MKR',
|
541
|
-
'NEO',
|
542
|
-
'PAX',
|
543
|
-
'QTUM',
|
544
|
-
'TRX',
|
545
|
-
'TUSD',
|
546
|
-
'USD',
|
547
|
-
'USDC',
|
548
|
-
'WAVES',
|
549
|
-
'XEM',
|
550
|
-
'XMR',
|
551
|
-
'ZEC',
|
552
|
-
'ZRX',
|
553
|
-
]
|
554
|
-
spotSymbols = [
|
555
|
-
'BTC/USD',
|
556
|
-
'BTC/USDT',
|
557
|
-
'BTC/CNY',
|
558
|
-
'BTC/EUR',
|
559
|
-
'BTC/ETH',
|
560
|
-
'ETH/BTC',
|
561
|
-
'ETH/USD',
|
562
|
-
'ETH/USDT',
|
563
|
-
'BTC/JPY',
|
564
|
-
'LTC/BTC',
|
565
|
-
'ZRX/WETH',
|
566
|
-
'EUR/USD',
|
567
|
-
]
|
568
|
-
swapSymbols = [
|
569
|
-
'BTC/USDT:USDT',
|
570
|
-
'BTC/USD:USD',
|
571
|
-
'ETH/USDT:USDT',
|
572
|
-
'ETH/USD:USD',
|
573
|
-
'LTC/USDT:USDT',
|
574
|
-
'DOGE/USDT:USDT',
|
575
|
-
'ADA/USDT:USDT',
|
576
|
-
'BTC/USD:BTC',
|
577
|
-
'ETH/USD:ETH',
|
578
|
-
]
|
579
|
-
targetSymbols = spotSymbols if spot else swapSymbols
|
580
|
-
symbol = self.get_test_symbol(exchange, spot, targetSymbols)
|
520
|
+
current_type_markets = self.get_markets_from_exchange(exchange, spot)
|
521
|
+
codes = ['BTC', 'ETH', 'XRP', 'LTC', 'BCH', 'EOS', 'BNB', 'BSV', 'USDT', 'ATOM', 'BAT', 'BTG', 'DASH', 'DOGE', 'ETC', 'IOTA', 'LSK', 'MKR', 'NEO', 'PAX', 'QTUM', 'TRX', 'TUSD', 'USD', 'USDC', 'WAVES', 'XEM', 'XMR', 'ZEC', 'ZRX']
|
522
|
+
spot_symbols = ['BTC/USD', 'BTC/USDT', 'BTC/CNY', 'BTC/EUR', 'BTC/ETH', 'ETH/BTC', 'ETH/USD', 'ETH/USDT', 'BTC/JPY', 'LTC/BTC', 'ZRX/WETH', 'EUR/USD']
|
523
|
+
swap_symbols = ['BTC/USDT:USDT', 'BTC/USD:USD', 'ETH/USDT:USDT', 'ETH/USD:USD', 'LTC/USDT:USDT', 'DOGE/USDT:USDT', 'ADA/USDT:USDT', 'BTC/USD:BTC', 'ETH/USD:ETH']
|
524
|
+
target_symbols = spot_symbols if spot else swap_symbols
|
525
|
+
symbol = self.get_test_symbol(exchange, spot, target_symbols)
|
581
526
|
# if symbols wasn't found from above hardcoded list, then try to locate any symbol which has our target hardcoded 'base' code
|
582
527
|
if symbol is None:
|
583
528
|
for i in range(0, len(codes)):
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
if
|
590
|
-
symbol = self.get_test_symbol(exchange, spot,
|
529
|
+
current_code = codes[i]
|
530
|
+
markets_array_for_current_code = exchange.filter_by(current_type_markets, 'base', current_code)
|
531
|
+
indexed_mkts = exchange.index_by(markets_array_for_current_code, 'symbol')
|
532
|
+
symbols_array_for_current_code = list(indexed_mkts.keys())
|
533
|
+
symbols_length = len(symbols_array_for_current_code)
|
534
|
+
if symbols_length:
|
535
|
+
symbol = self.get_test_symbol(exchange, spot, symbols_array_for_current_code)
|
591
536
|
break
|
592
537
|
# if there wasn't found any symbol with our hardcoded 'base' code, then just try to find symbols that are 'active'
|
593
538
|
if symbol is None:
|
594
|
-
|
595
|
-
|
596
|
-
for i in range(0, len(
|
597
|
-
|
598
|
-
symbol = self.get_test_symbol(exchange, spot,
|
539
|
+
active_markets = exchange.filter_by(current_type_markets, 'active', True)
|
540
|
+
active_symbols = []
|
541
|
+
for i in range(0, len(active_markets)):
|
542
|
+
active_symbols.append(active_markets[i]['symbol'])
|
543
|
+
symbol = self.get_test_symbol(exchange, spot, active_symbols)
|
599
544
|
if symbol is None:
|
600
|
-
values = list(
|
601
|
-
|
602
|
-
if
|
545
|
+
values = list(current_type_markets.values())
|
546
|
+
values_length = len(values)
|
547
|
+
if values_length > 0:
|
603
548
|
first = values[0]
|
604
549
|
if first is not None:
|
605
550
|
symbol = first['symbol']
|
606
551
|
return symbol
|
607
552
|
|
608
|
-
async def test_exchange(self, exchange,
|
609
|
-
|
610
|
-
|
611
|
-
if
|
612
|
-
market = exchange.market(
|
553
|
+
async def test_exchange(self, exchange, provided_symbol=None):
|
554
|
+
spot_symbol = None
|
555
|
+
swap_symbol = None
|
556
|
+
if provided_symbol is not None:
|
557
|
+
market = exchange.market(provided_symbol)
|
613
558
|
if market['spot']:
|
614
|
-
|
559
|
+
spot_symbol = provided_symbol
|
615
560
|
else:
|
616
|
-
|
561
|
+
swap_symbol = provided_symbol
|
617
562
|
else:
|
618
563
|
if exchange.has['spot']:
|
619
|
-
|
564
|
+
spot_symbol = self.get_valid_symbol(exchange, True)
|
620
565
|
if exchange.has['swap']:
|
621
|
-
|
622
|
-
if
|
623
|
-
dump('Selected SPOT SYMBOL:',
|
624
|
-
if
|
625
|
-
dump('Selected SWAP SYMBOL:',
|
626
|
-
if not self.
|
627
|
-
if exchange.has['spot'] and
|
566
|
+
swap_symbol = self.get_valid_symbol(exchange, False)
|
567
|
+
if spot_symbol is not None:
|
568
|
+
dump('Selected SPOT SYMBOL:', spot_symbol)
|
569
|
+
if swap_symbol is not None:
|
570
|
+
dump('Selected SWAP SYMBOL:', swap_symbol)
|
571
|
+
if not self.private_test_only:
|
572
|
+
if exchange.has['spot'] and spot_symbol is not None:
|
628
573
|
if self.info:
|
629
574
|
dump('[INFO:SPOT TESTS]')
|
630
575
|
exchange.options['type'] = 'spot'
|
631
|
-
await self.run_public_tests(exchange,
|
632
|
-
if exchange.has['swap'] and
|
576
|
+
await self.run_public_tests(exchange, spot_symbol)
|
577
|
+
if exchange.has['swap'] and swap_symbol is not None:
|
633
578
|
if self.info:
|
634
579
|
dump('[INFO:SWAP TESTS]')
|
635
580
|
exchange.options['type'] = 'swap'
|
636
|
-
await self.run_public_tests(exchange,
|
637
|
-
if self.
|
638
|
-
if exchange.has['spot'] and
|
581
|
+
await self.run_public_tests(exchange, swap_symbol)
|
582
|
+
if self.private_test or self.private_test_only:
|
583
|
+
if exchange.has['spot'] and spot_symbol is not None:
|
639
584
|
exchange.options['defaultType'] = 'spot'
|
640
|
-
await self.run_private_tests(exchange,
|
641
|
-
if exchange.has['swap'] and
|
585
|
+
await self.run_private_tests(exchange, spot_symbol)
|
586
|
+
if exchange.has['swap'] and swap_symbol is not None:
|
642
587
|
exchange.options['defaultType'] = 'swap'
|
643
|
-
await self.run_private_tests(exchange,
|
588
|
+
await self.run_private_tests(exchange, swap_symbol)
|
644
589
|
|
645
590
|
async def run_private_tests(self, exchange, symbol):
|
646
591
|
if not exchange.check_required_credentials(False):
|
647
592
|
dump('[Skipping private tests]', 'Keys not found')
|
648
593
|
return
|
649
594
|
code = self.get_exchange_code(exchange)
|
650
|
-
# if exchange.extendedTest
|
651
|
-
# await test('InvalidNonce', exchange, symbol)
|
652
|
-
# await test('OrderNotFound', exchange, symbol)
|
653
|
-
# await test('InvalidOrder', exchange, symbol)
|
654
|
-
# await test('InsufficientFunds', exchange, symbol, balance)
|
595
|
+
# if (exchange.extendedTest) {
|
596
|
+
# await test ('InvalidNonce', exchange, symbol);
|
597
|
+
# await test ('OrderNotFound', exchange, symbol);
|
598
|
+
# await test ('InvalidOrder', exchange, symbol);
|
599
|
+
# await test ('InsufficientFunds', exchange, symbol, balance); # danger zone - won't execute with non-empty balance
|
655
600
|
# }
|
656
601
|
tests = {
|
657
602
|
'signIn': [],
|
@@ -672,40 +617,25 @@ class testMainClass(baseMainTestClass):
|
|
672
617
|
'fetchBorrowRates': [],
|
673
618
|
'fetchBorrowRate': [code],
|
674
619
|
'fetchBorrowInterest': [code, symbol],
|
675
|
-
# 'addMargin': [],
|
676
|
-
# 'reduceMargin': [],
|
677
|
-
# 'setMargin': [],
|
678
|
-
# 'setMarginMode': [],
|
679
|
-
# 'setLeverage': [],
|
680
620
|
'cancelAllOrders': [symbol],
|
681
|
-
# 'cancelOrder': [],
|
682
|
-
# 'cancelOrders': [],
|
683
621
|
'fetchCanceledOrders': [symbol],
|
684
|
-
# 'fetchClosedOrder': [],
|
685
|
-
# 'fetchOpenOrder': [],
|
686
|
-
# 'fetchOrder': [],
|
687
|
-
# 'fetchOrderTrades': [],
|
688
622
|
'fetchPosition': [symbol],
|
689
623
|
'fetchDeposit': [code],
|
690
624
|
'createDepositAddress': [code],
|
691
625
|
'fetchDepositAddress': [code],
|
692
626
|
'fetchDepositAddresses': [code],
|
693
627
|
'fetchDepositAddressesByNetwork': [code],
|
694
|
-
# 'editOrder': [],
|
695
628
|
'fetchBorrowRateHistory': [code],
|
696
629
|
'fetchBorrowRatesPerSymbol': [],
|
697
630
|
'fetchLedgerEntry': [code],
|
698
|
-
# 'fetchWithdrawal': [],
|
699
|
-
# 'transfer': [],
|
700
|
-
# 'withdraw': [],
|
701
631
|
}
|
702
632
|
market = exchange.market(symbol)
|
703
|
-
|
704
|
-
if
|
633
|
+
is_spot = market['spot']
|
634
|
+
if is_spot:
|
705
635
|
tests['fetchCurrencies'] = []
|
706
636
|
else:
|
707
637
|
# derivatives only
|
708
|
-
tests['fetchPositions'] = [symbol] #
|
638
|
+
tests['fetchPositions'] = [symbol] # this test fetches all positions for 1 symbol
|
709
639
|
tests['fetchPosition'] = [symbol]
|
710
640
|
tests['fetchPositionRisk'] = [symbol]
|
711
641
|
tests['setPositionMode'] = [symbol]
|
@@ -713,23 +643,23 @@ class testMainClass(baseMainTestClass):
|
|
713
643
|
tests['fetchOpenInterestHistory'] = [symbol]
|
714
644
|
tests['fetchFundingRateHistory'] = [symbol]
|
715
645
|
tests['fetchFundingHistory'] = [symbol]
|
716
|
-
|
717
|
-
|
646
|
+
combined_public_private_tests = exchange.deep_extend(self.public_tests, tests)
|
647
|
+
test_names = list(combined_public_private_tests.keys())
|
718
648
|
promises = []
|
719
|
-
for i in range(0, len(
|
720
|
-
|
721
|
-
|
722
|
-
promises.append(self.test_safe(
|
649
|
+
for i in range(0, len(test_names)):
|
650
|
+
test_name = test_names[i]
|
651
|
+
test_args = combined_public_private_tests[test_name]
|
652
|
+
promises.append(self.test_safe(test_name, exchange, test_args, False))
|
723
653
|
results = await asyncio.gather(*promises)
|
724
654
|
errors = []
|
725
|
-
for i in range(0, len(
|
726
|
-
|
655
|
+
for i in range(0, len(test_names)):
|
656
|
+
test_name = test_names[i]
|
727
657
|
success = results[i]
|
728
658
|
if not success:
|
729
|
-
errors.append(
|
730
|
-
|
731
|
-
if
|
732
|
-
#
|
659
|
+
errors.append(test_name)
|
660
|
+
errors_cnt = len(errors) # PHP transpile count($errors)
|
661
|
+
if errors_cnt > 0:
|
662
|
+
# throw new Error ('Failed private tests [' + market['type'] + ']: ' + errors.join (', '));
|
733
663
|
dump('[TEST_FAILURE]', 'Failed private tests [' + market['type'] + ']: ' + ', '.join(errors))
|
734
664
|
else:
|
735
665
|
if self.info:
|
@@ -741,7 +671,6 @@ class testMainClass(baseMainTestClass):
|
|
741
671
|
return
|
742
672
|
if self.sandbox or get_exchange_prop(exchange, 'sandbox'):
|
743
673
|
exchange.set_sandbox_mode(True)
|
744
|
-
# because of python-async, we need proper `.close()` handling
|
745
674
|
try:
|
746
675
|
result = await self.load_exchange(exchange)
|
747
676
|
if not result:
|
@@ -753,296 +682,373 @@ class testMainClass(baseMainTestClass):
|
|
753
682
|
await close(exchange)
|
754
683
|
raise e
|
755
684
|
|
756
|
-
def assert_static_error(self, cond
|
685
|
+
def assert_static_error(self, cond, message, calculated_output, stored_output):
|
757
686
|
# -----------------------------------------------------------------------------
|
758
687
|
# --- Init of static tests functions------------------------------------------
|
759
688
|
# -----------------------------------------------------------------------------
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
assert cond,
|
689
|
+
calculated_string = json_stringify(calculated_output)
|
690
|
+
output_string = json_stringify(stored_output)
|
691
|
+
error_message = message + ' expected ' + output_string + ' received: ' + calculated_string
|
692
|
+
assert cond, error_message
|
764
693
|
|
765
|
-
def load_markets_from_file(self, id
|
694
|
+
def load_markets_from_file(self, id):
|
766
695
|
# load markets from file
|
767
|
-
# to make
|
696
|
+
# to make this test as fast as possible
|
768
697
|
# and basically independent from the exchange
|
769
698
|
# so we can run it offline
|
770
|
-
filename =
|
699
|
+
filename = self.root_dir + './ts/src/test/static/markets/' + id + '.json'
|
700
|
+
content = io_file_read(filename)
|
701
|
+
return content
|
702
|
+
|
703
|
+
def load_currencies_from_file(self, id):
|
704
|
+
filename = self.root_dir + './ts/src/test/static/currencies/' + id + '.json'
|
771
705
|
content = io_file_read(filename)
|
772
706
|
return content
|
773
707
|
|
774
|
-
def load_static_data(self,
|
775
|
-
folder = rootDir + './ts/src/test/static/data/'
|
708
|
+
def load_static_data(self, folder, target_exchange=None):
|
776
709
|
result = {}
|
777
|
-
if
|
710
|
+
if target_exchange:
|
778
711
|
# read a single exchange
|
779
|
-
result[
|
712
|
+
result[target_exchange] = io_file_read(folder + target_exchange + '.json')
|
780
713
|
return result
|
781
714
|
files = io_dir_read(folder)
|
782
715
|
for i in range(0, len(files)):
|
783
716
|
file = files[i]
|
784
|
-
|
717
|
+
exchange_name = file.replace('.json', '')
|
785
718
|
content = io_file_read(folder + file)
|
786
|
-
result[
|
719
|
+
result[exchange_name] = content
|
787
720
|
return result
|
788
721
|
|
789
|
-
def remove_hostnamefrom_url(self, url
|
722
|
+
def remove_hostnamefrom_url(self, url):
|
790
723
|
if url is None:
|
791
724
|
return None
|
792
|
-
|
725
|
+
url_parts = url.split('/')
|
793
726
|
res = ''
|
794
|
-
for i in range(0, len(
|
727
|
+
for i in range(0, len(url_parts)):
|
795
728
|
if i > 2:
|
796
|
-
current =
|
729
|
+
current = url_parts[i]
|
797
730
|
if current.find('?') > -1:
|
798
|
-
# handle urls like
|
799
|
-
|
731
|
+
# handle urls like this: /v1/account/accounts?AccessK
|
732
|
+
current_parts = current.split('?')
|
800
733
|
res += '/'
|
801
|
-
res +=
|
734
|
+
res += current_parts[0]
|
802
735
|
break
|
803
736
|
res += '/'
|
804
737
|
res += current
|
805
738
|
return res
|
806
739
|
|
807
|
-
def urlencoded_to_dict(self, url
|
740
|
+
def urlencoded_to_dict(self, url):
|
808
741
|
result = {}
|
809
742
|
parts = url.split('&')
|
810
743
|
for i in range(0, len(parts)):
|
811
744
|
part = parts[i]
|
812
|
-
|
813
|
-
|
814
|
-
if
|
745
|
+
key_value = part.split('=')
|
746
|
+
keys_length = len(key_value)
|
747
|
+
if keys_length != 2:
|
815
748
|
continue
|
816
|
-
key =
|
817
|
-
value =
|
749
|
+
key = key_value[0]
|
750
|
+
value = key_value[1]
|
818
751
|
if (value is not None) and ((value.startswith('[')) or (value.startswith('{'))):
|
819
|
-
# some exchanges might return something like
|
752
|
+
# some exchanges might return something like this: timestamp=1699382693405&batchOrders=[{\"symbol\":\"LTCUSDT\",\"side\":\"BUY\",\"newClientOrderI
|
820
753
|
value = json_parse(value)
|
821
754
|
result[key] = value
|
822
755
|
return result
|
823
756
|
|
824
|
-
def assert_new_and_stored_output(self, exchange,
|
825
|
-
if (
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
757
|
+
def assert_new_and_stored_output(self, exchange, skip_keys, new_output, stored_output, strict_type_check=True):
|
758
|
+
if is_null_value(new_output) and is_null_value(stored_output):
|
759
|
+
return
|
760
|
+
if not new_output and not stored_output:
|
761
|
+
return
|
762
|
+
if (isinstance(stored_output, dict)) and (isinstance(new_output, dict)):
|
763
|
+
stored_output_keys = list(stored_output.keys())
|
764
|
+
new_output_keys = list(new_output.keys())
|
765
|
+
stored_keys_length = len(stored_output_keys)
|
766
|
+
new_keys_length = len(new_output_keys)
|
767
|
+
self.assert_static_error(stored_keys_length == new_keys_length, 'output length mismatch', stored_output, new_output)
|
831
768
|
# iterate over the keys
|
832
|
-
for i in range(0, len(
|
833
|
-
key =
|
834
|
-
if exchange.in_array(key,
|
769
|
+
for i in range(0, len(stored_output_keys)):
|
770
|
+
key = stored_output_keys[i]
|
771
|
+
if exchange.in_array(key, skip_keys):
|
835
772
|
continue
|
836
|
-
if not (exchange.in_array(key,
|
837
|
-
self.assert_static_error(False, 'output key missing: ' + key,
|
838
|
-
|
839
|
-
|
840
|
-
self.assert_new_and_stored_output(exchange,
|
841
|
-
elif isinstance(
|
842
|
-
|
843
|
-
|
844
|
-
self.assert_static_error(
|
845
|
-
for i in range(0, len(
|
846
|
-
|
847
|
-
|
848
|
-
self.assert_new_and_stored_output(exchange,
|
773
|
+
if not (exchange.in_array(key, new_output_keys)):
|
774
|
+
self.assert_static_error(False, 'output key missing: ' + key, stored_output, new_output)
|
775
|
+
stored_value = stored_output[key]
|
776
|
+
new_value = new_output[key]
|
777
|
+
self.assert_new_and_stored_output(exchange, skip_keys, new_value, stored_value, strict_type_check)
|
778
|
+
elif isinstance(stored_output, list) and (isinstance(new_output, list)):
|
779
|
+
stored_array_length = len(stored_output)
|
780
|
+
new_array_length = len(new_output)
|
781
|
+
self.assert_static_error(stored_array_length == new_array_length, 'output length mismatch', stored_output, new_output)
|
782
|
+
for i in range(0, len(stored_output)):
|
783
|
+
stored_item = stored_output[i]
|
784
|
+
new_item = new_output[i]
|
785
|
+
self.assert_new_and_stored_output(exchange, skip_keys, new_item, stored_item, strict_type_check)
|
849
786
|
else:
|
850
787
|
# built-in types like strings, numbers, booleans
|
851
|
-
|
852
|
-
|
788
|
+
sanitized_new_output = None if (not new_output) else new_output # we store undefined as nulls in the json file so we need to convert it back
|
789
|
+
sanitized_stored_output = None if (not stored_output) else stored_output
|
790
|
+
new_output_string = str(sanitized_new_output) if sanitized_new_output else 'undefined'
|
791
|
+
stored_output_string = str(sanitized_stored_output) if sanitized_stored_output else 'undefined'
|
792
|
+
message_error = 'output value mismatch:' + new_output_string + ' != ' + stored_output_string
|
793
|
+
if strict_type_check:
|
794
|
+
# upon building the request we want strict type check to make sure all the types are correct
|
795
|
+
# when comparing the response we want to allow some flexibility, because a 50.0 can be equal to 50 after saving it to the json file
|
796
|
+
self.assert_static_error(sanitized_new_output == sanitized_stored_output, message_error, stored_output, new_output)
|
797
|
+
else:
|
798
|
+
is_boolean = (isinstance(sanitized_new_output, bool)) or (isinstance(sanitized_stored_output, bool))
|
799
|
+
is_string = (isinstance(sanitized_new_output, str)) or (isinstance(sanitized_stored_output, str))
|
800
|
+
is_undefined = (sanitized_new_output is None) or (sanitized_stored_output is None) # undefined is a perfetly valid value
|
801
|
+
if is_boolean or is_string or is_undefined:
|
802
|
+
self.assert_static_error(new_output_string == stored_output_string, message_error, stored_output, new_output)
|
803
|
+
else:
|
804
|
+
numeric_new_output = exchange.parse_to_numeric(new_output_string)
|
805
|
+
numeric_stored_output = exchange.parse_to_numeric(stored_output_string)
|
806
|
+
self.assert_static_error(numeric_new_output == numeric_stored_output, message_error, stored_output, new_output)
|
853
807
|
|
854
|
-
def
|
855
|
-
if
|
808
|
+
def assert_static_request_output(self, exchange, type, skip_keys, stored_url, request_url, stored_output, new_output):
|
809
|
+
if stored_url != request_url:
|
856
810
|
# remove the host part from the url
|
857
|
-
|
858
|
-
|
859
|
-
self.assert_static_error(
|
860
|
-
# body(aka storedOutput and newOutput) is not defined and information is in the url
|
811
|
+
first_path = self.remove_hostnamefrom_url(stored_url)
|
812
|
+
second_path = self.remove_hostnamefrom_url(request_url)
|
813
|
+
self.assert_static_error(first_path == second_path, 'url mismatch', first_path, second_path)
|
814
|
+
# body (aka storedOutput and newOutput) is not defined and information is in the url
|
861
815
|
# example: "https://open-api.bingx.com/openApi/spot/v1/trade/order?quoteOrderQty=5&side=BUY&symbol=LTC-USDT×tamp=1698777135343&type=MARKET&signature=d55a7e4f7f9dbe56c4004c9f3ab340869d3cb004e2f0b5b861e5fbd1762fd9a0
|
862
|
-
if (
|
863
|
-
if (
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
if (
|
816
|
+
if (stored_output is None) and (new_output is None):
|
817
|
+
if (stored_url is not None) and (request_url is not None):
|
818
|
+
stored_url_parts = stored_url.split('?')
|
819
|
+
new_url_parts = request_url.split('?')
|
820
|
+
stored_url_query = exchange.safe_value(stored_url_parts, 1)
|
821
|
+
new_url_query = exchange.safe_value(new_url_parts, 1)
|
822
|
+
if (stored_url_query is None) and (new_url_query is None):
|
869
823
|
# might be a get request without any query parameters
|
870
824
|
# example: https://api.gateio.ws/api/v4/delivery/usdt/positions
|
871
825
|
return
|
872
|
-
|
873
|
-
|
874
|
-
self.assert_new_and_stored_output(exchange,
|
826
|
+
stored_url_params = self.urlencoded_to_dict(stored_url_query)
|
827
|
+
new_url_params = self.urlencoded_to_dict(new_url_query)
|
828
|
+
self.assert_new_and_stored_output(exchange, skip_keys, new_url_params, stored_url_params)
|
875
829
|
return
|
876
|
-
# body is defined
|
877
830
|
if type == 'json':
|
878
|
-
if isinstance(
|
879
|
-
|
880
|
-
if isinstance(
|
881
|
-
|
831
|
+
if isinstance(stored_output, str):
|
832
|
+
stored_output = json_parse(stored_output)
|
833
|
+
if isinstance(new_output, str):
|
834
|
+
new_output = json_parse(new_output)
|
882
835
|
elif type == 'urlencoded':
|
883
|
-
|
884
|
-
|
885
|
-
self.assert_new_and_stored_output(exchange,
|
836
|
+
stored_output = self.urlencoded_to_dict(stored_output)
|
837
|
+
new_output = self.urlencoded_to_dict(new_output)
|
838
|
+
self.assert_new_and_stored_output(exchange, skip_keys, new_output, stored_output)
|
839
|
+
|
840
|
+
def assert_static_response_output(self, exchange, skip_keys, computed_result, stored_result):
|
841
|
+
self.assert_new_and_stored_output(exchange, skip_keys, computed_result, stored_result, False)
|
886
842
|
|
887
843
|
def sanitize_data_input(self, input):
|
888
844
|
# remove nulls and replace with unefined instead
|
889
845
|
if input is None:
|
890
846
|
return None
|
891
|
-
|
847
|
+
new_input = []
|
892
848
|
for i in range(0, len(input)):
|
893
849
|
current = input[i]
|
894
|
-
if current
|
895
|
-
|
850
|
+
if is_null_value(current):
|
851
|
+
new_input.append(None)
|
896
852
|
else:
|
897
|
-
|
898
|
-
return
|
853
|
+
new_input.append(current)
|
854
|
+
return new_input
|
899
855
|
|
900
|
-
async def test_method_statically(self, exchange, method
|
856
|
+
async def test_method_statically(self, exchange, method, data, type, skip_keys):
|
901
857
|
output = None
|
902
|
-
|
858
|
+
request_url = None
|
903
859
|
try:
|
904
860
|
await call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
|
905
861
|
except Exception as e:
|
906
862
|
if not (isinstance(e, NetworkError)):
|
907
|
-
# if it's not a network error, it means our request was not created succesfully
|
908
|
-
# so we might have an error in the request creation
|
909
863
|
raise e
|
910
864
|
output = exchange.last_request_body
|
911
|
-
|
865
|
+
request_url = exchange.last_request_url
|
912
866
|
try:
|
913
|
-
|
914
|
-
self.
|
867
|
+
call_output = exchange.safe_value(data, 'output')
|
868
|
+
self.assert_static_request_output(exchange, type, skip_keys, data['url'], request_url, call_output, output)
|
915
869
|
except Exception as e:
|
916
|
-
self.
|
917
|
-
|
918
|
-
dump(
|
919
|
-
|
920
|
-
def init_offline_exchange(self, exchangeName: str):
|
921
|
-
markets = self.load_markets_from_file(exchangeName)
|
922
|
-
return init_exchange(exchangeName, {'markets': markets, 'rateLimit': 1, 'httpsProxy': 'http://fake:8080', 'apiKey': 'key', 'secret': 'secretsecret', 'password': 'password', 'uid': 'uid', 'accounts': [{'id': 'myAccount'}], 'options': {'enableUnifiedAccount': True, 'enableUnifiedMargin': False, 'accessToken': 'token', 'expires': 999999999999999, 'leverageBrackets': {}}})
|
870
|
+
self.request_tests_failed = True
|
871
|
+
error_message = '[' + self.lang + '][STATIC_REQUEST_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
|
872
|
+
dump(error_message)
|
923
873
|
|
924
|
-
async def
|
874
|
+
async def test_response_statically(self, exchange, method, skip_keys, data):
|
875
|
+
expected_result = exchange.safe_value(data, 'parsedResponse')
|
876
|
+
mocked_exchange = set_fetch_response(exchange, data['httpResponse'])
|
877
|
+
try:
|
878
|
+
unified_result = await call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
|
879
|
+
self.assert_static_response_output(mocked_exchange, skip_keys, unified_result, expected_result)
|
880
|
+
except Exception as e:
|
881
|
+
self.request_tests_failed = True
|
882
|
+
error_message = '[' + self.lang + '][STATIC_RESPONSE_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
|
883
|
+
dump(error_message)
|
884
|
+
set_fetch_response(exchange, None) # reset state
|
885
|
+
|
886
|
+
def init_offline_exchange(self, exchange_name):
|
887
|
+
markets = self.load_markets_from_file(exchange_name)
|
888
|
+
currencies = self.load_currencies_from_file(exchange_name)
|
889
|
+
exchange = init_exchange(exchange_name, {
|
890
|
+
'markets': markets,
|
891
|
+
'enableRateLimit': False,
|
892
|
+
'rateLimit': 1,
|
893
|
+
'httpsProxy': 'http://fake:8080',
|
894
|
+
'apiKey': 'key',
|
895
|
+
'secret': 'secretsecret',
|
896
|
+
'password': 'password',
|
897
|
+
'uid': 'uid',
|
898
|
+
'accounts': [{
|
899
|
+
'id': 'myAccount',
|
900
|
+
}],
|
901
|
+
'options': {
|
902
|
+
'enableUnifiedAccount': True,
|
903
|
+
'enableUnifiedMargin': False,
|
904
|
+
'accessToken': 'token',
|
905
|
+
'expires': 999999999999999,
|
906
|
+
'leverageBrackets': {},
|
907
|
+
},
|
908
|
+
})
|
909
|
+
exchange.currencies = currencies # not working in python if assigned in the config dict
|
910
|
+
return exchange
|
911
|
+
|
912
|
+
async def test_exchange_request_statically(self, exchange_name, exchange_data, test_name=None):
|
925
913
|
# instantiate the exchange and make sure that we sink the requests to avoid an actual request
|
926
|
-
exchange = self.init_offline_exchange(
|
927
|
-
methods = exchange.safe_value(
|
928
|
-
|
929
|
-
for i in range(0, len(
|
930
|
-
method =
|
914
|
+
exchange = self.init_offline_exchange(exchange_name)
|
915
|
+
methods = exchange.safe_value(exchange_data, 'methods', {})
|
916
|
+
methods_names = list(methods.keys())
|
917
|
+
for i in range(0, len(methods_names)):
|
918
|
+
method = methods_names[i]
|
919
|
+
results = methods[method]
|
920
|
+
for j in range(0, len(results)):
|
921
|
+
result = results[j]
|
922
|
+
description = exchange.safe_value(result, 'description')
|
923
|
+
if (test_name is not None) and (test_name != description):
|
924
|
+
continue
|
925
|
+
type = exchange.safe_string(exchange_data, 'outputType')
|
926
|
+
skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
|
927
|
+
await self.test_method_statically(exchange, method, result, type, skip_keys)
|
928
|
+
await close(exchange)
|
929
|
+
|
930
|
+
async def test_exchange_response_statically(self, exchange_name, exchange_data, test_name=None):
|
931
|
+
exchange = self.init_offline_exchange(exchange_name)
|
932
|
+
methods = exchange.safe_value(exchange_data, 'methods', {})
|
933
|
+
options = exchange.safe_value(exchange_data, 'options', {})
|
934
|
+
exchange.options = exchange.deep_extend(exchange.options, options) # custom options to be used in the tests
|
935
|
+
methods_names = list(methods.keys())
|
936
|
+
for i in range(0, len(methods_names)):
|
937
|
+
method = methods_names[i]
|
931
938
|
results = methods[method]
|
932
939
|
for j in range(0, len(results)):
|
933
940
|
result = results[j]
|
934
941
|
description = exchange.safe_value(result, 'description')
|
935
|
-
if (
|
942
|
+
if (test_name is not None) and (test_name != description):
|
936
943
|
continue
|
937
|
-
|
938
|
-
|
939
|
-
await self.test_method_statically(exchange, method, result, type, skipKeys)
|
944
|
+
skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
|
945
|
+
await self.test_response_statically(exchange, method, skip_keys, result)
|
940
946
|
await close(exchange)
|
941
947
|
|
942
|
-
def get_number_of_tests_from_exchange(self, exchange,
|
948
|
+
def get_number_of_tests_from_exchange(self, exchange, exchange_data):
|
943
949
|
sum = 0
|
944
|
-
methods =
|
945
|
-
|
946
|
-
for i in range(0, len(
|
947
|
-
method =
|
950
|
+
methods = exchange_data['methods']
|
951
|
+
methods_names = list(methods.keys())
|
952
|
+
for i in range(0, len(methods_names)):
|
953
|
+
method = methods_names[i]
|
948
954
|
results = methods[method]
|
949
|
-
|
950
|
-
sum = exchange.sum(sum,
|
955
|
+
results_length = len(results)
|
956
|
+
sum = exchange.sum(sum, results_length)
|
951
957
|
return sum
|
952
958
|
|
953
|
-
async def
|
954
|
-
|
955
|
-
|
956
|
-
|
959
|
+
async def run_static_request_tests(self, target_exchange=None, test_name=None):
|
960
|
+
await self.run_static_tests('request', target_exchange, test_name)
|
961
|
+
|
962
|
+
async def run_static_tests(self, type, target_exchange=None, test_name=None):
|
963
|
+
folder = self.root_dir + './ts/src/test/static/' + type + '/'
|
964
|
+
static_data = self.load_static_data(folder, target_exchange)
|
965
|
+
exchanges = list(static_data.keys())
|
966
|
+
exchange = init_exchange('Exchange', {}) # tmp to do the calculations until we have the ast-transpiler transpiling this code
|
957
967
|
promises = []
|
958
968
|
sum = 0
|
959
|
-
if
|
960
|
-
dump(
|
961
|
-
if
|
962
|
-
dump(
|
969
|
+
if target_exchange:
|
970
|
+
dump('Exchange to test: ' + target_exchange)
|
971
|
+
if test_name:
|
972
|
+
dump('Testing only: ' + test_name)
|
963
973
|
for i in range(0, len(exchanges)):
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
sum = exchange.sum(sum,
|
968
|
-
|
974
|
+
exchange_name = exchanges[i]
|
975
|
+
exchange_data = static_data[exchange_name]
|
976
|
+
number_of_tests = self.get_number_of_tests_from_exchange(exchange, exchange_data)
|
977
|
+
sum = exchange.sum(sum, number_of_tests)
|
978
|
+
if type == 'request':
|
979
|
+
promises.append(self.test_exchange_request_statically(exchange_name, exchange_data, test_name))
|
980
|
+
else:
|
981
|
+
promises.append(self.test_exchange_response_statically(exchange_name, exchange_data, test_name))
|
969
982
|
await asyncio.gather(*promises)
|
970
|
-
if self.
|
983
|
+
if self.request_tests_failed or self.response_tests_failed:
|
971
984
|
exit_script(1)
|
972
985
|
else:
|
973
|
-
|
974
|
-
dump(
|
986
|
+
success_message = '[' + self.lang + '][TEST_SUCCESS] ' + str(sum) + ' static ' + type + ' tests passed.'
|
987
|
+
dump(success_message)
|
975
988
|
exit_script(0)
|
976
989
|
|
990
|
+
async def run_static_response_tests(self, exchange_name=None, test=None):
|
991
|
+
# -----------------------------------------------------------------------------
|
992
|
+
# --- Init of mockResponses tests functions------------------------------------
|
993
|
+
# -----------------------------------------------------------------------------
|
994
|
+
await self.run_static_tests('response', exchange_name, test)
|
995
|
+
|
977
996
|
async def run_broker_id_tests(self):
|
978
997
|
# -----------------------------------------------------------------------------
|
979
998
|
# --- Init of brokerId tests functions-----------------------------------------
|
980
999
|
# -----------------------------------------------------------------------------
|
981
|
-
promises = [
|
982
|
-
self.test_binance(),
|
983
|
-
self.test_okx(),
|
984
|
-
self.test_cryptocom(),
|
985
|
-
self.test_bybit(),
|
986
|
-
self.test_kucoin(),
|
987
|
-
self.test_kucoinfutures(),
|
988
|
-
self.test_bitget(),
|
989
|
-
self.test_mexc(),
|
990
|
-
self.test_huobi(),
|
991
|
-
self.test_woo(),
|
992
|
-
self.test_bitmart(),
|
993
|
-
self.test_coinex()
|
994
|
-
]
|
1000
|
+
promises = [self.test_binance(), self.test_okx(), self.test_cryptocom(), self.test_bybit(), self.test_kucoin(), self.test_kucoinfutures(), self.test_bitget(), self.test_mexc(), self.test_huobi(), self.test_woo(), self.test_bitmart(), self.test_coinex()]
|
995
1001
|
await asyncio.gather(*promises)
|
996
|
-
|
997
|
-
dump(
|
1002
|
+
success_message = '[' + self.lang + '][TEST_SUCCESS] brokerId tests passed.'
|
1003
|
+
dump(success_message)
|
998
1004
|
exit_script(0)
|
999
1005
|
|
1000
1006
|
async def test_binance(self):
|
1001
1007
|
binance = self.init_offline_exchange('binance')
|
1002
|
-
|
1003
|
-
|
1008
|
+
spot_id = 'x-R4BD3S82'
|
1009
|
+
spot_order_request = None
|
1004
1010
|
try:
|
1005
1011
|
await binance.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1006
1012
|
except Exception as e:
|
1007
|
-
|
1008
|
-
|
1009
|
-
assert
|
1010
|
-
|
1011
|
-
|
1013
|
+
spot_order_request = self.urlencoded_to_dict(binance.last_request_body)
|
1014
|
+
client_order_id = spot_order_request['newClientOrderId']
|
1015
|
+
assert client_order_id.startswith(spot_id), 'spot clientOrderId does not start with spotId'
|
1016
|
+
swap_id = 'x-xcKtGhcu'
|
1017
|
+
swap_order_request = None
|
1012
1018
|
try:
|
1013
1019
|
await binance.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
|
1014
1020
|
except Exception as e:
|
1015
|
-
|
1016
|
-
|
1021
|
+
swap_order_request = self.urlencoded_to_dict(binance.last_request_body)
|
1022
|
+
swap_inverse_order_request = None
|
1017
1023
|
try:
|
1018
1024
|
await binance.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
|
1019
1025
|
except Exception as e:
|
1020
|
-
|
1021
|
-
|
1022
|
-
assert
|
1023
|
-
|
1024
|
-
assert
|
1026
|
+
swap_inverse_order_request = self.urlencoded_to_dict(binance.last_request_body)
|
1027
|
+
client_order_id_spot = swap_order_request['newClientOrderId']
|
1028
|
+
assert client_order_id_spot.startswith(swap_id), 'swap clientOrderId does not start with swapId'
|
1029
|
+
client_order_id_inverse = swap_inverse_order_request['newClientOrderId']
|
1030
|
+
assert client_order_id_inverse.startswith(swap_id), 'swap clientOrderIdInverse does not start with swapId'
|
1025
1031
|
await close(binance)
|
1026
1032
|
|
1027
1033
|
async def test_okx(self):
|
1028
1034
|
okx = self.init_offline_exchange('okx')
|
1029
1035
|
id = 'e847386590ce4dBC'
|
1030
|
-
|
1036
|
+
spot_order_request = None
|
1031
1037
|
try:
|
1032
1038
|
await okx.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1033
1039
|
except Exception as e:
|
1034
|
-
|
1035
|
-
|
1036
|
-
assert
|
1037
|
-
assert
|
1038
|
-
|
1040
|
+
spot_order_request = json_parse(okx.last_request_body)
|
1041
|
+
client_order_id = spot_order_request[0]['clOrdId'] # returns order inside array
|
1042
|
+
assert client_order_id.startswith(id), 'spot clientOrderId does not start with id'
|
1043
|
+
assert spot_order_request[0]['tag'] == id, 'id different from spot tag'
|
1044
|
+
swap_order_request = None
|
1039
1045
|
try:
|
1040
1046
|
await okx.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
|
1041
1047
|
except Exception as e:
|
1042
|
-
|
1043
|
-
|
1044
|
-
assert
|
1045
|
-
assert
|
1048
|
+
swap_order_request = json_parse(okx.last_request_body)
|
1049
|
+
client_order_id_spot = swap_order_request[0]['clOrdId']
|
1050
|
+
assert client_order_id_spot.startswith(id), 'swap clientOrderId does not start with id'
|
1051
|
+
assert swap_order_request[0]['tag'] == id, 'id different from swap tag'
|
1046
1052
|
await close(okx)
|
1047
1053
|
|
1048
1054
|
async def test_cryptocom(self):
|
@@ -1059,142 +1065,144 @@ class testMainClass(baseMainTestClass):
|
|
1059
1065
|
|
1060
1066
|
async def test_bybit(self):
|
1061
1067
|
bybit = self.init_offline_exchange('bybit')
|
1062
|
-
|
1068
|
+
req_headers = None
|
1063
1069
|
id = 'CCXT'
|
1064
1070
|
assert bybit.options['brokerId'] == id, 'id not in options'
|
1065
1071
|
try:
|
1066
1072
|
await bybit.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1067
1073
|
except Exception as e:
|
1068
1074
|
# we expect an error here, we're only interested in the headers
|
1069
|
-
|
1070
|
-
assert
|
1075
|
+
req_headers = bybit.last_request_headers
|
1076
|
+
assert req_headers['Referer'] == id, 'id not in headers'
|
1071
1077
|
await close(bybit)
|
1072
1078
|
|
1073
1079
|
async def test_kucoin(self):
|
1074
1080
|
kucoin = self.init_offline_exchange('kucoin')
|
1075
|
-
|
1081
|
+
req_headers = None
|
1076
1082
|
assert kucoin.options['partner']['spot']['id'] == 'ccxt', 'id not in options'
|
1077
1083
|
assert kucoin.options['partner']['spot']['key'] == '9e58cc35-5b5e-4133-92ec-166e3f077cb8', 'key not in options'
|
1078
1084
|
try:
|
1079
1085
|
await kucoin.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1080
1086
|
except Exception as e:
|
1081
1087
|
# we expect an error here, we're only interested in the headers
|
1082
|
-
|
1088
|
+
req_headers = kucoin.last_request_headers
|
1083
1089
|
id = 'ccxt'
|
1084
|
-
assert
|
1090
|
+
assert req_headers['KC-API-PARTNER'] == id, 'id not in headers'
|
1085
1091
|
await close(kucoin)
|
1086
1092
|
|
1087
1093
|
async def test_kucoinfutures(self):
|
1088
1094
|
kucoin = self.init_offline_exchange('kucoinfutures')
|
1089
|
-
|
1095
|
+
req_headers = None
|
1090
1096
|
id = 'ccxtfutures'
|
1091
1097
|
assert kucoin.options['partner']['future']['id'] == id, 'id not in options'
|
1092
1098
|
assert kucoin.options['partner']['future']['key'] == '1b327198-f30c-4f14-a0ac-918871282f15', 'key not in options'
|
1093
1099
|
try:
|
1094
1100
|
await kucoin.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
|
1095
1101
|
except Exception as e:
|
1096
|
-
|
1097
|
-
assert
|
1102
|
+
req_headers = kucoin.last_request_headers
|
1103
|
+
assert req_headers['KC-API-PARTNER'] == id, 'id not in headers'
|
1098
1104
|
await close(kucoin)
|
1099
1105
|
|
1100
1106
|
async def test_bitget(self):
|
1101
1107
|
bitget = self.init_offline_exchange('bitget')
|
1102
|
-
|
1108
|
+
req_headers = None
|
1103
1109
|
id = 'p4sve'
|
1104
1110
|
assert bitget.options['broker'] == id, 'id not in options'
|
1105
1111
|
try:
|
1106
1112
|
await bitget.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1107
1113
|
except Exception as e:
|
1108
|
-
|
1109
|
-
assert
|
1114
|
+
req_headers = bitget.last_request_headers
|
1115
|
+
assert req_headers['X-CHANNEL-API-CODE'] == id, 'id not in headers'
|
1110
1116
|
await close(bitget)
|
1111
1117
|
|
1112
1118
|
async def test_mexc(self):
|
1113
1119
|
mexc = self.init_offline_exchange('mexc')
|
1114
|
-
|
1120
|
+
req_headers = None
|
1115
1121
|
id = 'CCXT'
|
1116
1122
|
assert mexc.options['broker'] == id, 'id not in options'
|
1117
1123
|
await mexc.load_markets()
|
1118
1124
|
try:
|
1119
1125
|
await mexc.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1120
1126
|
except Exception as e:
|
1121
|
-
|
1122
|
-
assert
|
1127
|
+
req_headers = mexc.last_request_headers
|
1128
|
+
assert req_headers['source'] == id, 'id not in headers'
|
1123
1129
|
await close(mexc)
|
1124
1130
|
|
1125
1131
|
async def test_huobi(self):
|
1126
1132
|
huobi = self.init_offline_exchange('huobi')
|
1127
1133
|
# spot test
|
1128
1134
|
id = 'AA03022abc'
|
1129
|
-
|
1135
|
+
spot_order_request = None
|
1130
1136
|
try:
|
1131
1137
|
await huobi.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1132
1138
|
except Exception as e:
|
1133
|
-
|
1134
|
-
|
1135
|
-
assert
|
1139
|
+
spot_order_request = json_parse(huobi.last_request_body)
|
1140
|
+
client_order_id = spot_order_request['client-order-id']
|
1141
|
+
assert client_order_id.startswith(id), 'spot clientOrderId does not start with id'
|
1136
1142
|
# swap test
|
1137
|
-
|
1143
|
+
swap_order_request = None
|
1138
1144
|
try:
|
1139
1145
|
await huobi.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
|
1140
1146
|
except Exception as e:
|
1141
|
-
|
1142
|
-
|
1147
|
+
swap_order_request = json_parse(huobi.last_request_body)
|
1148
|
+
swap_inverse_order_request = None
|
1143
1149
|
try:
|
1144
1150
|
await huobi.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
|
1145
1151
|
except Exception as e:
|
1146
|
-
|
1147
|
-
|
1148
|
-
assert
|
1149
|
-
|
1150
|
-
assert
|
1152
|
+
swap_inverse_order_request = json_parse(huobi.last_request_body)
|
1153
|
+
client_order_id_spot = swap_order_request['channel_code']
|
1154
|
+
assert client_order_id_spot.startswith(id), 'swap channel_code does not start with id'
|
1155
|
+
client_order_id_inverse = swap_inverse_order_request['channel_code']
|
1156
|
+
assert client_order_id_inverse.startswith(id), 'swap inverse channel_code does not start with id'
|
1151
1157
|
await close(huobi)
|
1152
1158
|
|
1153
1159
|
async def test_woo(self):
|
1154
1160
|
woo = self.init_offline_exchange('woo')
|
1155
1161
|
# spot test
|
1156
1162
|
id = 'bc830de7-50f3-460b-9ee0-f430f83f9dad'
|
1157
|
-
|
1163
|
+
spot_order_request = None
|
1158
1164
|
try:
|
1159
1165
|
await woo.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1160
1166
|
except Exception as e:
|
1161
|
-
|
1162
|
-
|
1163
|
-
assert
|
1167
|
+
spot_order_request = self.urlencoded_to_dict(woo.last_request_body)
|
1168
|
+
broker_id = spot_order_request['broker_id']
|
1169
|
+
assert broker_id.startswith(id), 'broker_id does not start with id'
|
1164
1170
|
# swap test
|
1165
|
-
|
1171
|
+
stop_order_request = None
|
1166
1172
|
try:
|
1167
|
-
await woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {
|
1173
|
+
await woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {
|
1174
|
+
'stopPrice': 30000,
|
1175
|
+
})
|
1168
1176
|
except Exception as e:
|
1169
|
-
|
1170
|
-
|
1171
|
-
assert
|
1177
|
+
stop_order_request = json_parse(woo.last_request_body)
|
1178
|
+
client_order_id_spot = stop_order_request['brokerId']
|
1179
|
+
assert client_order_id_spot.startswith(id), 'brokerId does not start with id'
|
1172
1180
|
await close(woo)
|
1173
1181
|
|
1174
1182
|
async def test_bitmart(self):
|
1175
1183
|
bitmart = self.init_offline_exchange('bitmart')
|
1176
|
-
|
1184
|
+
req_headers = None
|
1177
1185
|
id = 'CCXTxBitmart000'
|
1178
1186
|
assert bitmart.options['brokerId'] == id, 'id not in options'
|
1179
1187
|
await bitmart.load_markets()
|
1180
1188
|
try:
|
1181
1189
|
await bitmart.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1182
1190
|
except Exception as e:
|
1183
|
-
|
1184
|
-
assert
|
1191
|
+
req_headers = bitmart.last_request_headers
|
1192
|
+
assert req_headers['X-BM-BROKER-ID'] == id, 'id not in headers'
|
1185
1193
|
await close(bitmart)
|
1186
1194
|
|
1187
1195
|
async def test_coinex(self):
|
1188
1196
|
exchange = self.init_offline_exchange('coinex')
|
1189
1197
|
id = 'x-167673045'
|
1190
1198
|
assert exchange.options['brokerId'] == id, 'id not in options'
|
1191
|
-
|
1199
|
+
spot_order_request = None
|
1192
1200
|
try:
|
1193
1201
|
await exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
|
1194
1202
|
except Exception as e:
|
1195
|
-
|
1196
|
-
|
1197
|
-
assert
|
1203
|
+
spot_order_request = json_parse(exchange.last_request_body)
|
1204
|
+
client_order_id = spot_order_request['client_id']
|
1205
|
+
assert client_order_id.startswith(id), 'clientOrderId does not start with id'
|
1198
1206
|
await close(exchange)
|
1199
1207
|
|
1200
1208
|
# ***** AUTO-TRANSPILER-END *****
|