ccxt-ir 4.3.46.0.2__py2.py3-none-any.whl → 4.5.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. ccxt/__init__.py +39 -35
  2. ccxt/abantether.py +9 -9
  3. ccxt/abstract/alpaca.py +4 -0
  4. ccxt/abstract/apex.py +31 -0
  5. ccxt/abstract/bigone.py +1 -1
  6. ccxt/abstract/binance.py +106 -48
  7. ccxt/abstract/binancecoinm.py +106 -48
  8. ccxt/abstract/binanceus.py +141 -83
  9. ccxt/abstract/binanceusdm.py +106 -48
  10. ccxt/abstract/bingx.py +50 -1
  11. ccxt/abstract/bitbank.py +5 -0
  12. ccxt/abstract/bitfinex.py +136 -65
  13. ccxt/abstract/bitflyer.py +1 -0
  14. ccxt/abstract/bitget.py +67 -0
  15. ccxt/abstract/bitmart.py +19 -1
  16. ccxt/abstract/bitopro.py +1 -0
  17. ccxt/abstract/bitrue.py +68 -68
  18. ccxt/abstract/bitstamp.py +1 -0
  19. ccxt/abstract/blofin.py +30 -0
  20. ccxt/abstract/btcbox.py +2 -0
  21. ccxt/abstract/bybit.py +28 -13
  22. ccxt/abstract/cex.py +28 -29
  23. ccxt/abstract/coinbaseexchange.py +1 -0
  24. ccxt/abstract/coinbaseinternational.py +1 -1
  25. ccxt/abstract/cryptocom.py +16 -0
  26. ccxt/abstract/cryptomus.py +20 -0
  27. ccxt/abstract/defx.py +69 -0
  28. ccxt/abstract/deribit.py +1 -0
  29. ccxt/abstract/derive.py +117 -0
  30. ccxt/abstract/digifinex.py +1 -0
  31. ccxt/abstract/ellipx.py +25 -0
  32. ccxt/abstract/foxbit.py +26 -0
  33. ccxt/abstract/gate.py +19 -0
  34. ccxt/abstract/gateio.py +19 -0
  35. ccxt/abstract/gemini.py +1 -0
  36. ccxt/abstract/hibachi.py +26 -0
  37. ccxt/abstract/hyperliquid.py +1 -1
  38. ccxt/abstract/independentreserve.py +6 -0
  39. ccxt/abstract/kraken.py +1 -0
  40. ccxt/abstract/krakenfutures.py +4 -0
  41. ccxt/abstract/kucoin.py +10 -0
  42. ccxt/abstract/kucoinfutures.py +18 -0
  43. ccxt/abstract/lbank.py +2 -1
  44. ccxt/abstract/luno.py +1 -0
  45. ccxt/abstract/mexc.py +2 -0
  46. ccxt/abstract/modetrade.py +119 -0
  47. ccxt/abstract/myokx.py +349 -0
  48. ccxt/abstract/oceanex.py +5 -0
  49. ccxt/abstract/okx.py +25 -0
  50. ccxt/abstract/okxus.py +349 -0
  51. ccxt/abstract/onetrading.py +0 -12
  52. ccxt/abstract/paradex.py +23 -0
  53. ccxt/abstract/phemex.py +2 -0
  54. ccxt/abstract/poloniex.py +36 -0
  55. ccxt/abstract/tradeogre.py +3 -1
  56. ccxt/abstract/upbit.py +51 -34
  57. ccxt/abstract/whitebit.py +16 -0
  58. ccxt/abstract/woo.py +64 -6
  59. ccxt/abstract/xt.py +10 -5
  60. ccxt/afratether.py +7 -7
  61. ccxt/alpaca.py +828 -51
  62. ccxt/apex.py +1875 -0
  63. ccxt/arzinja.py +7 -7
  64. ccxt/arzplus.py +9 -9
  65. ccxt/ascendex.py +501 -306
  66. ccxt/async_support/__init__.py +39 -35
  67. ccxt/async_support/abantether.py +10 -10
  68. ccxt/async_support/afratether.py +9 -9
  69. ccxt/async_support/alpaca.py +828 -51
  70. ccxt/async_support/apex.py +1875 -0
  71. ccxt/async_support/arzinja.py +10 -10
  72. ccxt/async_support/arzplus.py +12 -12
  73. ccxt/async_support/ascendex.py +502 -306
  74. ccxt/async_support/base/exchange.py +303 -89
  75. ccxt/async_support/base/ws/cache.py +9 -3
  76. ccxt/async_support/base/ws/client.py +173 -38
  77. ccxt/async_support/base/ws/future.py +25 -37
  78. ccxt/async_support/bequant.py +5 -3
  79. ccxt/async_support/bigone.py +279 -144
  80. ccxt/async_support/binance.py +2347 -1158
  81. ccxt/async_support/binancecoinm.py +9 -3
  82. ccxt/async_support/binanceus.py +17 -3
  83. ccxt/async_support/binanceusdm.py +9 -4
  84. ccxt/async_support/bingx.py +2962 -920
  85. ccxt/async_support/bit2c.py +147 -27
  86. ccxt/async_support/bitbank.py +151 -23
  87. ccxt/async_support/bitbns.py +104 -30
  88. ccxt/async_support/bitfinex.py +3291 -1113
  89. ccxt/async_support/bitflyer.py +202 -27
  90. ccxt/async_support/bitget.py +3683 -1538
  91. ccxt/async_support/bithumb.py +195 -38
  92. ccxt/async_support/bitimen.py +12 -12
  93. ccxt/async_support/bitir.py +38 -38
  94. ccxt/async_support/bitmart.py +1288 -350
  95. ccxt/async_support/bitmex.py +260 -75
  96. ccxt/async_support/bitopro.py +262 -62
  97. ccxt/async_support/bitpin.py +17 -16
  98. ccxt/async_support/bitrue.py +459 -290
  99. ccxt/async_support/bitso.py +199 -54
  100. ccxt/async_support/bitstamp.py +230 -96
  101. ccxt/async_support/bitteam.py +167 -25
  102. ccxt/async_support/{huobijp.py → bittrade.py} +158 -30
  103. ccxt/async_support/bitvavo.py +213 -49
  104. ccxt/async_support/blockchaincom.py +160 -46
  105. ccxt/async_support/blofin.py +502 -120
  106. ccxt/async_support/btcalpha.py +169 -31
  107. ccxt/async_support/btcbox.py +292 -23
  108. ccxt/async_support/btcmarkets.py +211 -58
  109. ccxt/async_support/btcturk.py +161 -38
  110. ccxt/async_support/bybit.py +1775 -1030
  111. ccxt/async_support/cex.py +1440 -1303
  112. ccxt/async_support/coinbase.py +724 -212
  113. ccxt/async_support/coinbaseadvanced.py +2 -1
  114. ccxt/async_support/coinbaseexchange.py +388 -89
  115. ccxt/async_support/coinbaseinternational.py +412 -57
  116. ccxt/async_support/coincatch.py +177 -78
  117. ccxt/async_support/coincheck.py +135 -19
  118. ccxt/async_support/coinex.py +606 -232
  119. ccxt/async_support/coinmate.py +189 -63
  120. ccxt/async_support/coinmetro.py +195 -54
  121. ccxt/async_support/coinone.py +158 -51
  122. ccxt/async_support/coinsph.py +336 -61
  123. ccxt/async_support/coinspot.py +151 -52
  124. ccxt/async_support/cryptocom.py +661 -111
  125. ccxt/async_support/cryptomus.py +1137 -0
  126. ccxt/async_support/defx.py +2071 -0
  127. ccxt/async_support/delta.py +299 -99
  128. ccxt/async_support/deribit.py +348 -126
  129. ccxt/async_support/derive.py +2572 -0
  130. ccxt/async_support/digifinex.py +430 -214
  131. ccxt/async_support/ellipx.py +2029 -0
  132. ccxt/async_support/eterex.py +10 -10
  133. ccxt/async_support/excoino.py +31 -31
  134. ccxt/async_support/exir.py +14 -14
  135. ccxt/async_support/exmo.py +344 -131
  136. ccxt/async_support/exnovin.py +10 -10
  137. ccxt/async_support/farhadexchange.py +12 -12
  138. ccxt/async_support/fmfwio.py +2 -1
  139. ccxt/async_support/foxbit.py +1935 -0
  140. ccxt/async_support/gate.py +1351 -529
  141. ccxt/async_support/gateio.py +2 -1
  142. ccxt/async_support/gemini.py +144 -39
  143. ccxt/async_support/hashkey.py +152 -109
  144. ccxt/async_support/hibachi.py +2080 -0
  145. ccxt/async_support/hitbtc.py +395 -167
  146. ccxt/async_support/hitobit.py +12 -12
  147. ccxt/async_support/hollaex.py +307 -119
  148. ccxt/async_support/htx.py +851 -383
  149. ccxt/async_support/huobi.py +2 -1
  150. ccxt/async_support/hyperliquid.py +1848 -536
  151. ccxt/async_support/independentreserve.py +288 -15
  152. ccxt/async_support/indodax.py +190 -33
  153. ccxt/async_support/jibitex.py +12 -12
  154. ccxt/async_support/kraken.py +795 -351
  155. ccxt/async_support/krakenfutures.py +214 -62
  156. ccxt/async_support/kucoin.py +715 -396
  157. ccxt/async_support/kucoinfutures.py +652 -89
  158. ccxt/async_support/latoken.py +217 -113
  159. ccxt/async_support/lbank.py +425 -97
  160. ccxt/async_support/luno.py +382 -35
  161. ccxt/async_support/mercado.py +113 -6
  162. ccxt/async_support/mexc.py +874 -437
  163. ccxt/async_support/modetrade.py +2818 -0
  164. ccxt/async_support/myokx.py +54 -0
  165. ccxt/async_support/ndax.py +221 -64
  166. ccxt/async_support/nobitex.py +31 -37
  167. ccxt/async_support/novadax.py +190 -34
  168. ccxt/async_support/oceanex.py +217 -28
  169. ccxt/async_support/okcoin.py +253 -145
  170. ccxt/async_support/okexchange.py +11 -11
  171. ccxt/async_support/okx.py +1088 -351
  172. ccxt/async_support/okxus.py +54 -0
  173. ccxt/async_support/ompfinex.py +25 -24
  174. ccxt/async_support/onetrading.py +213 -392
  175. ccxt/async_support/oxfun.py +245 -166
  176. ccxt/async_support/p2b.py +151 -29
  177. ccxt/async_support/paradex.py +562 -49
  178. ccxt/async_support/paymium.py +82 -19
  179. ccxt/async_support/phemex.py +713 -172
  180. ccxt/async_support/poloniex.py +1602 -283
  181. ccxt/async_support/probit.py +224 -95
  182. ccxt/async_support/ramzinex.py +30 -27
  183. ccxt/async_support/sarmayex.py +9 -9
  184. ccxt/async_support/sarrafex.py +13 -13
  185. ccxt/async_support/tabdeal.py +14 -13
  186. ccxt/async_support/tetherland.py +9 -9
  187. ccxt/async_support/timex.py +210 -51
  188. ccxt/async_support/tokocrypto.py +167 -47
  189. ccxt/async_support/tradeogre.py +266 -31
  190. ccxt/async_support/twox.py +9 -9
  191. ccxt/async_support/ubitex.py +12 -12
  192. ccxt/async_support/upbit.py +568 -165
  193. ccxt/async_support/vertex.py +160 -32
  194. ccxt/async_support/wallex.py +12 -12
  195. ccxt/async_support/wavesexchange.py +165 -30
  196. ccxt/async_support/whitebit.py +975 -127
  197. ccxt/async_support/woo.py +1918 -1016
  198. ccxt/async_support/woofipro.py +433 -141
  199. ccxt/async_support/xt.py +649 -193
  200. ccxt/async_support/yobit.py +195 -70
  201. ccxt/async_support/zaif.py +91 -15
  202. ccxt/async_support/zonda.py +151 -36
  203. ccxt/base/decimal_to_precision.py +14 -10
  204. ccxt/base/errors.py +49 -18
  205. ccxt/base/exchange.py +1556 -450
  206. ccxt/base/precise.py +10 -0
  207. ccxt/base/types.py +114 -6
  208. ccxt/bequant.py +5 -3
  209. ccxt/bigone.py +279 -144
  210. ccxt/binance.py +2347 -1158
  211. ccxt/binancecoinm.py +9 -3
  212. ccxt/binanceus.py +17 -3
  213. ccxt/binanceusdm.py +9 -4
  214. ccxt/bingx.py +2962 -920
  215. ccxt/bit2c.py +147 -27
  216. ccxt/bitbank.py +151 -23
  217. ccxt/bitbns.py +104 -30
  218. ccxt/bitfinex.py +3290 -1113
  219. ccxt/bitflyer.py +202 -27
  220. ccxt/bitget.py +3683 -1538
  221. ccxt/bithumb.py +194 -38
  222. ccxt/bitimen.py +9 -9
  223. ccxt/bitir.py +35 -35
  224. ccxt/bitmart.py +1288 -350
  225. ccxt/bitmex.py +260 -75
  226. ccxt/bitopro.py +262 -62
  227. ccxt/bitpin.py +15 -14
  228. ccxt/bitrue.py +459 -290
  229. ccxt/bitso.py +199 -54
  230. ccxt/bitstamp.py +230 -96
  231. ccxt/bitteam.py +167 -25
  232. ccxt/{huobijp.py → bittrade.py} +158 -30
  233. ccxt/bitvavo.py +213 -49
  234. ccxt/blockchaincom.py +160 -46
  235. ccxt/blofin.py +502 -120
  236. ccxt/btcalpha.py +169 -31
  237. ccxt/btcbox.py +291 -23
  238. ccxt/btcmarkets.py +211 -58
  239. ccxt/btcturk.py +161 -38
  240. ccxt/bybit.py +1775 -1030
  241. ccxt/cex.py +1439 -1303
  242. ccxt/coinbase.py +724 -212
  243. ccxt/coinbaseadvanced.py +2 -1
  244. ccxt/coinbaseexchange.py +388 -89
  245. ccxt/coinbaseinternational.py +412 -57
  246. ccxt/coincatch.py +177 -78
  247. ccxt/coincheck.py +135 -19
  248. ccxt/coinex.py +606 -232
  249. ccxt/coinmate.py +189 -63
  250. ccxt/coinmetro.py +194 -54
  251. ccxt/coinone.py +158 -51
  252. ccxt/coinsph.py +336 -61
  253. ccxt/coinspot.py +151 -52
  254. ccxt/cryptocom.py +661 -111
  255. ccxt/cryptomus.py +1137 -0
  256. ccxt/defx.py +2070 -0
  257. ccxt/delta.py +299 -99
  258. ccxt/deribit.py +348 -126
  259. ccxt/derive.py +2571 -0
  260. ccxt/digifinex.py +430 -214
  261. ccxt/ellipx.py +2029 -0
  262. ccxt/eterex.py +7 -7
  263. ccxt/excoino.py +29 -29
  264. ccxt/exir.py +11 -11
  265. ccxt/exmo.py +343 -131
  266. ccxt/exnovin.py +8 -8
  267. ccxt/farhadexchange.py +10 -10
  268. ccxt/fmfwio.py +2 -1
  269. ccxt/foxbit.py +1935 -0
  270. ccxt/gate.py +1351 -529
  271. ccxt/gateio.py +2 -1
  272. ccxt/gemini.py +144 -39
  273. ccxt/hashkey.py +152 -109
  274. ccxt/hibachi.py +2079 -0
  275. ccxt/hitbtc.py +395 -167
  276. ccxt/hitobit.py +9 -9
  277. ccxt/hollaex.py +307 -119
  278. ccxt/htx.py +851 -383
  279. ccxt/huobi.py +2 -1
  280. ccxt/hyperliquid.py +1848 -536
  281. ccxt/independentreserve.py +287 -15
  282. ccxt/indodax.py +190 -33
  283. ccxt/jibitex.py +9 -9
  284. ccxt/kraken.py +794 -351
  285. ccxt/krakenfutures.py +214 -62
  286. ccxt/kucoin.py +715 -396
  287. ccxt/kucoinfutures.py +652 -89
  288. ccxt/latoken.py +217 -113
  289. ccxt/lbank.py +425 -97
  290. ccxt/luno.py +382 -35
  291. ccxt/mercado.py +113 -6
  292. ccxt/mexc.py +873 -437
  293. ccxt/modetrade.py +2818 -0
  294. ccxt/myokx.py +54 -0
  295. ccxt/ndax.py +221 -64
  296. ccxt/nobitex.py +29 -35
  297. ccxt/novadax.py +190 -34
  298. ccxt/oceanex.py +217 -28
  299. ccxt/okcoin.py +253 -145
  300. ccxt/okexchange.py +9 -9
  301. ccxt/okx.py +1088 -351
  302. ccxt/okxus.py +54 -0
  303. ccxt/ompfinex.py +22 -21
  304. ccxt/onetrading.py +213 -392
  305. ccxt/oxfun.py +245 -166
  306. ccxt/p2b.py +151 -29
  307. ccxt/paradex.py +562 -49
  308. ccxt/paymium.py +82 -19
  309. ccxt/phemex.py +712 -172
  310. ccxt/poloniex.py +1601 -283
  311. ccxt/pro/__init__.py +76 -17
  312. ccxt/pro/alpaca.py +21 -6
  313. ccxt/pro/apex.py +984 -0
  314. ccxt/pro/ascendex.py +58 -10
  315. ccxt/pro/bequant.py +6 -1
  316. ccxt/pro/binance.py +728 -156
  317. ccxt/pro/binancecoinm.py +6 -2
  318. ccxt/pro/binanceus.py +8 -4
  319. ccxt/pro/binanceusdm.py +7 -2
  320. ccxt/pro/bingx.py +333 -142
  321. ccxt/pro/bitfinex.py +727 -262
  322. ccxt/pro/bitget.py +570 -79
  323. ccxt/pro/bithumb.py +20 -6
  324. ccxt/pro/bitmart.py +216 -87
  325. ccxt/pro/bitmex.py +47 -9
  326. ccxt/pro/bitopro.py +26 -14
  327. ccxt/pro/bitrue.py +22 -22
  328. ccxt/pro/bitstamp.py +54 -21
  329. ccxt/pro/{huobijp.py → bittrade.py} +7 -6
  330. ccxt/pro/bitvavo.py +191 -67
  331. ccxt/pro/blockchaincom.py +21 -8
  332. ccxt/pro/blofin.py +9 -1
  333. ccxt/pro/bybit.py +632 -245
  334. ccxt/pro/cex.py +59 -24
  335. ccxt/pro/coinbase.py +102 -73
  336. ccxt/pro/coinbaseadvanced.py +2 -1
  337. ccxt/pro/coinbaseexchange.py +8 -8
  338. ccxt/pro/coinbaseinternational.py +181 -25
  339. ccxt/pro/coincatch.py +6 -7
  340. ccxt/pro/coincheck.py +11 -6
  341. ccxt/pro/coinex.py +967 -665
  342. ccxt/pro/coinone.py +16 -9
  343. ccxt/pro/cryptocom.py +448 -45
  344. ccxt/pro/defx.py +831 -0
  345. ccxt/pro/deribit.py +150 -14
  346. ccxt/pro/derive.py +704 -0
  347. ccxt/pro/exmo.py +239 -6
  348. ccxt/pro/gate.py +623 -65
  349. ccxt/pro/gateio.py +2 -1
  350. ccxt/pro/gemini.py +27 -11
  351. ccxt/pro/hashkey.py +2 -2
  352. ccxt/pro/hitbtc.py +196 -91
  353. ccxt/pro/hollaex.py +23 -7
  354. ccxt/pro/htx.py +51 -14
  355. ccxt/pro/huobi.py +2 -1
  356. ccxt/pro/hyperliquid.py +591 -27
  357. ccxt/pro/independentreserve.py +9 -6
  358. ccxt/pro/kraken.py +640 -320
  359. ccxt/pro/krakenfutures.py +62 -35
  360. ccxt/pro/kucoin.py +267 -46
  361. ccxt/pro/kucoinfutures.py +165 -21
  362. ccxt/pro/lbank.py +102 -21
  363. ccxt/pro/luno.py +12 -8
  364. ccxt/pro/mexc.py +877 -111
  365. ccxt/pro/modetrade.py +1271 -0
  366. ccxt/pro/myokx.py +38 -0
  367. ccxt/pro/ndax.py +15 -2
  368. ccxt/pro/okcoin.py +23 -4
  369. ccxt/pro/okx.py +573 -98
  370. ccxt/pro/okxus.py +38 -0
  371. ccxt/pro/onetrading.py +30 -13
  372. ccxt/pro/oxfun.py +131 -27
  373. ccxt/pro/p2b.py +88 -22
  374. ccxt/pro/paradex.py +3 -3
  375. ccxt/pro/phemex.py +75 -21
  376. ccxt/pro/poloniex.py +124 -41
  377. ccxt/pro/probit.py +87 -80
  378. ccxt/pro/tradeogre.py +272 -0
  379. ccxt/pro/upbit.py +152 -12
  380. ccxt/pro/vertex.py +8 -3
  381. ccxt/pro/whitebit.py +58 -5
  382. ccxt/pro/woo.py +228 -37
  383. ccxt/pro/woofipro.py +106 -18
  384. ccxt/pro/xt.py +111 -5
  385. ccxt/probit.py +224 -95
  386. ccxt/protobuf/__init__.py +0 -0
  387. ccxt/protobuf/mexc/PrivateAccountV3Api_pb2.py +37 -0
  388. ccxt/protobuf/mexc/PrivateDealsV3Api_pb2.py +37 -0
  389. ccxt/protobuf/mexc/PrivateOrdersV3Api_pb2.py +37 -0
  390. ccxt/protobuf/mexc/PublicAggreBookTickerV3Api_pb2.py +37 -0
  391. ccxt/protobuf/mexc/PublicAggreDealsV3Api_pb2.py +39 -0
  392. ccxt/protobuf/mexc/PublicAggreDepthsV3Api_pb2.py +39 -0
  393. ccxt/protobuf/mexc/PublicBookTickerBatchV3Api_pb2.py +38 -0
  394. ccxt/protobuf/mexc/PublicBookTickerV3Api_pb2.py +37 -0
  395. ccxt/protobuf/mexc/PublicDealsV3Api_pb2.py +39 -0
  396. ccxt/protobuf/mexc/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
  397. ccxt/protobuf/mexc/PublicIncreaseDepthsV3Api_pb2.py +39 -0
  398. ccxt/protobuf/mexc/PublicLimitDepthsV3Api_pb2.py +39 -0
  399. ccxt/protobuf/mexc/PublicMiniTickerV3Api_pb2.py +37 -0
  400. ccxt/protobuf/mexc/PublicMiniTickersV3Api_pb2.py +38 -0
  401. ccxt/protobuf/mexc/PublicSpotKlineV3Api_pb2.py +37 -0
  402. ccxt/protobuf/mexc/PushDataV3ApiWrapper_pb2.py +52 -0
  403. ccxt/protobuf/mexc/__init__.py +0 -0
  404. ccxt/ramzinex.py +28 -25
  405. ccxt/sarmayex.py +7 -7
  406. ccxt/sarrafex.py +10 -10
  407. ccxt/static_dependencies/__init__.py +1 -1
  408. ccxt/static_dependencies/lark/py.typed +0 -0
  409. ccxt/static_dependencies/marshmallow/py.typed +0 -0
  410. ccxt/static_dependencies/marshmallow_dataclass/py.typed +0 -0
  411. ccxt/static_dependencies/marshmallow_oneofschema/py.typed +0 -0
  412. ccxt/tabdeal.py +12 -11
  413. ccxt/test/tests_async.py +261 -57
  414. ccxt/test/tests_helpers.py +1 -3
  415. ccxt/test/tests_init.py +4 -3
  416. ccxt/test/tests_sync.py +261 -57
  417. ccxt/tetherland.py +7 -7
  418. ccxt/timex.py +210 -51
  419. ccxt/tokocrypto.py +167 -47
  420. ccxt/tradeogre.py +266 -31
  421. ccxt/twox.py +7 -7
  422. ccxt/ubitex.py +9 -9
  423. ccxt/upbit.py +568 -165
  424. ccxt/vertex.py +160 -32
  425. ccxt/wallex.py +9 -9
  426. ccxt/wavesexchange.py +165 -30
  427. ccxt/whitebit.py +975 -127
  428. ccxt/woo.py +1917 -1016
  429. ccxt/woofipro.py +432 -141
  430. ccxt/xt.py +649 -193
  431. ccxt/yobit.py +194 -70
  432. ccxt/zaif.py +91 -15
  433. ccxt/zonda.py +151 -36
  434. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/METADATA +225 -73
  435. ccxt_ir-4.5.0.dist-info/RECORD +743 -0
  436. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/WHEEL +1 -1
  437. ccxt/abstract/ace.py +0 -15
  438. ccxt/abstract/bitbay.py +0 -53
  439. ccxt/abstract/bitcoincom.py +0 -115
  440. ccxt/abstract/bitfinex2.py +0 -139
  441. ccxt/abstract/bitpanda.py +0 -35
  442. ccxt/abstract/bl3p.py +0 -19
  443. ccxt/abstract/coinlist.py +0 -54
  444. ccxt/abstract/currencycom.py +0 -68
  445. ccxt/abstract/hitbtc3.py +0 -115
  446. ccxt/abstract/idex.py +0 -26
  447. ccxt/abstract/kuna.py +0 -182
  448. ccxt/abstract/lykke.py +0 -29
  449. ccxt/abstract/poloniexfutures.py +0 -48
  450. ccxt/abstract/wazirx.py +0 -30
  451. ccxt/ace.py +0 -1012
  452. ccxt/async_support/ace.py +0 -1012
  453. ccxt/async_support/base/ws/aiohttp_client.py +0 -125
  454. ccxt/async_support/base/ws/fast_client.py +0 -96
  455. ccxt/async_support/bitbay.py +0 -17
  456. ccxt/async_support/bitcoincom.py +0 -17
  457. ccxt/async_support/bitfinex2.py +0 -3552
  458. ccxt/async_support/bitpanda.py +0 -16
  459. ccxt/async_support/bl3p.py +0 -485
  460. ccxt/async_support/coinlist.py +0 -2243
  461. ccxt/async_support/currencycom.py +0 -1950
  462. ccxt/async_support/hitbtc3.py +0 -16
  463. ccxt/async_support/idex.py +0 -1766
  464. ccxt/async_support/kuna.py +0 -1841
  465. ccxt/async_support/lykke.py +0 -1270
  466. ccxt/async_support/poloniexfutures.py +0 -1717
  467. ccxt/async_support/wazirx.py +0 -1224
  468. ccxt/bitbay.py +0 -17
  469. ccxt/bitcoincom.py +0 -17
  470. ccxt/bitfinex2.py +0 -3552
  471. ccxt/bitpanda.py +0 -16
  472. ccxt/bl3p.py +0 -485
  473. ccxt/coinlist.py +0 -2243
  474. ccxt/currencycom.py +0 -1950
  475. ccxt/hitbtc3.py +0 -16
  476. ccxt/idex.py +0 -1766
  477. ccxt/kuna.py +0 -1841
  478. ccxt/lykke.py +0 -1270
  479. ccxt/poloniexfutures.py +0 -1717
  480. ccxt/pro/bitcoincom.py +0 -34
  481. ccxt/pro/bitfinex2.py +0 -1083
  482. ccxt/pro/bitpanda.py +0 -15
  483. ccxt/pro/currencycom.py +0 -536
  484. ccxt/pro/idex.py +0 -672
  485. ccxt/pro/poloniexfutures.py +0 -990
  486. ccxt/pro/wazirx.py +0 -749
  487. ccxt/test/base/__init__.py +0 -29
  488. ccxt/test/base/test_account.py +0 -26
  489. ccxt/test/base/test_balance.py +0 -56
  490. ccxt/test/base/test_borrow_interest.py +0 -35
  491. ccxt/test/base/test_borrow_rate.py +0 -32
  492. ccxt/test/base/test_calculate_fee.py +0 -51
  493. ccxt/test/base/test_crypto.py +0 -127
  494. ccxt/test/base/test_currency.py +0 -76
  495. ccxt/test/base/test_datetime.py +0 -109
  496. ccxt/test/base/test_decimal_to_precision.py +0 -392
  497. ccxt/test/base/test_deep_extend.py +0 -68
  498. ccxt/test/base/test_deposit_withdrawal.py +0 -50
  499. ccxt/test/base/test_exchange_datetime_functions.py +0 -76
  500. ccxt/test/base/test_funding_rate_history.py +0 -29
  501. ccxt/test/base/test_last_price.py +0 -31
  502. ccxt/test/base/test_ledger_entry.py +0 -45
  503. ccxt/test/base/test_ledger_item.py +0 -48
  504. ccxt/test/base/test_leverage_tier.py +0 -33
  505. ccxt/test/base/test_liquidation.py +0 -50
  506. ccxt/test/base/test_margin_mode.py +0 -24
  507. ccxt/test/base/test_margin_modification.py +0 -35
  508. ccxt/test/base/test_market.py +0 -193
  509. ccxt/test/base/test_number.py +0 -411
  510. ccxt/test/base/test_ohlcv.py +0 -33
  511. ccxt/test/base/test_open_interest.py +0 -32
  512. ccxt/test/base/test_order.py +0 -64
  513. ccxt/test/base/test_order_book.py +0 -69
  514. ccxt/test/base/test_position.py +0 -60
  515. ccxt/test/base/test_shared_methods.py +0 -353
  516. ccxt/test/base/test_status.py +0 -24
  517. ccxt/test/base/test_throttle.py +0 -126
  518. ccxt/test/base/test_ticker.py +0 -92
  519. ccxt/test/base/test_trade.py +0 -47
  520. ccxt/test/base/test_trading_fee.py +0 -26
  521. ccxt/test/base/test_transaction.py +0 -39
  522. ccxt/test/test_async.py +0 -1649
  523. ccxt/test/test_sync.py +0 -1648
  524. ccxt/wazirx.py +0 -1224
  525. ccxt_ir-4.3.46.0.2.dist-info/RECORD +0 -772
  526. /ccxt/abstract/{huobijp.py → bittrade.py} +0 -0
  527. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info/licenses}/LICENSE.txt +0 -0
  528. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/top_level.txt +0 -0
