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.

Files changed (251) hide show
  1. ccxt/__init__.py +2 -2
  2. ccxt/abstract/binance.py +1 -0
  3. ccxt/abstract/binancecoinm.py +1 -0
  4. ccxt/abstract/binanceus.py +1 -0
  5. ccxt/abstract/binanceusdm.py +1 -0
  6. ccxt/abstract/bitbank.py +1 -0
  7. ccxt/abstract/coinbase.py +2 -0
  8. ccxt/abstract/htx.py +3 -0
  9. ccxt/abstract/huobi.py +3 -0
  10. ccxt/abstract/huobipro.py +3 -0
  11. ccxt/abstract/okex.py +3 -1
  12. ccxt/abstract/okex5.py +3 -1
  13. ccxt/abstract/okx.py +3 -1
  14. ccxt/ace.py +23 -23
  15. ccxt/alpaca.py +8 -8
  16. ccxt/ascendex.py +26 -26
  17. ccxt/async_support/__init__.py +2 -2
  18. ccxt/async_support/ace.py +23 -23
  19. ccxt/async_support/alpaca.py +8 -8
  20. ccxt/async_support/ascendex.py +26 -26
  21. ccxt/async_support/base/exchange.py +4 -2216
  22. ccxt/async_support/bigone.py +21 -24
  23. ccxt/async_support/binance.py +61 -54
  24. ccxt/async_support/bingx.py +28 -28
  25. ccxt/async_support/bit2c.py +9 -9
  26. ccxt/async_support/bitbank.py +11 -10
  27. ccxt/async_support/bitbns.py +11 -11
  28. ccxt/async_support/bitfinex.py +15 -15
  29. ccxt/async_support/bitfinex2.py +22 -22
  30. ccxt/async_support/bitflyer.py +13 -13
  31. ccxt/async_support/bitforex.py +10 -10
  32. ccxt/async_support/bitget.py +44 -44
  33. ccxt/async_support/bithumb.py +9 -9
  34. ccxt/async_support/bitmart.py +85 -104
  35. ccxt/async_support/bitmex.py +27 -27
  36. ccxt/async_support/bitopro.py +18 -18
  37. ccxt/async_support/bitpanda.py +18 -18
  38. ccxt/async_support/bitrue.py +14 -14
  39. ccxt/async_support/bitso.py +17 -17
  40. ccxt/async_support/bitstamp.py +17 -17
  41. ccxt/async_support/bittrex.py +22 -24
  42. ccxt/async_support/bitvavo.py +15 -15
  43. ccxt/async_support/bl3p.py +4 -4
  44. ccxt/async_support/blockchaincom.py +17 -17
  45. ccxt/async_support/btcalpha.py +14 -14
  46. ccxt/async_support/btcbox.py +9 -9
  47. ccxt/async_support/btcmarkets.py +17 -17
  48. ccxt/async_support/btcturk.py +9 -9
  49. ccxt/async_support/bybit.py +46 -46
  50. ccxt/async_support/cex.py +10 -10
  51. ccxt/async_support/coinbase.py +69 -25
  52. ccxt/async_support/coinbasepro.py +19 -19
  53. ccxt/async_support/coincheck.py +10 -10
  54. ccxt/async_support/coinex.py +57 -66
  55. ccxt/async_support/coinlist.py +22 -22
  56. ccxt/async_support/coinmate.py +10 -10
  57. ccxt/async_support/coinone.py +10 -10
  58. ccxt/async_support/coinsph.py +17 -17
  59. ccxt/async_support/coinspot.py +5 -5
  60. ccxt/async_support/cryptocom.py +27 -27
  61. ccxt/async_support/currencycom.py +18 -18
  62. ccxt/async_support/delta.py +21 -21
  63. ccxt/async_support/deribit.py +24 -24
  64. ccxt/async_support/digifinex.py +35 -35
  65. ccxt/async_support/exmo.py +19 -19
  66. ccxt/async_support/gate.py +38 -38
  67. ccxt/async_support/gemini.py +11 -11
  68. ccxt/async_support/hitbtc.py +27 -27
  69. ccxt/async_support/hollaex.py +19 -19
  70. ccxt/async_support/htx.py +47 -44
  71. ccxt/async_support/huobijp.py +22 -22
  72. ccxt/async_support/idex.py +20 -20
  73. ccxt/async_support/independentreserve.py +9 -9
  74. ccxt/async_support/indodax.py +10 -10
  75. ccxt/async_support/kraken.py +25 -25
  76. ccxt/async_support/krakenfutures.py +17 -17
  77. ccxt/async_support/kucoin.py +27 -27
  78. ccxt/async_support/kucoinfutures.py +20 -20
  79. ccxt/async_support/kuna.py +19 -19
  80. ccxt/async_support/latoken.py +14 -14
  81. ccxt/async_support/lbank.py +18 -18
  82. ccxt/async_support/luno.py +14 -14
  83. ccxt/async_support/lykke.py +12 -12
  84. ccxt/async_support/mercado.py +11 -11
  85. ccxt/async_support/mexc.py +36 -36
  86. ccxt/async_support/ndax.py +18 -18
  87. ccxt/async_support/novadax.py +17 -17
  88. ccxt/async_support/oceanex.py +12 -12
  89. ccxt/async_support/okcoin.py +19 -19
  90. ccxt/async_support/okx.py +48 -45
  91. ccxt/async_support/p2b.py +6 -6
  92. ccxt/async_support/paymium.py +6 -6
  93. ccxt/async_support/phemex.py +57 -57
  94. ccxt/async_support/poloniex.py +31 -30
  95. ccxt/async_support/poloniexfutures.py +16 -16
  96. ccxt/async_support/probit.py +22 -22
  97. ccxt/async_support/tidex.py +15 -15
  98. ccxt/async_support/timex.py +20 -20
  99. ccxt/async_support/tokocrypto.py +16 -16
  100. ccxt/async_support/upbit.py +15 -15
  101. ccxt/async_support/wavesexchange.py +12 -12
  102. ccxt/async_support/wazirx.py +13 -13
  103. ccxt/async_support/whitebit.py +26 -26
  104. ccxt/async_support/woo.py +47 -47
  105. ccxt/async_support/yobit.py +8 -8
  106. ccxt/async_support/zaif.py +10 -10
  107. ccxt/async_support/zonda.py +16 -16
  108. ccxt/base/errors.py +17 -16
  109. ccxt/base/exchange.py +57 -97
  110. ccxt/base/types.py +138 -139
  111. ccxt/bigone.py +21 -24
  112. ccxt/binance.py +61 -54
  113. ccxt/bingx.py +28 -28
  114. ccxt/bit2c.py +9 -9
  115. ccxt/bitbank.py +11 -10
  116. ccxt/bitbns.py +11 -11
  117. ccxt/bitfinex.py +15 -15
  118. ccxt/bitfinex2.py +22 -22
  119. ccxt/bitflyer.py +13 -13
  120. ccxt/bitforex.py +10 -10
  121. ccxt/bitget.py +44 -44
  122. ccxt/bithumb.py +9 -9
  123. ccxt/bitmart.py +85 -104
  124. ccxt/bitmex.py +27 -27
  125. ccxt/bitopro.py +18 -18
  126. ccxt/bitpanda.py +18 -18
  127. ccxt/bitrue.py +14 -14
  128. ccxt/bitso.py +17 -17
  129. ccxt/bitstamp.py +17 -17
  130. ccxt/bittrex.py +22 -24
  131. ccxt/bitvavo.py +15 -15
  132. ccxt/bl3p.py +4 -4
  133. ccxt/blockchaincom.py +17 -17
  134. ccxt/btcalpha.py +14 -14
  135. ccxt/btcbox.py +9 -9
  136. ccxt/btcmarkets.py +17 -17
  137. ccxt/btcturk.py +9 -9
  138. ccxt/bybit.py +46 -46
  139. ccxt/cex.py +10 -10
  140. ccxt/coinbase.py +69 -25
  141. ccxt/coinbasepro.py +19 -19
  142. ccxt/coincheck.py +10 -10
  143. ccxt/coinex.py +57 -66
  144. ccxt/coinlist.py +22 -22
  145. ccxt/coinmate.py +10 -10
  146. ccxt/coinone.py +10 -10
  147. ccxt/coinsph.py +17 -17
  148. ccxt/coinspot.py +5 -5
  149. ccxt/cryptocom.py +27 -27
  150. ccxt/currencycom.py +18 -18
  151. ccxt/delta.py +21 -21
  152. ccxt/deribit.py +24 -24
  153. ccxt/digifinex.py +35 -35
  154. ccxt/exmo.py +19 -19
  155. ccxt/gate.py +38 -38
  156. ccxt/gemini.py +11 -11
  157. ccxt/hitbtc.py +27 -27
  158. ccxt/hollaex.py +19 -19
  159. ccxt/htx.py +47 -44
  160. ccxt/huobijp.py +22 -22
  161. ccxt/idex.py +20 -20
  162. ccxt/independentreserve.py +9 -9
  163. ccxt/indodax.py +10 -10
  164. ccxt/kraken.py +25 -25
  165. ccxt/krakenfutures.py +17 -17
  166. ccxt/kucoin.py +27 -27
  167. ccxt/kucoinfutures.py +20 -20
  168. ccxt/kuna.py +19 -19
  169. ccxt/latoken.py +14 -14
  170. ccxt/lbank.py +18 -18
  171. ccxt/luno.py +14 -14
  172. ccxt/lykke.py +12 -12
  173. ccxt/mercado.py +11 -11
  174. ccxt/mexc.py +36 -36
  175. ccxt/ndax.py +18 -18
  176. ccxt/novadax.py +17 -17
  177. ccxt/oceanex.py +12 -12
  178. ccxt/okcoin.py +19 -19
  179. ccxt/okx.py +48 -45
  180. ccxt/p2b.py +6 -6
  181. ccxt/paymium.py +6 -6
  182. ccxt/phemex.py +57 -57
  183. ccxt/poloniex.py +31 -30
  184. ccxt/poloniexfutures.py +16 -16
  185. ccxt/pro/__init__.py +1 -1
  186. ccxt/pro/alpaca.py +3 -3
  187. ccxt/pro/ascendex.py +2 -2
  188. ccxt/pro/binance.py +9 -9
  189. ccxt/pro/bingx.py +3 -3
  190. ccxt/pro/bitfinex.py +3 -3
  191. ccxt/pro/bitfinex2.py +3 -3
  192. ccxt/pro/bitget.py +3 -3
  193. ccxt/pro/bitmart.py +2 -2
  194. ccxt/pro/bitmex.py +3 -3
  195. ccxt/pro/bitpanda.py +3 -3
  196. ccxt/pro/bitrue.py +2 -2
  197. ccxt/pro/bitstamp.py +2 -2
  198. ccxt/pro/bittrex.py +3 -3
  199. ccxt/pro/bitvavo.py +3 -3
  200. ccxt/pro/blockchaincom.py +2 -2
  201. ccxt/pro/bybit.py +4 -4
  202. ccxt/pro/cex.py +3 -3
  203. ccxt/pro/coinbasepro.py +3 -3
  204. ccxt/pro/coinex.py +2 -2
  205. ccxt/pro/cryptocom.py +5 -5
  206. ccxt/pro/deribit.py +3 -3
  207. ccxt/pro/exmo.py +2 -2
  208. ccxt/pro/gate.py +3 -3
  209. ccxt/pro/gemini.py +2 -2
  210. ccxt/pro/hitbtc.py +4 -4
  211. ccxt/pro/hollaex.py +3 -3
  212. ccxt/pro/htx.py +3 -3
  213. ccxt/pro/idex.py +3 -3
  214. ccxt/pro/kraken.py +7 -7
  215. ccxt/pro/krakenfutures.py +4 -4
  216. ccxt/pro/kucoin.py +3 -3
  217. ccxt/pro/kucoinfutures.py +3 -3
  218. ccxt/pro/mexc.py +3 -3
  219. ccxt/pro/okcoin.py +2 -2
  220. ccxt/pro/okx.py +6 -6
  221. ccxt/pro/phemex.py +3 -3
  222. ccxt/pro/poloniex.py +3 -3
  223. ccxt/pro/poloniexfutures.py +3 -3
  224. ccxt/pro/probit.py +3 -3
  225. ccxt/pro/wazirx.py +3 -3
  226. ccxt/pro/whitebit.py +3 -3
  227. ccxt/pro/woo.py +2 -2
  228. ccxt/probit.py +22 -22
  229. ccxt/test/base/test_shared_methods.py +3 -3
  230. ccxt/test/test_async.py +543 -535
  231. ccxt/test/test_sync.py +542 -534
  232. ccxt/tidex.py +15 -15
  233. ccxt/timex.py +20 -20
  234. ccxt/tokocrypto.py +16 -16
  235. ccxt/upbit.py +15 -15
  236. ccxt/wavesexchange.py +12 -12
  237. ccxt/wazirx.py +13 -13
  238. ccxt/whitebit.py +26 -26
  239. ccxt/woo.py +47 -47
  240. ccxt/yobit.py +8 -8
  241. ccxt/zaif.py +10 -10
  242. ccxt/zonda.py +16 -16
  243. {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/METADATA +10 -8
  244. ccxt-4.1.56.dist-info/RECORD +449 -0
  245. ccxt/async_support/bitstamp1.py +0 -402
  246. ccxt/async_support/lbank2.py +0 -2620
  247. ccxt/bitstamp1.py +0 -402
  248. ccxt/lbank2.py +0 -2619
  249. ccxt-4.1.54.dist-info/RECORD +0 -453
  250. {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/WHEEL +0 -0
  251. {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
- idTests = False
28
- staticTests = False
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
- # ***** AUTO-TRANSPILER-START *****
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.idTests = get_cli_arg_value('--idTests')
221
- self.staticTests = get_cli_arg_value('--static')
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.privateTest = get_cli_arg_value('--private')
226
- self.privateTestOnly = get_cli_arg_value('--privateOnly')
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, exchangeId, symbol):
244
+ async def init(self, exchange_id, symbol):
230
245
  self.parse_cli_args()
231
- if self.staticTests:
232
- await self.run_static_tests(exchangeId, symbol) # symbol here is the testname
246
+ if self.response_tests:
247
+ await self.run_static_response_tests(exchange_id, symbol)
233
248
  return
234
- if self.idTests:
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
- symbolStr = symbol is not symbol if None else 'all'
238
- dump('\nTESTING ', ext, {'exchange': exchangeId, 'symbol': symbolStr}, '\n')
239
- exchangeArgs = {
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(exchangeId, exchangeArgs)
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.testFiles = {}
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
- exchangeId = exchange.id
259
- keysGlobal = rootDir + 'keys.json'
260
- keysLocal = rootDir + 'keys.local.json'
261
- keysGlobalExists = io_file_exists(keysGlobal)
262
- keysLocalExists = io_file_exists(keysLocal)
263
- globalSettings = io_file_read(keysGlobal) if keysGlobalExists else {}
264
- localSettings = io_file_read(keysLocal) if keysLocalExists else {}
265
- allSettings = exchange.deep_extend(globalSettings, localSettings)
266
- exchangeSettings = exchange.safe_value(allSettings, exchangeId, {})
267
- if exchangeSettings:
268
- settingKeys = list(exchangeSettings.keys())
269
- for i in range(0, len(settingKeys)):
270
- key = settingKeys[i]
271
- if exchangeSettings[key]:
272
- finalValue = None
273
- if isinstance(exchangeSettings[key], dict):
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
- finalValue = exchange.deep_extend(existing, exchangeSettings[key])
296
+ final_value = exchange.deep_extend(existing, exchange_settings[key])
276
297
  else:
277
- finalValue = exchangeSettings[key]
278
- set_exchange_prop(exchange, key, finalValue)
298
+ final_value = exchange_settings[key]
299
+ set_exchange_prop(exchange, key, final_value)
279
300
  # credentials
280
- reqCreds = get_exchange_prop(exchange, 're' + 'quiredCredentials') # dont glue the r-e-q-u-i-r-e phrase, because leads to messed up transpilation
281
- objkeys = list(reqCreds.keys())
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
- isRequired = reqCreds[credential]
285
- if isRequired and get_exchange_prop(exchange, credential) is None:
286
- fullKey = exchangeId + '_' + credential
287
- credentialEnvName = fullKey.upper() # example: KRAKEN_APIKEY
288
- credentialValue = envVars[credentialEnvName] if (credentialEnvName in envVars) else None
289
- if credentialValue:
290
- set_exchange_prop(exchange, credential, credentialValue)
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
- skippedFile = rootDirForSkips + 'skip-tests.json'
293
- skippedSettings = io_file_read(skippedFile)
294
- skippedSettingsForExchange = exchange.safe_value(skippedSettings, exchangeId, {})
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(skippedSettingsForExchange, 'timeout')
317
+ timeout = exchange.safe_value(skipped_settings_for_exchange, 'timeout')
297
318
  if timeout is not None:
298
319
  exchange.timeout = timeout
299
- exchange.httpsProxy = exchange.safe_string(skippedSettingsForExchange, 'httpsProxy')
300
- self.skippedMethods = exchange.safe_value(skippedSettingsForExchange, 'skipMethods', {})
301
- self.checkedPublicTests = {}
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
- missingSpace = size - len(message) - 0 # - 0 is added just to trick transpile to treat the .length string for php
307
- if missingSpace > 0:
308
- for i in range(0, missingSpace):
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, methodName, exchange, args, isPublic):
313
- isLoadMarkets = (methodName == 'loadMarkets')
314
- methodNameInTest = get_test_name(methodName)
315
- # if self 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)
316
- if not isPublic and (methodNameInTest in self.checkedPublicTests) and (methodName != 'fetchCurrencies'):
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
- skipMessage = None
319
- if not isLoadMarkets and (not(methodName in exchange.has) or not exchange.has[methodName]):
320
- skipMessage = '[INFO:UNSUPPORTED_TEST]' # keep it aligned with the longest message
321
- elif (methodName in self.skippedMethods) and (isinstance(self.skippedMethods[methodName], str)):
322
- skipMessage = '[INFO:SKIPPED_TEST]'
323
- elif not (methodNameInTest in self.testFiles):
324
- skipMessage = '[INFO:UNIMPLEMENTED_TEST]'
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 isLoadMarkets:
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 skipMessage:
350
+ if skip_message:
329
351
  if self.info:
330
- dump(self.add_padding(skipMessage, 25), exchange.id, methodNameInTest)
352
+ dump(self.add_padding(skip_message, 25), exchange.id, method_name_in_test)
331
353
  return
332
354
  if self.info:
333
- argsStringified = '(' + ','.join(args) + ')'
334
- dump(self.add_padding('[INFO:TESTING]', 25), exchange.id, methodNameInTest, argsStringified)
335
- skippedProperties = exchange.safe_value(self.skippedMethods, methodName, {})
336
- await call_method(self.testFiles, methodNameInTest, exchange, skippedProperties, args)
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 isPublic:
339
- self.checkedPublicTests[methodNameInTest] = True
360
+ if is_public:
361
+ self.checked_public_tests[method_name_in_test] = True
340
362
 
341
- async def test_safe(self, methodName, exchange, args=[], isPublic=False):
342
- # `testSafe` method does not raise an exception, instead mutes it.
343
- # The reason we mute the thrown exceptions here is because if self test is part
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 self fact,
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
- maxRetries = 3
353
- argsStringified = exchange.json(args) # args.join() breaks when we provide a list of symbols | "args.toString()" breaks bcz of "array to string conversion"
354
- for i in range(0, maxRetries):
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(methodName, exchange, args, isPublic)
378
+ await self.test_method(method_name, exchange, args, is_public)
357
379
  return True
358
380
  except Exception as e:
359
- isAuthError = (isinstance(e, AuthenticationError))
360
- isNotSupported = (isinstance(e, NotSupported))
361
- isNetworkError = (isinstance(e, NetworkError)) # includes "DDoSProtection", "RateLimitExceeded", "RequestTimeout", "ExchangeNotAvailable", "isOperationFailed", "InvalidNonce", ...
362
- isExchangeNotAvailable = (isinstance(e, ExchangeNotAvailable))
363
- isOnMaintenance = (isinstance(e, OnMaintenance))
364
- tempFailure = isNetworkError and (not isExchangeNotAvailable or isOnMaintenance) # we do not mute specifically "ExchangeNotAvailable" excetpion(but its subtype "OnMaintenance" can be muted)
365
- if tempFailure:
366
- # if last retry was gone with same `tempFailure` error, then let's eventually return False
367
- if i == maxRetries - 1:
368
- dump('[TEST_WARNING]', 'Method could not be tested due to a repeated Network/Availability issues', ' | ', exchange.id, methodName, argsStringified)
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
- # If public test faces authentication error, we don't break(see comments under `testSafe` method)
377
- elif isPublic and isAuthError:
378
- # 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
379
- if methodName == 'loadMarkets':
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, methodName, argsStringified)
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 isNotSupported:
386
- dump('[NOT_SUPPORTED]', exchange.id, methodName, argsStringified)
387
- return True # why consider not supported failed test?
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, methodName, argsStringified)
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
- isSpot = market['spot']
408
- if isSpot:
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.publicTests = tests
418
- testNames = list(tests.keys())
438
+ self.public_tests = tests
439
+ test_names = list(tests.keys())
419
440
  promises = []
