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