ccxt/test/test_sync.py DELETED
@@ -1,1648 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- import argparse
4
- import json
5
- # import logging
6
- import os
7
- import sys
8
- from traceback import format_tb, format_exception
9
-
10
- import importlib # noqa: E402
11
- import re
12
-
13
- # ------------------------------------------------------------------------------
14
- # logging.basicConfig(level=logging.INFO)
15
- # ------------------------------------------------------------------------------
16
- DIR_NAME = os.path.dirname(os.path.abspath(__file__))
17
- root = os.path.dirname(os.path.dirname(DIR_NAME))
18
- sys.path.append(root)
19
-
20
- import ccxt # noqa: E402
21
- import ccxt.pro as ccxtpro # noqa: E402
22
-
23
- # ------------------------------------------------------------------------------
24
- # from typing import Optional
25
- # from typing import List
26
- from ccxt.base.errors import NotSupported
27
- from ccxt.base.errors import ProxyError
28
- from ccxt.base.errors import OperationFailed
29
- # from ccxt.base.errors import ExchangeError
30
- from ccxt.base.errors import ExchangeNotAvailable
31
- from ccxt.base.errors import OnMaintenance
32
- from ccxt.base.errors import AuthenticationError
33
-
34
- # ------------------------------------------------------------------------------
35
-
36
- class Argv(object):
37
- id_tests = False
38
- static_tests = False
39
- ws_tests = False
40
- request_tests = False
41
- response_tests = False
42
-
43
- sandbox = False
44
- privateOnly = False
45
- private = False
46
- ws = False
47
- verbose = False
48
- nonce = None
49
- exchange = None
50
- symbol = None
51
- info = False
52
- pass
53
-
54
-
55
- argv = Argv()
56
- parser = argparse.ArgumentParser()
57
-
58
- parser.add_argument('--sandbox', action='store_true', help='enable sandbox mode')
59
- parser.add_argument('--privateOnly', action='store_true', help='run private tests only')
60
- parser.add_argument('--private', action='store_true', help='run private tests')
61
- parser.add_argument('--verbose', action='store_true', help='enable verbose output')
62
- parser.add_argument('--ws', action='store_true', help='websockets version')
63
- parser.add_argument('--info', action='store_true', help='enable info output')
64
- parser.add_argument('--static', action='store_true', help='run static tests')
65
- parser.add_argument('--useProxy', action='store_true', help='run static tests')
66
- parser.add_argument('--idTests', action='store_true', help='run brokerId tests')
67
- parser.add_argument('--responseTests', action='store_true', help='run response tests')
68
- parser.add_argument('--requestTests', action='store_true', help='run response tests')
69
- parser.add_argument('--nonce', type=int, help='integer')
70
- parser.add_argument('exchange', type=str, help='exchange id in lowercase', nargs='?')
71
- parser.add_argument('symbol', type=str, help='symbol in uppercase', nargs='?')
72
- parser.parse_args(namespace=argv)
73
-
74
- # ------------------------------------------------------------------------------
75
-
76
- path = os.path.dirname(ccxt.__file__)
77
- if 'site-packages' in os.path.dirname(ccxt.__file__):
78
- raise Exception("You are running test_async.py/test.py against a globally-installed version of the library! It was previously installed into your site-packages folder by pip or pip3. To ensure testing against the local folder uninstall it first with pip uninstall ccxt or pip3 uninstall ccxt")
79
-
80
- # ------------------------------------------------------------------------------
81
-
82
- Error = Exception
83
-
84
- # # print an error string
85
- # def dump_error(*args):
86
- # string = ' '.join([str(arg) for arg in args])
87
- # print(string)
88
- # sys.stderr.write(string + "\n")
89
- # sys.stderr.flush()
90
-
91
-
92
- def handle_all_unhandled_exceptions(type, value, traceback):
93
- dump((type), (value), '\n<UNHANDLED EXCEPTION>\n' + ('\n'.join(format_tb(traceback))))
94
- exit(1) # unrecoverable crash
95
-
96
-
97
- sys.excepthook = handle_all_unhandled_exceptions
98
- # ------------------------------------------------------------------------------
99
-
100
- # non-transpiled part, but shared names among langs
101
-
102
- is_synchronous = 'async' not in os.path.basename(__file__)
103
-
104
- rootDir = DIR_NAME + '/../../../'
105
- rootDirForSkips = DIR_NAME + '/../../../'
106
- envVars = os.environ
107
- LOG_CHARS_LENGTH = 10000
108
- ext = 'py'
109
- proxyTestFileName = 'proxies'
110
-
111
-
112
- def get_cli_arg_value(arg):
113
- arg_exists = getattr(argv, arg) if hasattr(argv, arg) else False
114
- with_hyphen = '--' + arg
115
- arg_exists_with_hyphen = getattr(argv, with_hyphen) if hasattr(argv, with_hyphen) else False
116
- without_hyphen = arg.replace('--', '')
117
- arg_exists_wo_hyphen = getattr(argv, without_hyphen) if hasattr(argv, without_hyphen) else False
118
- return arg_exists or arg_exists_with_hyphen or arg_exists_wo_hyphen
119
-
120
- isWsTests = get_cli_arg_value('--ws')
121
-
122
-
123
- class baseMainTestClass():
124
- lang = 'PY'
125
- is_synchronous = is_synchronous
126
- request_tests_failed = False
127
- response_tests_failed = False
128
- response_tests = False
129
- ws_tests = False
130
- load_keys = False
131
- skipped_settings_for_exchange = {}
132
- skipped_methods = {}
133
- check_public_tests = {}
134
- test_files = {}
135
- public_tests = {}
136
- new_line = '\n'
137
- root_dir = rootDir
138
- env_vars = envVars
139
- ext = ext
140
- root_dir_for_skips = rootDirForSkips
141
- only_specific_tests = []
142
- proxy_test_file_name = proxyTestFileName
143
- pass
144
-
145
-
146
- def dump(*args):
147
- print(' '.join([str(arg) for arg in args]))
148
-
149
-
150
- def convert_ascii(str):
151
- return str # stub
152
-
153
- def json_parse(elem):
154
- return json.loads(elem)
155
-
156
-
157
- def json_stringify(elem):
158
- return json.dumps(elem)
159
-
160
-
161
- def convert_to_snake_case(content):
162
- res = re.sub(r'(?<!^)(?=[A-Z])', '_', content).lower()
163
- return res.replace('o_h_l_c_v', 'ohlcv')
164
-
165
-
166
- def get_test_name(methodName):
167
- # stub
168
- return methodName
169
-
170
-
171
- def io_file_exists(path):
172
- return os.path.isfile(path)
173
-
174
-
175
- def io_file_read(path, decode=True):
176
- fs = open(path, "r", encoding="utf-8")
177
- content = fs.read()
178
- if decode:
179
- return json.loads(content)
180
- else:
181
- return content
182
-
183
-
184
- def io_dir_read(path):
185
- return os.listdir(path)
186
-
187
-
188
- def call_method(test_files, methodName, exchange, skippedProperties, args):
189
- methodNameToCall = 'test_' + convert_to_snake_case(methodName)
190
- return getattr(test_files[methodName], methodNameToCall)(exchange, skippedProperties, *args)
191
-
192
-
193
- def call_exchange_method_dynamically(exchange, methodName, args):
194
- return getattr(exchange, methodName)(*args)
195
-
196
- def call_overriden_method(exchange, methodName, args):
197
- # needed for php
198
- return call_exchange_method_dynamically(exchange, methodName, args)
199
-
200
- def exception_message(exc):
201
- message = '[' + type(exc).__name__ + '] ' + "".join(format_exception(type(exc), exc, exc.__traceback__, limit=6))
202
- if len(message) > LOG_CHARS_LENGTH:
203
- # Accessing out of range element causes error
204
- message = message[0:LOG_CHARS_LENGTH]
205
- return message
206
-
207
-
208
- def exit_script(code=0):
209
- exit(code)
210
-
211
-
212
- def get_exchange_prop(exchange, prop, defaultValue=None):
213
- if hasattr(exchange, prop):
214
- res = getattr(exchange, prop)
215
- if res is not None and res != '':
216
- return res
217
- return defaultValue
218
-
219
-
220
- def set_exchange_prop(exchange, prop, value):
221
- setattr(exchange, prop, value)
222
- # set snake case too
223
- setattr(exchange, convert_to_snake_case(prop), value)
224
-
225
-
226
- def init_exchange(exchangeId, args, is_ws=False):
227
- if (is_ws):
228
- return getattr(ccxtpro, exchangeId)(args)
229
- return getattr(ccxt, exchangeId)(args)
230
-
231
-
232
- def get_test_files(properties, ws=False):
233
- tests = {}
234
- finalPropList = properties + [proxyTestFileName]
235
- for i in range(0, len(finalPropList)):
236
- methodName = finalPropList[i]
237
- name_snake_case = convert_to_snake_case(methodName)
238
- prefix = 'async' if not is_synchronous else 'sync'
239
- dir_to_test = DIR_NAME + '/' + prefix + '/'
240
- module_string = 'ccxt.test.' + prefix + '.test_' + name_snake_case
241
- if (ws):
242
- prefix = 'pro'
243
- dir_to_test = DIR_NAME + '/../' + prefix + '/test/Exchange/'
244
- module_string = 'ccxt.pro.test.Exchange.test_' + name_snake_case
245
- filePathWithExt = dir_to_test + 'test_' + name_snake_case + '.py'
246
- if (io_file_exists (filePathWithExt)):
247
- imp = importlib.import_module(module_string)
248
- tests[methodName] = imp # getattr(imp, finalName)
249
- return tests
250
-
251
- def close(exchange):
252
- if (not is_synchronous and hasattr(exchange, 'close')):
253
- exchange.close()
254
-
255
- def is_null_value(value):
256
- return value is None
257
-
258
- def set_fetch_response(exchange: ccxt.Exchange, data):
259
- def fetch(url, method='GET', headers=None, body=None):
260
- return data
261
- exchange.fetch = fetch
262
- return exchange
263
-
264
- # *********************************
265
- # ***** AUTO-TRANSPILER-START *****
266
- class testMainClass(baseMainTestClass):
267
- def parse_cli_args(self):
268
- self.response_tests = get_cli_arg_value('--responseTests')
269
- self.id_tests = get_cli_arg_value('--idTests')
270
- self.request_tests = get_cli_arg_value('--requestTests')
271
- self.info = get_cli_arg_value('--info')
272
- self.verbose = get_cli_arg_value('--verbose')
273
- self.debug = get_cli_arg_value('--debug')
274
- self.private_test = get_cli_arg_value('--private')
275
- self.private_test_only = get_cli_arg_value('--privateOnly')
276
- self.sandbox = get_cli_arg_value('--sandbox')
277
- self.load_keys = get_cli_arg_value('--loadKeys')
278
- self.ws_tests = get_cli_arg_value('--ws')
279
-
280
- def init(self, exchange_id, symbol_argv, method_argv):
281
- self.parse_cli_args()
282
- if self.request_tests and self.response_tests:
283
- self.run_static_request_tests(exchange_id, symbol_argv)
284
- self.run_static_response_tests(exchange_id, symbol_argv)
285
- return
286
- if self.response_tests:
287
- self.run_static_response_tests(exchange_id, symbol_argv)
288
- return
289
- if self.request_tests:
290
- self.run_static_request_tests(exchange_id, symbol_argv) # symbol here is the testname
291
- return
292
- if self.id_tests:
293
- self.run_broker_id_tests()
294
- return
295
- dump(self.new_line + '' + self.new_line + '' + '[INFO] TESTING ', self.ext, {
296
- 'exchange': exchange_id,
297
- 'symbol': symbol_argv,
298
- 'method': method_argv,
299
- 'isWs': self.ws_tests,
300
- }, self.new_line)
301
- exchange_args = {
302
- 'verbose': self.verbose,
303
- 'debug': self.debug,
304
- 'enableRateLimit': True,
305
- 'timeout': 30000,
306
- }
307
- exchange = init_exchange(exchange_id, exchange_args, self.ws_tests)
308
- if exchange.alias:
309
- exit_script(0)
310
- self.import_files(exchange)
311
- assert len(list(self.test_files.keys())) > 0, 'Test files were not loaded' # ensure test files are found & filled
312
- self.expand_settings(exchange)
313
- self.check_if_specific_test_is_chosen(method_argv)
314
- self.start_test(exchange, symbol_argv)
315
- exit_script(0) # needed to be explicitly finished for WS tests
316
-
317
- def check_if_specific_test_is_chosen(self, method_argv):
318
- if method_argv is not None:
319
- test_file_names = list(self.test_files.keys())
320
- possible_method_names = method_argv.split(',') # i.e. `test.ts binance fetchBalance,fetchDeposits`
321
- if len(possible_method_names) >= 1:
322
- for i in range(0, len(test_file_names)):
323
- test_file_name = test_file_names[i]
324
- for j in range(0, len(possible_method_names)):
325
- method_name = possible_method_names[j]
326
- method_name = method_name.replace('()', '')
327
- if test_file_name == method_name:
328
- self.only_specific_tests.append(test_file_name)
329
-
330
- def import_files(self, exchange):
331
- properties = list(exchange.has.keys())
332
- properties.append('loadMarkets')
333
- self.test_files = get_test_files(properties, self.ws_tests)
334
-
335
- def load_credentials_from_env(self, exchange):
336
- exchange_id = exchange.id
337
- 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
338
- objkeys = list(req_creds.keys())
339
- for i in range(0, len(objkeys)):
340
- credential = objkeys[i]
341
- is_required = req_creds[credential]
342
- if is_required and get_exchange_prop(exchange, credential) is None:
343
- full_key = exchange_id + '_' + credential
344
- credential_env_name = full_key.upper() # example: KRAKEN_APIKEY
345
- credential_value = self.env_vars[credential_env_name] if (credential_env_name in self.env_vars) else None
346
- if credential_value:
347
- set_exchange_prop(exchange, credential, credential_value)
348
-
349
- def expand_settings(self, exchange):
350
- exchange_id = exchange.id
351
- keys_global = self.root_dir + 'keys.json'
352
- keys_local = self.root_dir + 'keys.local.json'
353
- keys_global_exists = io_file_exists(keys_global)
354
- keys_local_exists = io_file_exists(keys_local)
355
- global_settings = io_file_read(keys_global) if keys_global_exists else {}
356
- local_settings = io_file_read(keys_local) if keys_local_exists else {}
357
- all_settings = exchange.deep_extend(global_settings, local_settings)
358
- exchange_settings = exchange.safe_value(all_settings, exchange_id, {})
359
- if exchange_settings:
360
- setting_keys = list(exchange_settings.keys())
361
- for i in range(0, len(setting_keys)):
362
- key = setting_keys[i]
363
- if exchange_settings[key]:
364
- final_value = None
365
- if isinstance(exchange_settings[key], dict):
366
- existing = get_exchange_prop(exchange, key, {})
367
- final_value = exchange.deep_extend(existing, exchange_settings[key])
368
- else:
369
- final_value = exchange_settings[key]
370
- set_exchange_prop(exchange, key, final_value)
371
- # credentials
372
- if self.load_keys:
373
- self.load_credentials_from_env(exchange)
374
- # skipped tests
375
- skipped_file = self.root_dir_for_skips + 'skip-tests.json'
376
- skipped_settings = io_file_read(skipped_file)
377
- self.skipped_settings_for_exchange = exchange.safe_value(skipped_settings, exchange_id, {})
378
- skipped_settings_for_exchange = self.skipped_settings_for_exchange
379
- # others
380
- timeout = exchange.safe_value(skipped_settings_for_exchange, 'timeout')
381
- if timeout is not None:
382
- exchange.timeout = exchange.parse_to_int(timeout)
383
- if get_cli_arg_value('--useProxy'):
384
- exchange.http_proxy = exchange.safe_string(skipped_settings_for_exchange, 'httpProxy')
385
- exchange.https_proxy = exchange.safe_string(skipped_settings_for_exchange, 'httpsProxy')
386
- exchange.ws_proxy = exchange.safe_string(skipped_settings_for_exchange, 'wsProxy')
387
- exchange.wss_proxy = exchange.safe_string(skipped_settings_for_exchange, 'wssProxy')
388
- self.skipped_methods = exchange.safe_value(skipped_settings_for_exchange, 'skipMethods', {})
389
- self.checked_public_tests = {}
390
-
391
- def add_padding(self, message, size):
392
- # has to be transpilable
393
- res = ''
394
- message_length = len(message) # avoid php transpilation issue
395
- missing_space = size - message_length - 0 # - 0 is added just to trick transpile to treat the .length as a string for php
396
- if missing_space > 0:
397
- for i in range(0, missing_space):
398
- res += ' '
399
- return message + res
400
-
401
- def exchange_hint(self, exchange, market=None):
402
- market_type = exchange.safe_string_2(exchange.options, 'defaultType', 'type', '')
403
- market_sub_type = exchange.safe_string_2(exchange.options, 'defaultSubType', 'subType')
404
- if market is not None:
405
- market_type = market['type']
406
- if market['linear']:
407
- market_sub_type = 'linear'
408
- elif market['inverse']:
409
- market_sub_type = 'inverse'
410
- elif exchange.safe_value(market, 'quanto'):
411
- market_sub_type = 'quanto'
412
- is_ws = ('ws' in exchange.has)
413
- ws_flag = '(WS)' if is_ws else ''
414
- result = exchange.id + ' ' + ws_flag + ' ' + market_type
415
- if market_sub_type is not None:
416
- result = result + ' [subType: ' + market_sub_type + '] '
417
- return result
418
-
419
- def test_method(self, method_name, exchange, args, is_public):
420
- # todo: temporary skip for c#
421
- if 'OrderBook' in method_name and self.ext == 'cs':
422
- exchange.options['checksum'] = False
423
- # todo: temporary skip for php
424
- if 'OrderBook' in method_name and self.ext == 'php':
425
- return
426
- skipped_properties_for_method = self.get_skips(exchange, method_name)
427
- is_load_markets = (method_name == 'loadMarkets')
428
- is_fetch_currencies = (method_name == 'fetchCurrencies')
429
- is_proxy_test = (method_name == self.proxy_test_file_name)
430
- # 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)
431
- if not is_public and (method_name in self.checked_public_tests) and not is_fetch_currencies:
432
- return
433
- skip_message = None
434
- supported_by_exchange = (method_name in exchange.has) and exchange.has[method_name]
435
- if not is_load_markets and (len(self.only_specific_tests) > 0 and not exchange.in_array(method_name, self.only_specific_tests)):
436
- skip_message = '[INFO] IGNORED_TEST'
437
- elif not is_load_markets and not supported_by_exchange and not is_proxy_test:
438
- skip_message = '[INFO] UNSUPPORTED_TEST' # keep it aligned with the longest message
439
- elif isinstance(skipped_properties_for_method, str):
440
- skip_message = '[INFO] SKIPPED_TEST'
441
- elif not (method_name in self.test_files):
442
- skip_message = '[INFO] UNIMPLEMENTED_TEST'
443
- # 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)
444
- if is_load_markets:
445
- exchange.load_markets(True)
446
- if skip_message:
447
- if self.info:
448
- dump(self.add_padding(skip_message, 25), self.exchange_hint(exchange), method_name)
449
- return
450
- if self.info:
451
- args_stringified = '(' + exchange.json(args) + ')' # args.join() breaks when we provide a list of symbols or multidimensional array; "args.toString()" breaks bcz of "array to string conversion"
452
- dump(self.add_padding('[INFO] TESTING', 25), self.exchange_hint(exchange), method_name, args_stringified)
453
- call_method(self.test_files, method_name, exchange, skipped_properties_for_method, args)
454
- # if it was passed successfully, add to the list of successfull tests
455
- if is_public:
456
- self.checked_public_tests[method_name] = True
457
- return
458
-
459
- def get_skips(self, exchange, method_name):
460
- final_skips = {}
461
- # check the exact method (i.e. `fetchTrades`) and language-specific (i.e. `fetchTrades.php`)
462
- method_names = [method_name, method_name + '.' + self.ext]
463
- for i in range(0, len(method_names)):
464
- m_name = method_names[i]
465
- if m_name in self.skipped_methods:
466
- # if whole method is skipped, by assigning a string to it, i.e. "fetchOrders":"blabla"
467
- if isinstance(self.skipped_methods[m_name], str):
468
- return self.skipped_methods[m_name]
469
- else:
470
- final_skips = exchange.deep_extend(final_skips, self.skipped_methods[m_name])
471
- # get "object-specific" skips
472
- object_skips = {
473
- 'orderBook': ['fetchOrderBook', 'fetchOrderBooks', 'fetchL2OrderBook', 'watchOrderBook', 'watchOrderBookForSymbols'],
474
- 'ticker': ['fetchTicker', 'fetchTickers', 'watchTicker', 'watchTickers'],
475
- 'trade': ['fetchTrades', 'watchTrades', 'watchTradesForSymbols'],
476
- 'ohlcv': ['fetchOHLCV', 'watchOHLCV', 'watchOHLCVForSymbols'],
477
- 'ledger': ['fetchLedger', 'fetchLedgerEntry'],
478
- 'depositWithdraw': ['fetchDepositsWithdrawals', 'fetchDeposits', 'fetchWithdrawals'],
479
- 'depositWithdrawFee': ['fetchDepositWithdrawFee', 'fetchDepositWithdrawFees'],
480
- }
481
- object_names = list(object_skips.keys())
482
- for i in range(0, len(object_names)):
483
- object_name = object_names[i]
484
- object_methods = object_skips[object_name]
485
- if exchange.in_array(method_name, object_methods):
486
- # if whole object is skipped, by assigning a string to it, i.e. "orderBook":"blabla"
487
- if (object_name in self.skipped_methods) and (isinstance(self.skipped_methods[object_name], str)):
488
- return self.skipped_methods[object_name]
489
- extra_skips = exchange.safe_dict(self.skipped_methods, object_name, {})
490
- final_skips = exchange.deep_extend(final_skips, extra_skips)
491
- # extend related skips
492
- # - if 'timestamp' is skipped, we should do so for 'datetime' too
493
- # - if 'bid' is skipped, skip 'ask' too
494
- if ('timestamp' in final_skips) and not ('datetime' in final_skips):
495
- final_skips['datetime'] = final_skips['timestamp']
496
- if ('bid' in final_skips) and not ('ask' in final_skips):
497
- final_skips['ask'] = final_skips['bid']
498
- if ('baseVolume' in final_skips) and not ('quoteVolume' in final_skips):
499
- final_skips['quoteVolume'] = final_skips['baseVolume']
500
- return final_skips
501
-
502
- def test_safe(self, method_name, exchange, args=[], is_public=False):
503
- # `testSafe` method does not throw an exception, instead mutes it. The reason we
504
- # mute the thrown exceptions here is because we don't want to stop the whole
505
- # tests queue if any single test-method fails. Instead, they are echoed with
506
- # formatted message "[TEST_FAILURE] ..." and that output is then regex-matched by
507
- # run-tests.js, so the exceptions are still printed out to console from there.
508
- max_retries = 3
509
- args_stringified = exchange.json(args) # args.join() breaks when we provide a list of symbols | "args.toString()" breaks bcz of "array to string conversion"
510
- for i in range(0, max_retries):
511
- try:
512
- self.test_method(method_name, exchange, args, is_public)
513
- return True
514
- except Exception as e:
515
- is_load_markets = (method_name == 'loadMarkets')
516
- is_auth_error = (isinstance(e, AuthenticationError))
517
- is_not_supported = (isinstance(e, NotSupported))
518
- is_operation_failed = (isinstance(e, OperationFailed)) # includes "DDoSProtection", "RateLimitExceeded", "RequestTimeout", "ExchangeNotAvailable", "OperationFailed", "InvalidNonce", ...
519
- if is_operation_failed:
520
- # if last retry was gone with same `tempFailure` error, then let's eventually return false
521
- if i == max_retries - 1:
522
- is_on_maintenance = (isinstance(e, OnMaintenance))
523
- is_exchange_not_available = (isinstance(e, ExchangeNotAvailable))
524
- should_fail = None
525
- return_success = None
526
- if is_load_markets:
527
- # if "loadMarkets" does not succeed, we must return "false" to caller method, to stop tests continual
528
- return_success = False
529
- # we might not break exchange tests, if exchange is on maintenance at this moment
530
- if is_on_maintenance:
531
- should_fail = False
532
- else:
533
- should_fail = True
534
- else:
535
- # for any other method tests:
536
- if is_exchange_not_available and not is_on_maintenance:
537
- # break exchange tests if "ExchangeNotAvailable" exception is thrown, but it's not maintenance
538
- should_fail = True
539
- return_success = False
540
- else:
541
- # in all other cases of OperationFailed, show Warning, but don't mark test as failed
542
- should_fail = False
543
- return_success = True
544
- # output the message
545
- fail_type = '[TEST_FAILURE]' if should_fail else '[TEST_WARNING]'
546
- dump(fail_type, 'Method could not be tested due to a repeated Network/Availability issues', ' | ', self.exchange_hint(exchange), method_name, args_stringified, exception_message(e))
547
- return return_success
548
- else:
549
- # wait and retry again
550
- # (increase wait time on every retry)
551
- exchange.sleep(i * 1000)
552
- continue
553
- else:
554
- # if it's loadMarkets, then fail test, because it's mandatory for tests
555
- if is_load_markets:
556
- dump('[TEST_FAILURE]', 'Exchange can not load markets', exception_message(e), self.exchange_hint(exchange), method_name, args_stringified)
557
- return False
558
- # if the specific arguments to the test method throws "NotSupported" exception
559
- # then let's don't fail the test
560
- if is_not_supported:
561
- if self.info:
562
- dump('[INFO] NOT_SUPPORTED', exception_message(e), self.exchange_hint(exchange), method_name, args_stringified)
563
- return True
564
- # If public test faces authentication error, we don't break (see comments under `testSafe` method)
565
- if is_public and is_auth_error:
566
- if self.info:
567
- dump('[INFO]', 'Authentication problem for public method', exception_message(e), self.exchange_hint(exchange), method_name, args_stringified)
568
- return True
569
- else:
570
- dump('[TEST_FAILURE]', exception_message(e), self.exchange_hint(exchange), method_name, args_stringified)
571
- return False
572
- return True
573
-
574
- def run_public_tests(self, exchange, symbol):
575
- tests = {
576
- 'fetchCurrencies': [],
577
- 'fetchTicker': [symbol],
578
- 'fetchTickers': [symbol],
579
- 'fetchLastPrices': [symbol],
580
- 'fetchOHLCV': [symbol],
581
- 'fetchTrades': [symbol],
582
- 'fetchOrderBook': [symbol],
583
- 'fetchL2OrderBook': [symbol],
584
- 'fetchOrderBooks': [],
585
- 'fetchBidsAsks': [],
586
- 'fetchStatus': [],
587
- 'fetchTime': [],
588
- }
589
- if self.ws_tests:
590
- tests = {
591
- 'watchOHLCV': [symbol],
592
- 'watchOHLCVForSymbols': [symbol],
593
- 'watchTicker': [symbol],
594
- 'watchTickers': [symbol],
595
- 'watchBidsAsks': [symbol],
596
- 'watchOrderBook': [symbol],
597
- 'watchOrderBookForSymbols': [[symbol]],
598
- 'watchTrades': [symbol],
599
- 'watchTradesForSymbols': [[symbol]],
600
- }
601
- market = exchange.market(symbol)
602
- is_spot = market['spot']
603
- if not self.ws_tests:
604
- if is_spot:
605
- tests['fetchCurrencies'] = []
606
- else:
607
- tests['fetchFundingRates'] = [symbol]
608
- tests['fetchFundingRate'] = [symbol]
609
- tests['fetchFundingRateHistory'] = [symbol]
610
- tests['fetchIndexOHLCV'] = [symbol]
611
- tests['fetchMarkOHLCV'] = [symbol]
612
- tests['fetchPremiumIndexOHLCV'] = [symbol]
613
- self.public_tests = tests
614
- self.run_tests(exchange, tests, True)
615
-
616
- def run_tests(self, exchange, tests, is_public_test):
617
- test_names = list(tests.keys())
618
- promises = []
619
- for i in range(0, len(test_names)):
620
- test_name = test_names[i]
621
- test_args = tests[test_name]
622
- promises.append(self.test_safe(test_name, exchange, test_args, is_public_test))
623
- # todo - not yet ready in other langs too
624
- # promises.push (testThrottle ());
625
- results = (promises)
626
- # now count which test-methods retuned `false` from "testSafe" and dump that info below
627
- failed_methods = []
628
- for i in range(0, len(test_names)):
629
- test_name = test_names[i]
630
- test_returned_value = results[i]
631
- if not test_returned_value:
632
- failed_methods.append(test_name)
633
- test_prefix_string = 'PUBLIC_TESTS' if is_public_test else 'PRIVATE_TESTS'
634
- if len(failed_methods):
635
- errors_string = ', '.join(failed_methods)
636
- dump('[TEST_FAILURE]', self.exchange_hint(exchange), test_prefix_string, 'Failed methods : ' + errors_string)
637
- if self.info:
638
- dump(self.add_padding('[INFO] END ' + test_prefix_string + ' ' + self.exchange_hint(exchange), 25))
639
-
640
- def load_exchange(self, exchange):
641
- result = self.test_safe('loadMarkets', exchange, [], True)
642
- if not result:
643
- return False
644
- exchange_symbols_length = len(exchange.symbols)
645
- dump('[INFO:MAIN] Exchange loaded', exchange_symbols_length, 'symbols')
646
- return True
647
-
648
- def get_test_symbol(self, exchange, is_spot, symbols):
649
- symbol = None
650
- preferred_spot_symbol = exchange.safe_string(self.skipped_settings_for_exchange, 'preferredSpotSymbol')
651
- preferred_swap_symbol = exchange.safe_string(self.skipped_settings_for_exchange, 'preferredSwapSymbol')
652
- if is_spot and preferred_spot_symbol:
653
- return preferred_spot_symbol
654
- elif not is_spot and preferred_swap_symbol:
655
- return preferred_swap_symbol
656
- for i in range(0, len(symbols)):
657
- s = symbols[i]
658
- market = exchange.safe_value(exchange.markets, s)
659
- if market is not None:
660
- active = exchange.safe_value(market, 'active')
661
- if active or (active is None):
662
- symbol = s
663
- break
664
- return symbol
665
-
666
- def get_exchange_code(self, exchange, codes=None):
667
- if codes is None:
668
- codes = ['BTC', 'ETH', 'XRP', 'LTC', 'BCH', 'EOS', 'BNB', 'BSV', 'USDT']
669
- code = codes[0]
670
- for i in range(0, len(codes)):
671
- if codes[i] in exchange.currencies:
672
- return codes[i]
673
- return code
674
-
675
- def get_markets_from_exchange(self, exchange, spot=True):
676
- res = {}
677
- markets = exchange.markets
678
- keys = list(markets.keys())
679
- for i in range(0, len(keys)):
680
- key = keys[i]
681
- market = markets[key]
682
- if spot and market['spot']:
683
- res[market['symbol']] = market
684
- elif not spot and not market['spot']:
685
- res[market['symbol']] = market
686
- return res
687
-
688
- def get_valid_symbol(self, exchange, spot=True):
689
- current_type_markets = self.get_markets_from_exchange(exchange, spot)
690
- codes = ['BTC', 'ETH', 'XRP', 'LTC', 'BNB', 'DASH', 'DOGE', 'ETC', 'TRX', 'USDT', 'USDC', 'USD', 'EUR', 'TUSD', 'CNY', 'JPY', 'BRL']
691
- spot_symbols = ['BTC/USDT', 'BTC/USDC', 'BTC/USD', 'BTC/CNY', 'BTC/EUR', 'BTC/AUD', 'BTC/BRL', 'BTC/JPY', 'ETH/USDT', 'ETH/USDC', 'ETH/USD', 'ETH/CNY', 'ETH/EUR', 'ETH/AUD', 'ETH/BRL', 'ETH/JPY', 'EUR/USDT', 'EUR/USD', 'EUR/USDC', 'USDT/EUR', 'USD/EUR', 'USDC/EUR', 'BTC/ETH', 'ETH/BTC']
692
- swap_symbols = ['BTC/USDT:USDT', 'BTC/USDC:USDC', 'BTC/USD:USD', 'ETH/USDT:USDT', 'ETH/USDC:USDC', 'ETH/USD:USD', 'BTC/USD:BTC', 'ETH/USD:ETH']
693
- target_symbols = spot_symbols if spot else swap_symbols
694
- symbol = self.get_test_symbol(exchange, spot, target_symbols)
695
- # if symbols wasn't found from above hardcoded list, then try to locate any symbol which has our target hardcoded 'base' code
696
- if symbol is None:
697
- for i in range(0, len(codes)):
698
- current_code = codes[i]
699
- markets_array_for_current_code = exchange.filter_by(current_type_markets, 'base', current_code)
700
- indexed_mkts = exchange.index_by(markets_array_for_current_code, 'symbol')
701
- symbols_array_for_current_code = list(indexed_mkts.keys())
702
- symbols_length = len(symbols_array_for_current_code)
703
- if symbols_length:
704
- symbol = self.get_test_symbol(exchange, spot, symbols_array_for_current_code)
705
- break
706
- # if there wasn't found any symbol with our hardcoded 'base' code, then just try to find symbols that are 'active'
707
- if symbol is None:
708
- active_markets = exchange.filter_by(current_type_markets, 'active', True)
709
- active_symbols = []
710
- for i in range(0, len(active_markets)):
711
- active_symbols.append(active_markets[i]['symbol'])
712
- symbol = self.get_test_symbol(exchange, spot, active_symbols)
713
- if symbol is None:
714
- values = list(current_type_markets.values())
715
- values_length = len(values)
716
- if values_length > 0:
717
- first = values[0]
718
- if first is not None:
719
- symbol = first['symbol']
720
- return symbol
721
-
722
- def test_exchange(self, exchange, provided_symbol=None):
723
- spot_symbol = None
724
- swap_symbol = None
725
- if provided_symbol is not None:
726
- market = exchange.market(provided_symbol)
727
- if market['spot']:
728
- spot_symbol = provided_symbol
729
- else:
730
- swap_symbol = provided_symbol
731
- else:
732
- if exchange.has['spot']:
733
- spot_symbol = self.get_valid_symbol(exchange, True)
734
- if exchange.has['swap']:
735
- swap_symbol = self.get_valid_symbol(exchange, False)
736
- if spot_symbol is not None:
737
- dump('[INFO:MAIN] Selected SPOT SYMBOL:', spot_symbol)
738
- if swap_symbol is not None:
739
- dump('[INFO:MAIN] Selected SWAP SYMBOL:', swap_symbol)
740
- if not self.private_test_only:
741
- # note, spot & swap tests should run sequentially, because of conflicting `exchange.options['defaultType']` setting
742
- if exchange.has['spot'] and spot_symbol is not None:
743
- if self.info:
744
- dump('[INFO] ### SPOT TESTS ###')
745
- exchange.options['defaultType'] = 'spot'
746
- self.run_public_tests(exchange, spot_symbol)
747
- if exchange.has['swap'] and swap_symbol is not None:
748
- if self.info:
749
- dump('[INFO] ### SWAP TESTS ###')
750
- exchange.options['defaultType'] = 'swap'
751
- self.run_public_tests(exchange, swap_symbol)
752
- if self.private_test or self.private_test_only:
753
- if exchange.has['spot'] and spot_symbol is not None:
754
- exchange.options['defaultType'] = 'spot'
755
- self.run_private_tests(exchange, spot_symbol)
756
- if exchange.has['swap'] and swap_symbol is not None:
757
- exchange.options['defaultType'] = 'swap'
758
- self.run_private_tests(exchange, swap_symbol)
759
-
760
- def run_private_tests(self, exchange, symbol):
761
- if not exchange.check_required_credentials(False):
762
- dump('[INFO] Skipping private tests', 'Keys not found')
763
- return
764
- code = self.get_exchange_code(exchange)
765
- # if (exchange.deepExtendedTest) {
766
- # test ('InvalidNonce', exchange, symbol);
767
- # test ('OrderNotFound', exchange, symbol);
768
- # test ('InvalidOrder', exchange, symbol);
769
- # test ('InsufficientFunds', exchange, symbol, balance); # danger zone - won't execute with non-empty balance
770
- # }
771
- tests = {
772
- 'signIn': [],
773
- 'fetchBalance': [],
774
- 'fetchAccounts': [],
775
- 'fetchTransactionFees': [],
776
- 'fetchTradingFees': [],
777
- 'fetchStatus': [],
778
- 'fetchOrders': [symbol],
779
- 'fetchOpenOrders': [symbol],
780
- 'fetchClosedOrders': [symbol],
781
- 'fetchMyTrades': [symbol],
782
- 'fetchLeverageTiers': [[symbol]],
783
- 'fetchLedger': [code],
784
- 'fetchTransactions': [code],
785
- 'fetchDeposits': [code],
786
- 'fetchWithdrawals': [code],
787
- 'fetchBorrowInterest': [code, symbol],
788
- 'cancelAllOrders': [symbol],
789
- 'fetchCanceledOrders': [symbol],
790
- 'fetchMarginModes': [symbol],
791
- 'fetchPosition': [symbol],
792
- 'fetchDeposit': [code],
793
- 'createDepositAddress': [code],
794
- 'fetchDepositAddress': [code],
795
- 'fetchDepositAddresses': [code],
796
- 'fetchDepositAddressesByNetwork': [code],
797
- 'fetchBorrowRateHistory': [code],
798
- 'fetchLedgerEntry': [code],
799
- }
800
- if self.ws_tests:
801
- tests = {
802
- 'watchBalance': [code],
803
- 'watchMyTrades': [symbol],
804
- 'watchOrders': [symbol],
805
- 'watchPosition': [symbol],
806
- 'watchPositions': [symbol],
807
- }
808
- market = exchange.market(symbol)
809
- is_spot = market['spot']
810
- if not self.ws_tests:
811
- if is_spot:
812
- tests['fetchCurrencies'] = []
813
- else:
814
- # derivatives only
815
- tests['fetchPositions'] = [symbol] # this test fetches all positions for 1 symbol
816
- tests['fetchPosition'] = [symbol]
817
- tests['fetchPositionRisk'] = [symbol]
818
- tests['setPositionMode'] = [symbol]
819
- tests['setMarginMode'] = [symbol]
820
- tests['fetchOpenInterestHistory'] = [symbol]
821
- tests['fetchFundingRateHistory'] = [symbol]
822
- tests['fetchFundingHistory'] = [symbol]
823
- # const combinedTests = exchange.deepExtend (this.publicTests, privateTests);
824
- self.run_tests(exchange, tests, False)
825
-
826
- def test_proxies(self, exchange):
827
- # these tests should be synchronously executed, because of conflicting nature of proxy settings
828
- proxy_test_name = self.proxy_test_file_name
829
- # todo: temporary skip for sync py
830
- if self.ext == 'py' and self.is_synchronous:
831
- return
832
- # try proxy several times
833
- max_retries = 3
834
- exception = None
835
- for j in range(0, max_retries):
836
- try:
837
- self.test_method(proxy_test_name, exchange, [], True)
838
- break # if successfull, then break
839
- except Exception as e:
840
- exception = e
841
- # if exception was set, then throw it
842
- if exception:
843
- error_message = '[TEST_FAILURE] Failed ' + proxy_test_name + ' : ' + exception_message(exception)
844
-
845
- def start_test(self, exchange, symbol):
846
- # we do not need to test aliases
847
- if exchange.alias:
848
- return
849
- if self.sandbox or get_exchange_prop(exchange, 'sandbox'):
850
- exchange.set_sandbox_mode(True)
851
- try:
852
- result = self.load_exchange(exchange)
853
- if not result:
854
- close(exchange)
855
- return
856
- # if (exchange.id === 'binance') {
857
- # # we test proxies functionality just for one random exchange on each build, because proxy functionality is not exchange-specific, instead it's all done from base methods, so just one working sample would mean it works for all ccxt exchanges
858
- # # this.testProxies (exchange);
859
- # }
860
- self.test_exchange(exchange, symbol)
861
- close(exchange)
862
- except Exception as e:
863
- close(exchange)
864
- raise e
865
-
866
- def assert_static_error(self, cond, message, calculated_output, stored_output, key=None):
867
- # -----------------------------------------------------------------------------
868
- # --- Init of static tests functions------------------------------------------
869
- # -----------------------------------------------------------------------------
870
- calculated_string = json_stringify(calculated_output)
871
- stored_string = json_stringify(stored_output)
872
- error_message = message + ' computed ' + stored_string + ' stored: ' + calculated_string
873
- if key is not None:
874
- error_message = ' | ' + key + ' | ' + 'computed value: ' + stored_string + ' stored value: ' + calculated_string
875
- assert cond, error_message
876
-
877
- def load_markets_from_file(self, id):
878
- # load markets from file
879
- # to make this test as fast as possible
880
- # and basically independent from the exchange
881
- # so we can run it offline
882
- filename = self.root_dir + './ts/src/test/static/markets/' + id + '.json'
883
- content = io_file_read(filename)
884
- return content
885
-
886
- def load_currencies_from_file(self, id):
887
- filename = self.root_dir + './ts/src/test/static/currencies/' + id + '.json'
888
- content = io_file_read(filename)
889
- return content
890
-
891
- def load_static_data(self, folder, target_exchange=None):
892
- result = {}
893
- if target_exchange:
894
- # read a single exchange
895
- path = folder + target_exchange + '.json'
896
- if not io_file_exists(path):
897
- dump('[WARN] tests not found: ' + path)
898
- return None
899
- result[target_exchange] = io_file_read(path)
900
- return result
901
- files = io_dir_read(folder)
902
- for i in range(0, len(files)):
903
- file = files[i]
904
- exchange_name = file.replace('.json', '')
905
- content = io_file_read(folder + file)
906
- result[exchange_name] = content
907
- return result
908
-
909
- def remove_hostnamefrom_url(self, url):
910
- if url is None:
911
- return None
912
- url_parts = url.split('/')
913
- res = ''
914
- for i in range(0, len(url_parts)):
915
- if i > 2:
916
- current = url_parts[i]
917
- if current.find('?') > -1:
918
- # handle urls like this: /v1/account/accounts?AccessK
919
- current_parts = current.split('?')
920
- res += '/'
921
- res += current_parts[0]
922
- break
923
- res += '/'
924
- res += current
925
- return res
926
-
927
- def urlencoded_to_dict(self, url):
928
- result = {}
929
- parts = url.split('&')
930
- for i in range(0, len(parts)):
931
- part = parts[i]
932
- key_value = part.split('=')
933
- keys_length = len(key_value)
934
- if keys_length != 2:
935
- continue
936
- key = key_value[0]
937
- value = key_value[1]
938
- if (value is not None) and ((value.startswith('[')) or (value.startswith('{'))):
939
- # some exchanges might return something like this: timestamp=1699382693405&batchOrders=[{\"symbol\":\"LTCUSDT\",\"side\":\"BUY\",\"newClientOrderI
940
- value = json_parse(value)
941
- result[key] = value
942
- return result
943
-
944
- def assert_new_and_stored_output(self, exchange, skip_keys, new_output, stored_output, strict_type_check=True, asserting_key=None):
945
- if is_null_value(new_output) and is_null_value(stored_output):
946
- return True
947
- if not new_output and not stored_output:
948
- return True
949
- if (isinstance(stored_output, dict)) and (isinstance(new_output, dict)):
950
- stored_output_keys = list(stored_output.keys())
951
- new_output_keys = list(new_output.keys())
952
- stored_keys_length = len(stored_output_keys)
953
- new_keys_length = len(new_output_keys)
954
- self.assert_static_error(stored_keys_length == new_keys_length, 'output length mismatch', stored_output, new_output)
955
- # iterate over the keys
956
- for i in range(0, len(stored_output_keys)):
957
- key = stored_output_keys[i]
958
- if exchange.in_array(key, skip_keys):
959
- continue
960
- if not (exchange.in_array(key, new_output_keys)):
961
- self.assert_static_error(False, 'output key missing: ' + key, stored_output, new_output)
962
- stored_value = stored_output[key]
963
- new_value = new_output[key]
964
- self.assert_new_and_stored_output(exchange, skip_keys, new_value, stored_value, strict_type_check, key)
965
- elif isinstance(stored_output, list) and (isinstance(new_output, list)):
966
- stored_array_length = len(stored_output)
967
- new_array_length = len(new_output)
968
- self.assert_static_error(stored_array_length == new_array_length, 'output length mismatch', stored_output, new_output)
969
- for i in range(0, len(stored_output)):
970
- stored_item = stored_output[i]
971
- new_item = new_output[i]
972
- self.assert_new_and_stored_output(exchange, skip_keys, new_item, stored_item, strict_type_check)
973
- else:
974
- # built-in types like strings, numbers, booleans
975
- 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
976
- sanitized_stored_output = None if (not stored_output) else stored_output
977
- new_output_string = str(sanitized_new_output) if sanitized_new_output else 'undefined'
978
- stored_output_string = str(sanitized_stored_output) if sanitized_stored_output else 'undefined'
979
- message_error = 'output value mismatch:' + new_output_string + ' != ' + stored_output_string
980
- if strict_type_check and (self.lang != 'C#'):
981
- # upon building the request we want strict type check to make sure all the types are correct
982
- # 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
983
- self.assert_static_error(sanitized_new_output == sanitized_stored_output, message_error, stored_output, new_output, asserting_key)
984
- else:
985
- is_boolean = (isinstance(sanitized_new_output, bool)) or (isinstance(sanitized_stored_output, bool))
986
- is_string = (isinstance(sanitized_new_output, str)) or (isinstance(sanitized_stored_output, str))
987
- is_undefined = (sanitized_new_output is None) or (sanitized_stored_output is None) # undefined is a perfetly valid value
988
- if is_boolean or is_string or is_undefined:
989
- if self.lang == 'C#':
990
- # tmp c# number comparsion
991
- is_number = False
992
- try:
993
- exchange.parse_to_numeric(sanitized_new_output)
994
- is_number = True
995
- except Exception as e:
996
- # if we can't parse it to number, then it's not a number
997
- is_number = False
998
- if is_number:
999
- self.assert_static_error(exchange.parse_to_numeric(sanitized_new_output) == exchange.parse_to_numeric(sanitized_stored_output), message_error, stored_output, new_output, asserting_key)
1000
- return True
1001
- else:
1002
- self.assert_static_error(convert_ascii(new_output_string) == convert_ascii(stored_output_string), message_error, stored_output, new_output, asserting_key)
1003
- return True
1004
- else:
1005
- self.assert_static_error(convert_ascii(new_output_string) == convert_ascii(stored_output_string), message_error, stored_output, new_output, asserting_key)
1006
- return True
1007
- else:
1008
- if self.lang == 'C#':
1009
- stringified_new_output = exchange.number_to_string(sanitized_new_output)
1010
- stringified_stored_output = exchange.number_to_string(sanitized_stored_output)
1011
- self.assert_static_error(str(stringified_new_output) == str(stringified_stored_output), message_error, stored_output, new_output, asserting_key)
1012
- else:
1013
- numeric_new_output = exchange.parse_to_numeric(new_output_string)
1014
- numeric_stored_output = exchange.parse_to_numeric(stored_output_string)
1015
- self.assert_static_error(numeric_new_output == numeric_stored_output, message_error, stored_output, new_output, asserting_key)
1016
- return True # c# requ
1017
-
1018
- def assert_static_request_output(self, exchange, type, skip_keys, stored_url, request_url, stored_output, new_output):
1019
- if stored_url != request_url:
1020
- # remove the host part from the url
1021
- first_path = self.remove_hostnamefrom_url(stored_url)
1022
- second_path = self.remove_hostnamefrom_url(request_url)
1023
- self.assert_static_error(first_path == second_path, 'url mismatch', first_path, second_path)
1024
- # body (aka storedOutput and newOutput) is not defined and information is in the url
1025
- # example: "https://open-api.bingx.com/openApi/spot/v1/trade/order?quoteOrderQty=5&side=BUY&symbol=LTC-USDT&timestamp=1698777135343&type=MARKET&signature=d55a7e4f7f9dbe56c4004c9f3ab340869d3cb004e2f0b5b861e5fbd1762fd9a0
1026
- if (stored_output is None) and (new_output is None):
1027
- if (stored_url is not None) and (request_url is not None):
1028
- stored_url_parts = stored_url.split('?')
1029
- new_url_parts = request_url.split('?')
1030
- stored_url_query = exchange.safe_value(stored_url_parts, 1)
1031
- new_url_query = exchange.safe_value(new_url_parts, 1)
1032
- if (stored_url_query is None) and (new_url_query is None):
1033
- # might be a get request without any query parameters
1034
- # example: https://api.gateio.ws/api/v4/delivery/usdt/positions
1035
- return
1036
- stored_url_params = self.urlencoded_to_dict(stored_url_query)
1037
- new_url_params = self.urlencoded_to_dict(new_url_query)
1038
- self.assert_new_and_stored_output(exchange, skip_keys, new_url_params, stored_url_params)
1039
- return
1040
- if type == 'json' and (stored_output is not None) and (new_output is not None):
1041
- if isinstance(stored_output, str):
1042
- stored_output = json_parse(stored_output)
1043
- if isinstance(new_output, str):
1044
- new_output = json_parse(new_output)
1045
- elif type == 'urlencoded' and (stored_output is not None) and (new_output is not None):
1046
- stored_output = self.urlencoded_to_dict(stored_output)
1047
- new_output = self.urlencoded_to_dict(new_output)
1048
- elif type == 'both':
1049
- if stored_output.startswith('{') or stored_output.startswith('['):
1050
- stored_output = json_parse(stored_output)
1051
- new_output = json_parse(new_output)
1052
- else:
1053
- stored_output = self.urlencoded_to_dict(stored_output)
1054
- new_output = self.urlencoded_to_dict(new_output)
1055
- self.assert_new_and_stored_output(exchange, skip_keys, new_output, stored_output)
1056
-
1057
- def assert_static_response_output(self, exchange, skip_keys, computed_result, stored_result):
1058
- self.assert_new_and_stored_output(exchange, skip_keys, computed_result, stored_result, False)
1059
-
1060
- def sanitize_data_input(self, input):
1061
- # remove nulls and replace with unefined instead
1062
- if input is None:
1063
- return None
1064
- new_input = []
1065
- for i in range(0, len(input)):
1066
- current = input[i]
1067
- if is_null_value(current):
1068
- new_input.append(None)
1069
- else:
1070
- new_input.append(current)
1071
- return new_input
1072
-
1073
- def test_method_statically(self, exchange, method, data, type, skip_keys):
1074
- output = None
1075
- request_url = None
1076
- try:
1077
- call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
1078
- except Exception as e:
1079
- if not (isinstance(e, ProxyError)):
1080
- raise e
1081
- output = exchange.last_request_body
1082
- request_url = exchange.last_request_url
1083
- try:
1084
- call_output = exchange.safe_value(data, 'output')
1085
- self.assert_static_request_output(exchange, type, skip_keys, data['url'], request_url, call_output, output)
1086
- except Exception as e:
1087
- self.request_tests_failed = True
1088
- error_message = '[' + self.lang + '][STATIC_REQUEST_TEST_FAILURE]' + '[' + self.exchange_hint(exchange) + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
1089
- dump('[TEST_FAILURE]' + error_message)
1090
-
1091
- def test_response_statically(self, exchange, method, skip_keys, data):
1092
- expected_result = exchange.safe_value(data, 'parsedResponse')
1093
- mocked_exchange = set_fetch_response(exchange, data['httpResponse'])
1094
- try:
1095
- unified_result = call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
1096
- self.assert_static_response_output(mocked_exchange, skip_keys, unified_result, expected_result)
1097
- except Exception as e:
1098
- self.request_tests_failed = True
1099
- error_message = '[' + self.lang + '][STATIC_RESPONSE_TEST_FAILURE]' + '[' + self.exchange_hint(exchange) + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
1100
- dump('[TEST_FAILURE]' + error_message)
1101
- set_fetch_response(exchange, None) # reset state
1102
-
1103
- def init_offline_exchange(self, exchange_name):
1104
- markets = self.load_markets_from_file(exchange_name)
1105
- currencies = self.load_currencies_from_file(exchange_name)
1106
- exchange = init_exchange(exchange_name, {
1107
- 'markets': markets,
1108
- 'currencies': currencies,
1109
- 'enableRateLimit': False,
1110
- 'rateLimit': 1,
1111
- 'httpProxy': 'http://fake:8080',
1112
- 'httpsProxy': 'http://fake:8080',
1113
- 'apiKey': 'key',
1114
- 'secret': 'secretsecret',
1115
- 'password': 'password',
1116
- 'walletAddress': 'wallet',
1117
- 'privateKey': '0xff3bdd43534543d421f05aec535965b5050ad6ac15345435345435453495e771',
1118
- 'uid': 'uid',
1119
- 'token': 'token',
1120
- 'accountId': 'accountId',
1121
- 'accounts': [{
1122
- 'id': 'myAccount',
1123
- 'code': 'USDT',
1124
- }, {
1125
- 'id': 'myAccount',
1126
- 'code': 'USDC',
1127
- }],
1128
- 'options': {
1129
- 'enableUnifiedAccount': True,
1130
- 'enableUnifiedMargin': False,
1131
- 'accessToken': 'token',
1132
- 'expires': 999999999999999,
1133
- 'leverageBrackets': {},
1134
- },
1135
- })
1136
- exchange.currencies = currencies # not working in python if assigned in the config dict
1137
- return exchange
1138
-
1139
- def test_exchange_request_statically(self, exchange_name, exchange_data, test_name=None):
1140
- # instantiate the exchange and make sure that we sink the requests to avoid an actual request
1141
- exchange = self.init_offline_exchange(exchange_name)
1142
- global_options = exchange.safe_dict(exchange_data, 'options', {})
1143
- # read apiKey/secret from the test file
1144
- api_key = exchange.safe_string(exchange_data, 'apiKey')
1145
- if api_key:
1146
- exchange.api_key = str(api_key)
1147
- secret = exchange.safe_string(exchange_data, 'secret')
1148
- if secret:
1149
- exchange.secret = str(secret)
1150
- # exchange.options = exchange.deepExtend (exchange.options, globalOptions); # custom options to be used in the tests
1151
- exchange.extend_exchange_options(global_options)
1152
- methods = exchange.safe_value(exchange_data, 'methods', {})
1153
- methods_names = list(methods.keys())
1154
- for i in range(0, len(methods_names)):
1155
- method = methods_names[i]
1156
- results = methods[method]
1157
- for j in range(0, len(results)):
1158
- result = results[j]
1159
- old_exchange_options = exchange.options # snapshot options;
1160
- test_exchange_options = exchange.safe_value(result, 'options', {})
1161
- # exchange.options = exchange.deepExtend (oldExchangeOptions, testExchangeOptions); # custom options to be used in the tests
1162
- exchange.extend_exchange_options(exchange.deep_extend(old_exchange_options, test_exchange_options))
1163
- description = exchange.safe_value(result, 'description')
1164
- if (test_name is not None) and (test_name != description):
1165
- continue
1166
- is_disabled = exchange.safe_bool(result, 'disabled', False)
1167
- if is_disabled:
1168
- continue
1169
- type = exchange.safe_string(exchange_data, 'outputType')
1170
- skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
1171
- self.test_method_statically(exchange, method, result, type, skip_keys)
1172
- # reset options
1173
- # exchange.options = exchange.deepExtend (oldExchangeOptions, {});
1174
- exchange.extend_exchange_options(exchange.deep_extend(old_exchange_options, {}))
1175
- close(exchange)
1176
- return True # in c# methods that will be used with promiseAll need to return something
1177
-
1178
- def test_exchange_response_statically(self, exchange_name, exchange_data, test_name=None):
1179
- exchange = self.init_offline_exchange(exchange_name)
1180
- # read apiKey/secret from the test file
1181
- api_key = exchange.safe_string(exchange_data, 'apiKey')
1182
- if api_key:
1183
- exchange.api_key = str(api_key)
1184
- secret = exchange.safe_string(exchange_data, 'secret')
1185
- if secret:
1186
- exchange.secret = str(secret)
1187
- methods = exchange.safe_value(exchange_data, 'methods', {})
1188
- options = exchange.safe_value(exchange_data, 'options', {})
1189
- # exchange.options = exchange.deepExtend (exchange.options, options); # custom options to be used in the tests
1190
- exchange.extend_exchange_options(options)
1191
- methods_names = list(methods.keys())
1192
- for i in range(0, len(methods_names)):
1193
- method = methods_names[i]
1194
- results = methods[method]
1195
- for j in range(0, len(results)):
1196
- result = results[j]
1197
- description = exchange.safe_value(result, 'description')
1198
- old_exchange_options = exchange.options # snapshot options;
1199
- test_exchange_options = exchange.safe_value(result, 'options', {})
1200
- # exchange.options = exchange.deepExtend (oldExchangeOptions, testExchangeOptions); # custom options to be used in the tests
1201
- exchange.extend_exchange_options(exchange.deep_extend(old_exchange_options, test_exchange_options))
1202
- is_disabled = exchange.safe_bool(result, 'disabled', False)
1203
- if is_disabled:
1204
- continue
1205
- is_disabled_c_sharp = exchange.safe_bool(result, 'disabledCS', False)
1206
- if is_disabled_c_sharp and (self.lang == 'C#'):
1207
- continue
1208
- is_disabled_php = exchange.safe_bool(result, 'disabledPHP', False)
1209
- if is_disabled_php and (self.lang == 'PHP'):
1210
- continue
1211
- if (test_name is not None) and (test_name != description):
1212
- continue
1213
- skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
1214
- self.test_response_statically(exchange, method, skip_keys, result)
1215
- # reset options
1216
- # exchange.options = exchange.deepExtend (oldExchangeOptions, {});
1217
- exchange.extend_exchange_options(exchange.deep_extend(old_exchange_options, {}))
1218
- close(exchange)
1219
- return True # in c# methods that will be used with promiseAll need to return something
1220
-
1221
- def get_number_of_tests_from_exchange(self, exchange, exchange_data, test_name=None):
1222
- if test_name is not None:
1223
- return 1
1224
- sum = 0
1225
- methods = exchange_data['methods']
1226
- methods_names = list(methods.keys())
1227
- for i in range(0, len(methods_names)):
1228
- method = methods_names[i]
1229
- results = methods[method]
1230
- results_length = len(results)
1231
- sum = exchange.sum(sum, results_length)
1232
- return sum
1233
-
1234
- def run_static_request_tests(self, target_exchange=None, test_name=None):
1235
- self.run_static_tests('request', target_exchange, test_name)
1236
-
1237
- def run_static_tests(self, type, target_exchange=None, test_name=None):
1238
- folder = self.root_dir + './ts/src/test/static/' + type + '/'
1239
- static_data = self.load_static_data(folder, target_exchange)
1240
- if static_data is None:
1241
- return
1242
- exchanges = list(static_data.keys())
1243
- exchange = init_exchange('Exchange', {}) # tmp to do the calculations until we have the ast-transpiler transpiling this code
1244
- promises = []
1245
- sum = 0
1246
- if target_exchange:
1247
- dump('[INFO:MAIN] Exchange to test: ' + target_exchange)
1248
- if test_name:
1249
- dump('[INFO:MAIN] Testing only: ' + test_name)
1250
- for i in range(0, len(exchanges)):
1251
- exchange_name = exchanges[i]
1252
- exchange_data = static_data[exchange_name]
1253
- number_of_tests = self.get_number_of_tests_from_exchange(exchange, exchange_data, test_name)
1254
- sum = exchange.sum(sum, number_of_tests)
1255
- if type == 'request':
1256
- promises.append(self.test_exchange_request_statically(exchange_name, exchange_data, test_name))
1257
- else:
1258
- promises.append(self.test_exchange_response_statically(exchange_name, exchange_data, test_name))
1259
- (promises)
1260
- if self.request_tests_failed or self.response_tests_failed:
1261
- exit_script(1)
1262
- else:
1263
- success_message = '[' + self.lang + '][TEST_SUCCESS] ' + str(sum) + ' static ' + type + ' tests passed.'
1264
- dump('[INFO]' + success_message)
1265
-
1266
- def run_static_response_tests(self, exchange_name=None, test=None):
1267
- # -----------------------------------------------------------------------------
1268
- # --- Init of mockResponses tests functions------------------------------------
1269
- # -----------------------------------------------------------------------------
1270
- self.run_static_tests('response', exchange_name, test)
1271
-
1272
- def run_broker_id_tests(self):
1273
- # -----------------------------------------------------------------------------
1274
- # --- Init of brokerId tests functions-----------------------------------------
1275
- # -----------------------------------------------------------------------------
1276
- 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_htx(), self.test_woo(), self.test_bitmart(), self.test_coinex(), self.test_bingx(), self.test_phemex(), self.test_blofin(), self.test_hyperliquid(), self.test_coinbaseinternational(), self.test_coinbase_advanced(), self.test_woofi_pro(), self.test_oxfun(), self.test_xt()]
1277
- (promises)
1278
- success_message = '[' + self.lang + '][TEST_SUCCESS] brokerId tests passed.'
1279
- dump('[INFO]' + success_message)
1280
- exit_script(0)
1281
-
1282
- def test_binance(self):
1283
- exchange = self.init_offline_exchange('binance')
1284
- spot_id = 'x-R4BD3S82'
1285
- spot_order_request = None
1286
- try:
1287
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1288
- except Exception as e:
1289
- spot_order_request = self.urlencoded_to_dict(exchange.last_request_body)
1290
- client_order_id = spot_order_request['newClientOrderId']
1291
- spot_id_string = str(spot_id)
1292
- assert client_order_id.startswith(spot_id_string), 'binance - spot clientOrderId: ' + client_order_id + ' does not start with spotId' + spot_id_string
1293
- swap_id = 'x-xcKtGhcu'
1294
- swap_order_request = None
1295
- try:
1296
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1297
- except Exception as e:
1298
- swap_order_request = self.urlencoded_to_dict(exchange.last_request_body)
1299
- swap_inverse_order_request = None
1300
- try:
1301
- exchange.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
1302
- except Exception as e:
1303
- swap_inverse_order_request = self.urlencoded_to_dict(exchange.last_request_body)
1304
- client_order_id_swap = swap_order_request['newClientOrderId']
1305
- swap_id_string = str(swap_id)
1306
- assert client_order_id_swap.startswith(swap_id_string), 'binance - swap clientOrderId: ' + client_order_id_swap + ' does not start with swapId' + swap_id_string
1307
- client_order_id_inverse = swap_inverse_order_request['newClientOrderId']
1308
- assert client_order_id_inverse.startswith(swap_id_string), 'binance - swap clientOrderIdInverse: ' + client_order_id_inverse + ' does not start with swapId' + swap_id_string
1309
- close(exchange)
1310
- return True
1311
-
1312
- def test_okx(self):
1313
- exchange = self.init_offline_exchange('okx')
1314
- id = 'e847386590ce4dBC'
1315
- spot_order_request = None
1316
- try:
1317
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1318
- except Exception as e:
1319
- spot_order_request = json_parse(exchange.last_request_body)
1320
- client_order_id = spot_order_request[0]['clOrdId'] # returns order inside array
1321
- id_string = str(id)
1322
- assert client_order_id.startswith(id_string), 'okx - spot clientOrderId: ' + client_order_id + ' does not start with id: ' + id_string
1323
- spot_tag = spot_order_request[0]['tag']
1324
- assert spot_tag == id, 'okx - id: ' + id + ' different from spot tag: ' + spot_tag
1325
- swap_order_request = None
1326
- try:
1327
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1328
- except Exception as e:
1329
- swap_order_request = json_parse(exchange.last_request_body)
1330
- client_order_id_swap = swap_order_request[0]['clOrdId']
1331
- assert client_order_id_swap.startswith(id_string), 'okx - swap clientOrderId: ' + client_order_id_swap + ' does not start with id: ' + id_string
1332
- swap_tag = swap_order_request[0]['tag']
1333
- assert swap_tag == id, 'okx - id: ' + id + ' different from swap tag: ' + swap_tag
1334
- close(exchange)
1335
- return True
1336
-
1337
- def test_cryptocom(self):
1338
- exchange = self.init_offline_exchange('cryptocom')
1339
- id = 'CCXT'
1340
- exchange.load_markets()
1341
- request = None
1342
- try:
1343
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1344
- except Exception as e:
1345
- request = json_parse(exchange.last_request_body)
1346
- broker_id = request['params']['broker_id']
1347
- assert broker_id == id, 'cryptocom - id: ' + id + ' different from broker_id: ' + broker_id
1348
- close(exchange)
1349
- return True
1350
-
1351
- def test_bybit(self):
1352
- exchange = self.init_offline_exchange('bybit')
1353
- req_headers = None
1354
- id = 'CCXT'
1355
- assert exchange.options['brokerId'] == id, 'id not in options'
1356
- try:
1357
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1358
- except Exception as e:
1359
- # we expect an error here, we're only interested in the headers
1360
- req_headers = exchange.last_request_headers
1361
- assert req_headers['Referer'] == id, 'bybit - id: ' + id + ' not in headers.'
1362
- close(exchange)
1363
- return True
1364
-
1365
- def test_kucoin(self):
1366
- exchange = self.init_offline_exchange('kucoin')
1367
- req_headers = None
1368
- spot_id = exchange.options['partner']['spot']['id']
1369
- spot_key = exchange.options['partner']['spot']['key']
1370
- assert spot_id == 'ccxt', 'kucoin - id: ' + spot_id + ' not in options'
1371
- assert spot_key == '9e58cc35-5b5e-4133-92ec-166e3f077cb8', 'kucoin - key: ' + spot_key + ' not in options.'
1372
- try:
1373
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1374
- except Exception as e:
1375
- # we expect an error here, we're only interested in the headers
1376
- req_headers = exchange.last_request_headers
1377
- id = 'ccxt'
1378
- assert req_headers['KC-API-PARTNER'] == id, 'kucoin - id: ' + id + ' not in headers.'
1379
- close(exchange)
1380
- return True
1381
-
1382
- def test_kucoinfutures(self):
1383
- exchange = self.init_offline_exchange('kucoinfutures')
1384
- req_headers = None
1385
- id = 'ccxtfutures'
1386
- future_id = exchange.options['partner']['future']['id']
1387
- future_key = exchange.options['partner']['future']['key']
1388
- assert future_id == id, 'kucoinfutures - id: ' + future_id + ' not in options.'
1389
- assert future_key == '1b327198-f30c-4f14-a0ac-918871282f15', 'kucoinfutures - key: ' + future_key + ' not in options.'
1390
- try:
1391
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1392
- except Exception as e:
1393
- req_headers = exchange.last_request_headers
1394
- assert req_headers['KC-API-PARTNER'] == id, 'kucoinfutures - id: ' + id + ' not in headers.'
1395
- close(exchange)
1396
- return True
1397
-
1398
- def test_bitget(self):
1399
- exchange = self.init_offline_exchange('bitget')
1400
- req_headers = None
1401
- id = 'p4sve'
1402
- assert exchange.options['broker'] == id, 'bitget - id: ' + id + ' not in options'
1403
- try:
1404
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1405
- except Exception as e:
1406
- req_headers = exchange.last_request_headers
1407
- assert req_headers['X-CHANNEL-API-CODE'] == id, 'bitget - id: ' + id + ' not in headers.'
1408
- close(exchange)
1409
- return True
1410
-
1411
- def test_mexc(self):
1412
- exchange = self.init_offline_exchange('mexc')
1413
- req_headers = None
1414
- id = 'CCXT'
1415
- assert exchange.options['broker'] == id, 'mexc - id: ' + id + ' not in options'
1416
- exchange.load_markets()
1417
- try:
1418
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1419
- except Exception as e:
1420
- req_headers = exchange.last_request_headers
1421
- assert req_headers['source'] == id, 'mexc - id: ' + id + ' not in headers.'
1422
- close(exchange)
1423
- return True
1424
-
1425
- def test_htx(self):
1426
- exchange = self.init_offline_exchange('htx')
1427
- # spot test
1428
- id = 'AA03022abc'
1429
- spot_order_request = None
1430
- try:
1431
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1432
- except Exception as e:
1433
- spot_order_request = json_parse(exchange.last_request_body)
1434
- client_order_id = spot_order_request['client-order-id']
1435
- id_string = str(id)
1436
- assert client_order_id.startswith(id_string), 'htx - spot clientOrderId ' + client_order_id + ' does not start with id: ' + id_string
1437
- # swap test
1438
- swap_order_request = None
1439
- try:
1440
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1441
- except Exception as e:
1442
- swap_order_request = json_parse(exchange.last_request_body)
1443
- swap_inverse_order_request = None
1444
- try:
1445
- exchange.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
1446
- except Exception as e:
1447
- swap_inverse_order_request = json_parse(exchange.last_request_body)
1448
- client_order_id_swap = swap_order_request['channel_code']
1449
- assert client_order_id_swap.startswith(id_string), 'htx - swap channel_code ' + client_order_id_swap + ' does not start with id: ' + id_string
1450
- client_order_id_inverse = swap_inverse_order_request['channel_code']
1451
- assert client_order_id_inverse.startswith(id_string), 'htx - swap inverse channel_code ' + client_order_id_inverse + ' does not start with id: ' + id_string
1452
- close(exchange)
1453
- return True
1454
-
1455
- def test_woo(self):
1456
- exchange = self.init_offline_exchange('woo')
1457
- # spot test
1458
- id = 'bc830de7-50f3-460b-9ee0-f430f83f9dad'
1459
- spot_order_request = None
1460
- try:
1461
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1462
- except Exception as e:
1463
- spot_order_request = self.urlencoded_to_dict(exchange.last_request_body)
1464
- broker_id = spot_order_request['broker_id']
1465
- id_string = str(id)
1466
- assert broker_id.startswith(id_string), 'woo - broker_id: ' + broker_id + ' does not start with id: ' + id_string
1467
- # swap test
1468
- stop_order_request = None
1469
- try:
1470
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {
1471
- 'stopPrice': 30000,
1472
- })
1473
- except Exception as e:
1474
- stop_order_request = json_parse(exchange.last_request_body)
1475
- client_order_id_stop = stop_order_request['brokerId']
1476
- assert client_order_id_stop.startswith(id_string), 'woo - brokerId: ' + client_order_id_stop + ' does not start with id: ' + id_string
1477
- close(exchange)
1478
- return True
1479
-
1480
- def test_bitmart(self):
1481
- exchange = self.init_offline_exchange('bitmart')
1482
- req_headers = None
1483
- id = 'CCXTxBitmart000'
1484
- assert exchange.options['brokerId'] == id, 'bitmart - id: ' + id + ' not in options'
1485
- exchange.load_markets()
1486
- try:
1487
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1488
- except Exception as e:
1489
- req_headers = exchange.last_request_headers
1490
- assert req_headers['X-BM-BROKER-ID'] == id, 'bitmart - id: ' + id + ' not in headers'
1491
- close(exchange)
1492
- return True
1493
-
1494
- def test_coinex(self):
1495
- exchange = self.init_offline_exchange('coinex')
1496
- id = 'x-167673045'
1497
- assert exchange.options['brokerId'] == id, 'coinex - id: ' + id + ' not in options'
1498
- spot_order_request = None
1499
- try:
1500
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1501
- except Exception as e:
1502
- spot_order_request = json_parse(exchange.last_request_body)
1503
- client_order_id = spot_order_request['client_id']
1504
- id_string = str(id)
1505
- assert client_order_id.startswith(id_string), 'coinex - clientOrderId: ' + client_order_id + ' does not start with id: ' + id_string
1506
- close(exchange)
1507
- return True
1508
-
1509
- def test_bingx(self):
1510
- exchange = self.init_offline_exchange('bingx')
1511
- req_headers = None
1512
- id = 'CCXT'
1513
- assert exchange.options['broker'] == id, 'bingx - id: ' + id + ' not in options'
1514
- try:
1515
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1516
- except Exception as e:
1517
- # we expect an error here, we're only interested in the headers
1518
- req_headers = exchange.last_request_headers
1519
- assert req_headers['X-SOURCE-KEY'] == id, 'bingx - id: ' + id + ' not in headers.'
1520
- close(exchange)
1521
-
1522
- def test_phemex(self):
1523
- exchange = self.init_offline_exchange('phemex')
1524
- id = 'CCXT123456'
1525
- request = None
1526
- try:
1527
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1528
- except Exception as e:
1529
- request = json_parse(exchange.last_request_body)
1530
- client_order_id = request['clOrdID']
1531
- id_string = str(id)
1532
- assert client_order_id.startswith(id_string), 'phemex - clOrdID: ' + client_order_id + ' does not start with id: ' + id_string
1533
- close(exchange)
1534
-
1535
- def test_blofin(self):
1536
- exchange = self.init_offline_exchange('blofin')
1537
- id = 'ec6dd3a7dd982d0b'
1538
- request = None
1539
- try:
1540
- exchange.create_order('LTC/USDT:USDT', 'market', 'buy', 1)
1541
- except Exception as e:
1542
- request = json_parse(exchange.last_request_body)
1543
- broker_id = request['brokerId']
1544
- id_string = str(id)
1545
- assert broker_id.startswith(id_string), 'blofin - brokerId: ' + broker_id + ' does not start with id: ' + id_string
1546
- close(exchange)
1547
-
1548
- def test_hyperliquid(self):
1549
- exchange = self.init_offline_exchange('hyperliquid')
1550
- id = '1'
1551
- request = None
1552
- try:
1553
- exchange.create_order('SOL/USDC:USDC', 'limit', 'buy', 1, 100)
1554
- except Exception as e:
1555
- request = json_parse(exchange.last_request_body)
1556
- broker_id = str((request['action']['brokerCode']))
1557
- assert broker_id == id, 'hyperliquid - brokerId: ' + broker_id + ' does not start with id: ' + id
1558
- close(exchange)
1559
-
1560
- def test_coinbaseinternational(self):
1561
- exchange = self.init_offline_exchange('coinbaseinternational')
1562
- exchange.options['portfolio'] = 'random'
1563
- id = 'nfqkvdjp'
1564
- assert exchange.options['brokerId'] == id, 'id not in options'
1565
- request = None
1566
- try:
1567
- exchange.create_order('BTC/USDC:USDC', 'limit', 'buy', 1, 20000)
1568
- except Exception as e:
1569
- request = json_parse(exchange.last_request_body)
1570
- client_order_id = request['client_order_id']
1571
- assert client_order_id.startswith(str(id)), 'clientOrderId does not start with id'
1572
- close(exchange)
1573
- return True
1574
-
1575
- def test_coinbase_advanced(self):
1576
- exchange = self.init_offline_exchange('coinbase')
1577
- id = 'ccxt'
1578
- assert exchange.options['brokerId'] == id, 'id not in options'
1579
- request = None
1580
- try:
1581
- exchange.create_order('BTC/USDC', 'limit', 'buy', 1, 20000)
1582
- except Exception as e:
1583
- request = json_parse(exchange.last_request_body)
1584
- client_order_id = request['client_order_id']
1585
- assert client_order_id.startswith(str(id)), 'clientOrderId does not start with id'
1586
- close(exchange)
1587
- return True
1588
-
1589
- def test_woofi_pro(self):
1590
- exchange = self.init_offline_exchange('woofipro')
1591
- exchange.secret = 'secretsecretsecretsecretsecretsecretsecrets'
1592
- id = 'CCXT'
1593
- exchange.load_markets()
1594
- request = None
1595
- try:
1596
- exchange.create_order('BTC/USDC:USDC', 'limit', 'buy', 1, 20000)
1597
- except Exception as e:
1598
- request = json_parse(exchange.last_request_body)
1599
- broker_id = request['order_tag']
1600
- assert broker_id == id, 'woofipro - id: ' + id + ' different from broker_id: ' + broker_id
1601
- close(exchange)
1602
- return True
1603
-
1604
- def test_oxfun(self):
1605
- exchange = self.init_offline_exchange('oxfun')
1606
- exchange.secret = 'secretsecretsecretsecretsecretsecretsecrets'
1607
- id = 1000
1608
- exchange.load_markets()
1609
- request = None
1610
- try:
1611
- exchange.create_order('BTC/USD:OX', 'limit', 'buy', 1, 20000)
1612
- except Exception as e:
1613
- request = json_parse(exchange.last_request_body)
1614
- orders = request['orders']
1615
- first = orders[0]
1616
- broker_id = first['source']
1617
- assert broker_id == id, 'oxfun - id: ' + str(id) + ' different from broker_id: ' + str(broker_id)
1618
- return True
1619
-
1620
- def test_xt(self):
1621
- exchange = self.init_offline_exchange('xt')
1622
- id = 'CCXT'
1623
- spot_order_request = None
1624
- try:
1625
- exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1626
- except Exception as e:
1627
- spot_order_request = json_parse(exchange.last_request_body)
1628
- spot_media = spot_order_request['media']
1629
- assert spot_media == id, 'xt - id: ' + id + ' different from swap tag: ' + spot_media
1630
- swap_order_request = None
1631
- try:
1632
- exchange.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1633
- except Exception as e:
1634
- swap_order_request = json_parse(exchange.last_request_body)
1635
- swap_media = swap_order_request['clientMedia']
1636
- assert swap_media == id, 'xt - id: ' + id + ' different from swap tag: ' + swap_media
1637
- close(exchange)
1638
- return True
1639
-
1640
- # ***** AUTO-TRANSPILER-END *****
1641
- # *******************************
1642
-
1643
-
1644
- if __name__ == '__main__':
1645
- argvSymbol = argv.symbol if argv.symbol and '/' in argv.symbol else None
1646
- # in python, we check it through "symbol" arg (as opposed to JS/PHP) because argvs were already built above
1647
- argvMethod = argv.symbol if argv.symbol and '()' in argv.symbol else None
1648
- (testMainClass().init(argv.exchange, argvSymbol, argvMethod))