420
- for i in range(0, len(testNames)):
421
- testName = testNames[i]
422
- testArgs = tests[testName]
423
- promises.append(self.test_safe(testName, exchange, testArgs, True))
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.append(testThrottle())
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(testNames)):
451
+ for i in range(0, len(test_names)):
431
452
  if not results[i]:
432
- errors.append(testNames[i])
433
- # we don't raise exception for public-tests, see comments under 'testSafe' method
434
- errorsInMessage = ''
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
- failedMsg = ', '.join(errors)
437
- errorsInMessage = ' | Failed methods : ' + failedMsg
438
- messageContent = '[INFO:PUBLIC_TESTS_END] ' + market['type'] + errorsInMessage
439
- messageWithPadding = self.add_padding(messageContent, 25)
440
- dump(messageWithPadding, exchange.id)
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
- 'BTC/CNY',
448
- 'BTC/USD',
449
- 'BTC/USDT',
450
- 'BTC/EUR',
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
- resultSymbols.append(symbol)
475
- resultMsg = ''
476
- resultLength = len(resultSymbols)
477
- exchangeSymbolsLength = len(exchange.symbols)
478
- if resultLength > 0:
479
- if exchangeSymbolsLength > resultLength:
480
- resultMsg = ', '.join(resultSymbols) + ' + more...'
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
- resultMsg = ', '.join(resultSymbols)
483
- dump('Exchange loaded', exchangeSymbolsLength, 'symbols', resultMsg)
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, isSpot, symbols):
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
- currentTypeMarkets = self.get_markets_from_exchange(exchange, spot)
522
- codes = [
523
- 'BTC',
524
- 'ETH',
525
- 'XRP',
526
- 'LTC',
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
- currentCode = codes[i]
585
- marketsArrayForCurrentCode = exchange.filter_by(currentTypeMarkets, 'base', currentCode)
586
- indexedMkts = exchange.index_by(marketsArrayForCurrentCode, 'symbol')
587
- symbolsArrayForCurrentCode = list(indexedMkts.keys())
588
- symbolsLength = len(symbolsArrayForCurrentCode)
589
- if symbolsLength:
590
- symbol = self.get_test_symbol(exchange, spot, symbolsArrayForCurrentCode)
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
- activeMarkets = exchange.filter_by(currentTypeMarkets, 'active', True)
595
- activeSymbols = []
596
- for i in range(0, len(activeMarkets)):
597
- activeSymbols.append(activeMarkets[i]['symbol'])
598
- symbol = self.get_test_symbol(exchange, spot, activeSymbols)
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(currentTypeMarkets.values())
601
- valuesLength = len(values)
602
- if valuesLength > 0:
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, providedSymbol=None):
609
- spotSymbol = None
610
- swapSymbol = None
611
- if providedSymbol is not None:
612
- market = exchange.market(providedSymbol)
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
- spotSymbol = providedSymbol
559
+ spot_symbol = provided_symbol
615
560
  else:
616
- swapSymbol = providedSymbol
561
+ swap_symbol = provided_symbol
617
562
  else:
618
563
  if exchange.has['spot']:
619
- spotSymbol = self.get_valid_symbol(exchange, True)
564
+ spot_symbol = self.get_valid_symbol(exchange, True)
620
565
  if exchange.has['swap']:
621
- swapSymbol = self.get_valid_symbol(exchange, False)
622
- if spotSymbol is not None:
623
- dump('Selected SPOT SYMBOL:', spotSymbol)
624
- if swapSymbol is not None:
625
- dump('Selected SWAP SYMBOL:', swapSymbol)
626
- if not self.privateTestOnly:
627
- if exchange.has['spot'] and spotSymbol is not None:
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, spotSymbol)
632
- if exchange.has['swap'] and swapSymbol is not None:
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, swapSymbol)
637
- if self.privateTest or self.privateTestOnly:
638
- if exchange.has['spot'] and spotSymbol is not None:
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, spotSymbol)
641
- if exchange.has['swap'] and swapSymbol is not None:
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, swapSymbol)
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) # danger zone - won't execute with non-empty 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
- isSpot = market['spot']
704
- if isSpot:
633
+ is_spot = market['spot']
634
+ if is_spot:
705
635
  tests['fetchCurrencies'] = []
706
636
  else:
707
637
  # derivatives only
708
- tests['fetchPositions'] = [symbol] # self test fetches all positions for 1 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
- combinedPublicPrivateTests = exchange.deep_extend(self.publicTests, tests)
717
- testNames = list(combinedPublicPrivateTests.keys())
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(testNames)):
720
- testName = testNames[i]
721
- testArgs = combinedPublicPrivateTests[testName]
722
- promises.append(self.test_safe(testName, exchange, testArgs, False))
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(testNames)):
726
- testName = testNames[i]
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(testName)
730
- errorsCnt = len(errors) # PHP transpile count($errors)
731
- if errorsCnt > 0:
732
- # raise Error('Failed private tests [' + market['type'] + ']: ' + ', '.join(errors))
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: bool, message: str, calculatedOutput, storedOutput):
685
+ def assert_static_error(self, cond, message, calculated_output, stored_output):
757
686
  # -----------------------------------------------------------------------------
758
687
  # --- Init of static tests functions------------------------------------------
759
688
  # -----------------------------------------------------------------------------
760
- calculatedString = json_stringify(calculatedOutput)
761
- outputString = json_stringify(storedOutput)
762
- errorMessage = message + ' expected ' + outputString + ' received: ' + calculatedString
763
- assert cond, errorMessage
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: str):
694
+ def load_markets_from_file(self, id):
766
695
  # load markets from file
767
- # to make self test
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 = rootDir + './ts/src/test/static/markets/' + id + '.json'
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, targetExchange: str = None):
775
- folder = rootDir + './ts/src/test/static/data/'
708
+ def load_static_data(self, folder, target_exchange=None):
776
709
  result = {}
777
- if targetExchange:
710
+ if target_exchange:
778
711
  # read a single exchange
779
- result[targetExchange] = io_file_read(folder + targetExchange + '.json')
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
- exchangeName = file.replace('.json', '')
717
+ exchange_name = file.replace('.json', '')
785
718
  content = io_file_read(folder + file)
786
- result[exchangeName] = content
719
+ result[exchange_name] = content
787
720
  return result
788
721
 
789
- def remove_hostnamefrom_url(self, url: str):
722
+ def remove_hostnamefrom_url(self, url):
790
723
  if url is None:
791
724
  return None
792
- urlParts = url.split('/')
725
+ url_parts = url.split('/')
793
726
  res = ''
794
- for i in range(0, len(urlParts)):
727
+ for i in range(0, len(url_parts)):
795
728
  if i > 2:
796
- current = urlParts[i]
729
+ current = url_parts[i]
797
730
  if current.find('?') > -1:
798
- # handle urls like self: /v1/account/accounts?AccessK
799
- currentParts = current.split('?')
731
+ # handle urls like this: /v1/account/accounts?AccessK
732
+ current_parts = current.split('?')
800
733
  res += '/'
801
- res += currentParts[0]
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: str):
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
- keyValue = part.split('=')
813
- keysLength = len(keyValue)
814
- if keysLength != 2:
745
+ key_value = part.split('=')
746
+ keys_length = len(key_value)
747
+ if keys_length != 2:
815
748
  continue
816
- key = keyValue[0]
817
- value = keyValue[1]
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 self: timestamp=1699382693405&batchOrders=[{\"symbol\":\"LTCUSDT\",\"side\":\"BUY\",\"newClientOrderI
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, skipKeys: List[str], newOutput, storedOutput):
825
- if (isinstance(storedOutput, dict)) and (isinstance(newOutput, dict)):
826
- storedOutputKeys = list(storedOutput.keys())
827
- newOutputKeys = list(newOutput.keys())
828
- storedKeysLength = len(storedOutputKeys)
829
- newKeysLength = len(newOutputKeys)
830
- self.assert_static_error(storedKeysLength == newKeysLength, 'output length mismatch', storedOutput, newOutput)
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(storedOutputKeys)):
833
- key = storedOutputKeys[i]
834
- if exchange.in_array(key, skipKeys):
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, newOutputKeys)):
837
- self.assert_static_error(False, 'output key missing: ' + key, storedOutput, newOutput)
838
- storedValue = storedOutput[key]
839
- newValue = newOutput[key]
840
- self.assert_new_and_stored_output(exchange, skipKeys, newValue, storedValue)
841
- elif isinstance(storedOutput, list) and (isinstance(newOutput, list)):
842
- storedArrayLength = len(storedOutput)
843
- newArrayLength = len(newOutput)
844
- self.assert_static_error(storedArrayLength == newArrayLength, 'output length mismatch', storedOutput, newOutput)
845
- for i in range(0, len(storedOutput)):
846
- storedItem = storedOutput[i]
847
- newItem = newOutput[i]
848
- self.assert_new_and_stored_output(exchange, skipKeys, newItem, storedItem)
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
- messageError = 'output value mismatch:' + str(newOutput) + ' != ' + str(storedOutput)
852
- self.assert_static_error(newOutput == storedOutput, messageError, storedOutput, newOutput)
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 assert_static_output(self, exchange, type: str, skipKeys: List[str], storedUrl: str, requestUrl: str, storedOutput, newOutput):
855
- if storedUrl != requestUrl:
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
- firstPath = self.remove_hostnamefrom_url(storedUrl)
858
- secondPath = self.remove_hostnamefrom_url(requestUrl)
859
- self.assert_static_error(firstPath == secondPath, 'url mismatch', firstPath, secondPath)
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&timestamp=1698777135343&type=MARKET&signature=d55a7e4f7f9dbe56c4004c9f3ab340869d3cb004e2f0b5b861e5fbd1762fd9a0
862
- if (storedOutput is None) and (newOutput is None):
863
- if (storedUrl is not None) and (requestUrl is not None):
864
- storedUrlParts = storedUrl.split('?')
865
- newUrlParts = requestUrl.split('?')
866
- storedUrlQuery = exchange.safe_value(storedUrlParts, 1)
867
- newUrlQuery = exchange.safe_value(newUrlParts, 1)
868
- if (storedUrlQuery is None) and (newUrlQuery is None):
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
- storedUrlParams = self.urlencoded_to_dict(storedUrlQuery)
873
- newUrlParams = self.urlencoded_to_dict(newUrlQuery)
874
- self.assert_new_and_stored_output(exchange, skipKeys, newUrlParams, storedUrlParams)
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(storedOutput, str):
879
- storedOutput = json_parse(storedOutput)
880
- if isinstance(newOutput, str):
881
- newOutput = json_parse(newOutput)
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
- storedOutput = self.urlencoded_to_dict(storedOutput)
884
- newOutput = self.urlencoded_to_dict(newOutput)
885
- self.assert_new_and_stored_output(exchange, skipKeys, newOutput, storedOutput)
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
- newInput = []
847
+ new_input = []
892
848
  for i in range(0, len(input)):
893
849
  current = input[i]
894
- if current == None: # noqa: E711
895
- newInput.append(None)
850
+ if is_null_value(current):
851
+ new_input.append(None)
896
852
  else:
897
- newInput.append(current)
898
- return newInput
853
+ new_input.append(current)
854
+ return new_input
899
855
 
900
- async def test_method_statically(self, exchange, method: str, data: object, type: str, skipKeys: List[str]):
856
+ async def test_method_statically(self, exchange, method, data, type, skip_keys):
901
857
  output = None
902
- requestUrl = None
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
- requestUrl = exchange.last_request_url
865
+ request_url = exchange.last_request_url
912
866
  try:
913
- callOutput = exchange.safe_value(data, 'output')
914
- self.assert_static_output(exchange, type, skipKeys, data['url'], requestUrl, callOutput, output)
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.staticTestsFailed = True
917
- errorMessage = '[' + self.lang + '][STATIC_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
918
- dump(errorMessage)
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 test_exchange_statically(self, exchangeName: str, exchangeData: object, testName: str = None):
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(exchangeName)
927
- methods = exchange.safe_value(exchangeData, 'methods', {})
928
- methodsNames = list(methods.keys())
929
- for i in range(0, len(methodsNames)):
930
- method = methodsNames[i]
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 (testName is not None) and (testName != description):
942
+ if (test_name is not None) and (test_name != description):
936
943
  continue
937
- type = exchange.safe_string(exchangeData, 'outputType')
938
- skipKeys = exchange.safe_value(exchangeData, 'skipKeys', [])
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, exchangeData: object):
948
+ def get_number_of_tests_from_exchange(self, exchange, exchange_data):
943
949
  sum = 0
944
- methods = exchangeData['methods']
945
- methodsNames = list(methods.keys())
946
- for i in range(0, len(methodsNames)):
947
- method = methodsNames[i]
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
- resultsLength = len(results)
950
- sum = exchange.sum(sum, resultsLength)
955
+ results_length = len(results)
956
+ sum = exchange.sum(sum, results_length)
951
957
  return sum
952
958
 
953
- async def run_static_tests(self, targetExchange: str = None, testName: str = None):
954
- staticData = self.load_static_data(targetExchange)
955
- exchanges = list(staticData.keys())
956
- exchange = init_exchange('Exchange', {}) # tmp to do the calculations until we have the ast-transpiler transpiling self code
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 targetExchange:
960
- dump("Exchange to test: " + targetExchange)
961
- if testName:
962
- dump("Testing only: " + testName)
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
- exchangeName = exchanges[i]
965
- exchangeData = staticData[exchangeName]
966
- numberOfTests = self.get_number_of_tests_from_exchange(exchange, exchangeData)
967
- sum = exchange.sum(sum, numberOfTests)
968
- promises.append(self.test_exchange_statically(exchangeName, exchangeData, testName))
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.staticTestsFailed:
983
+ if self.request_tests_failed or self.response_tests_failed:
971
984
  exit_script(1)
972
985
  else:
973
- successMessage = '[' + self.lang + '][TEST_SUCCESS] ' + str(sum) + ' static tests passed.'
974
- dump(successMessage)
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
- successMessage = '[' + self.lang + '][TEST_SUCCESS] brokerId tests passed.'
997
- dump(successMessage)
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
- spotId = 'x-R4BD3S82'
1003
- spotOrderRequest = None
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
- spotOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1008
- clientOrderId = spotOrderRequest['newClientOrderId']
1009
- assert clientOrderId.startswith(spotId), 'spot clientOrderId does not start with spotId'
1010
- swapId = 'x-xcKtGhcu'
1011
- swapOrderRequest = None
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
- swapOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1016
- swapInverseOrderRequest = None
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
- swapInverseOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1021
- clientOrderIdSpot = swapOrderRequest['newClientOrderId']
1022
- assert clientOrderIdSpot.startswith(swapId), 'swap clientOrderId does not start with swapId'
1023
- clientOrderIdInverse = swapInverseOrderRequest['newClientOrderId']
1024
- assert clientOrderIdInverse.startswith(swapId), 'swap clientOrderIdInverse does not start with swapId'
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
- spotOrderRequest = None
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
- spotOrderRequest = json_parse(okx.last_request_body)
1035
- clientOrderId = spotOrderRequest[0]['clOrdId'] # returns order inside array
1036
- assert clientOrderId.startswith(id), 'spot clientOrderId does not start with id'
1037
- assert spotOrderRequest[0]['tag'] == id, 'id different from spot tag'
1038
- swapOrderRequest = None
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
- swapOrderRequest = json_parse(okx.last_request_body)
1043
- clientOrderIdSpot = swapOrderRequest[0]['clOrdId']
1044
- assert clientOrderIdSpot.startswith(id), 'swap clientOrderId does not start with id'
1045
- assert swapOrderRequest[0]['tag'] == id, 'id different from swap tag'
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
- reqHeaders = None
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
- reqHeaders = bybit.last_request_headers
1070
- assert reqHeaders['Referer'] == id, 'id not in headers'
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
- reqHeaders = None
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
- reqHeaders = kucoin.last_request_headers
1088
+ req_headers = kucoin.last_request_headers
1083
1089
  id = 'ccxt'
1084
- assert reqHeaders['KC-API-PARTNER'] == id, 'id not in headers'
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
- reqHeaders = None
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
- reqHeaders = kucoin.last_request_headers
1097
- assert reqHeaders['KC-API-PARTNER'] == id, 'id not in headers'
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
- reqHeaders = None
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
- reqHeaders = bitget.last_request_headers
1109
- assert reqHeaders['X-CHANNEL-API-CODE'] == id, 'id not in headers'
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
- reqHeaders = None
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
- reqHeaders = mexc.last_request_headers
1122
- assert reqHeaders['source'] == id, 'id not in headers'
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
- spotOrderRequest = None
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
- spotOrderRequest = json_parse(huobi.last_request_body)
1134
- clientOrderId = spotOrderRequest['client-order-id']
1135
- assert clientOrderId.startswith(id), 'spot clientOrderId does not start with id'
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
- swapOrderRequest = None
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
- swapOrderRequest = json_parse(huobi.last_request_body)
1142
- swapInverseOrderRequest = None
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
- swapInverseOrderRequest = json_parse(huobi.last_request_body)
1147
- clientOrderIdSpot = swapOrderRequest['channel_code']
1148
- assert clientOrderIdSpot.startswith(id), 'swap channel_code does not start with id'
1149
- clientOrderIdInverse = swapInverseOrderRequest['channel_code']
1150
- assert clientOrderIdInverse.startswith(id), 'swap inverse channel_code does not start with id'
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
- spotOrderRequest = None
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
- spotOrderRequest = self.urlencoded_to_dict(woo.last_request_body)
1162
- brokerId = spotOrderRequest['broker_id']
1163
- assert brokerId.startswith(id), 'broker_id does not start with id'
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
- stopOrderRequest = None
1171
+ stop_order_request = None
1166
1172
  try:
1167
- await woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {'stopPrice': 30000})
1173
+ await woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {
1174
+ 'stopPrice': 30000,
1175
+ })
1168
1176
  except Exception as e:
1169
- stopOrderRequest = json_parse(woo.last_request_body)
1170
- clientOrderIdSpot = stopOrderRequest['brokerId']
1171
- assert clientOrderIdSpot.startswith(id), 'brokerId does not start with id'
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
- reqHeaders = None
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
- reqHeaders = bitmart.last_request_headers
1184
- assert reqHeaders['X-BM-BROKER-ID'] == id, 'id not in headers'
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
- spotOrderRequest = None
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
- spotOrderRequest = json_parse(exchange.last_request_body)
1196
- clientOrderId = spotOrderRequest['client_id']
1197
- assert clientOrderId.startswith(id), 'clientOrderId does not start with id'
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 *****