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/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.3.46-0.1'
7
+ __version__ = '4.5.0'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -21,9 +21,11 @@ from ccxt.base.errors import ArgumentsRequired
21
21
  from ccxt.base.errors import BadSymbol
22
22
  from ccxt.base.errors import NullResponse
23
23
  from ccxt.base.errors import RateLimitExceeded
24
+ from ccxt.base.errors import OperationFailed
24
25
  from ccxt.base.errors import BadRequest
25
26
  from ccxt.base.errors import BadResponse
26
- from ccxt.base.errors import ProxyError
27
+ from ccxt.base.errors import InvalidProxySettings
28
+ from ccxt.base.errors import UnsubscribeError
27
29
 
28
30
  # -----------------------------------------------------------------------------
29
31
 
@@ -31,7 +33,7 @@ from ccxt.base.decimal_to_precision import decimal_to_precision
31
33
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES, TICK_SIZE, NO_PADDING, TRUNCATE, ROUND, ROUND_UP, ROUND_DOWN, SIGNIFICANT_DIGITS
32
34
  from ccxt.base.decimal_to_precision import number_to_string
33
35
  from ccxt.base.precise import Precise
34
- from ccxt.base.types import BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num, Strings, CancellationRequest, Bool
36
+ from ccxt.base.types import ConstructorArgs, BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num, Strings, CancellationRequest, Bool
35
37
 
36
38
  # -----------------------------------------------------------------------------
37
39
 
@@ -59,6 +61,16 @@ from ccxt.static_dependencies.ethereum import abi
59
61
  from ccxt.static_dependencies.ethereum import account
60
62
  from ccxt.static_dependencies.msgpack import packb
61
63
 
64
+ # starknet
65
+ from ccxt.static_dependencies.starknet.ccxt_utils import get_private_key_from_eth_signature
66
+ from ccxt.static_dependencies.starknet.hash.address import compute_address
67
+ from ccxt.static_dependencies.starknet.hash.selector import get_selector_from_name
68
+ from ccxt.static_dependencies.starknet.hash.utils import message_signature, private_to_stark_key
69
+ from ccxt.static_dependencies.starknet.utils.typed_data import TypedData as TypedDataDataclass
70
+ try:
71
+ import apexpro.zklink_sdk as zklink_sdk
72
+ except ImportError:
73
+ zklink_sdk = None
62
74
 
63
75
  # -----------------------------------------------------------------------------
64
76
 
@@ -81,6 +93,14 @@ import gzip
81
93
  import hashlib
82
94
  import hmac
83
95
  import io
96
+
97
+ # load orjson if available, otherwise default to json
98
+ orjson = None
99
+ try:
100
+ import orjson as orjson
101
+ except ImportError:
102
+ pass
103
+
84
104
  import json
85
105
  import math
86
106
  import random
@@ -104,11 +124,20 @@ from ccxt.base.types import Int
104
124
 
105
125
  # -----------------------------------------------------------------------------
106
126
 
127
+ class SafeJSONEncoder(json.JSONEncoder):
128
+ def default(self, obj):
129
+ if isinstance(obj, Exception):
130
+ return {"name": obj.__class__.__name__}
131
+ try:
132
+ return super().default(obj)
133
+ except TypeError:
134
+ return f"TypeError: Object of type {type(obj).__name__} is not JSON serializable"
107
135
 
108
136
  class Exchange(object):
109
137
  """Base exchange class"""
110
138
  id = None
111
139
  name = None
140
+ countries = None
112
141
  version = None
113
142
  certified = False # if certified by the CCXT dev team
114
143
  pro = False # if it is integrated with CCXT Pro for WebSocket support
@@ -124,6 +153,8 @@ class Exchange(object):
124
153
  aiohttp_trust_env = False
125
154
  requests_trust_env = False
126
155
  session = None # Session () by default
156
+ tcp_connector = None # aiohttp.TCPConnector
157
+ aiohttp_socks_connector = None
127
158
  socks_proxy_sessions = None
128
159
  verify = True # SSL verification
129
160
  validateServerSsl = True
@@ -133,12 +164,19 @@ class Exchange(object):
133
164
  markets = None
134
165
  symbols = None
135
166
  codes = None
136
- timeframes = None
167
+ timeframes = {}
168
+ tokenBucket = None
169
+
137
170
  fees = {
138
171
  'trading': {
139
- 'percentage': True, # subclasses should rarely have to redefine this
172
+ 'tierBased': None,
173
+ 'percentage': None,
174
+ 'taker': None,
175
+ 'maker': None,
140
176
  },
141
177
  'funding': {
178
+ 'tierBased': None,
179
+ 'percentage': None,
142
180
  'withdraw': {},
143
181
  'deposit': {},
144
182
  },
@@ -156,6 +194,7 @@ class Exchange(object):
156
194
  urls = None
157
195
  api = None
158
196
  parseJsonResponse = True
197
+ throttler = None
159
198
 
160
199
  # PROXY & USER-AGENTS (see "examples/proxy-usage" file for explanation)
161
200
  proxy = None # for backwards compatibility
@@ -190,7 +229,9 @@ class Exchange(object):
190
229
  'chrome100': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
191
230
  }
192
231
  headers = None
232
+ returnResponseHeaders = False
193
233
  origin = '*' # CORS origin
234
+ MAX_VALUE = float('inf')
194
235
  #
195
236
  proxies = None
196
237
 
@@ -206,6 +247,7 @@ class Exchange(object):
206
247
  twofa = None
207
248
  markets_by_id = None
208
249
  currencies_by_id = None
250
+
209
251
  precision = None
210
252
  exceptions = None
211
253
  limits = {
@@ -226,6 +268,7 @@ class Exchange(object):
226
268
  'max': None,
227
269
  },
228
270
  }
271
+
229
272
  httpExceptions = {
230
273
  '422': ExchangeError,
231
274
  '418': DDoSProtection,
@@ -269,10 +312,14 @@ class Exchange(object):
269
312
  base_currencies = None
270
313
  quote_currencies = None
271
314
  currencies = None
315
+
272
316
  options = None # Python does not allow to define properties in run-time with setattr
317
+ isSandboxModeEnabled = False
273
318
  accounts = None
274
319
  positions = None
275
320
 
321
+ status = None
322
+
276
323
  requiredCredentials = {
277
324
  'apiKey': True,
278
325
  'secret': True,
@@ -287,117 +334,8 @@ class Exchange(object):
287
334
  }
288
335
 
289
336
  # API method metainfo
290
- has = {
291
- 'publicAPI': True,
292
- 'privateAPI': True,
293
- 'CORS': None,
294
- 'spot': None,
295
- 'margin': None,
296
- 'swap': None,
297
- 'future': None,
298
- 'option': None,
299
- 'addMargin': None,
300
- 'cancelAllOrders': None,
301
- 'cancelOrder': True,
302
- 'cancelOrders': None,
303
- 'createDepositAddress': None,
304
- 'createLimitOrder': True,
305
- 'createMarketOrder': True,
306
- 'createOrder': True,
307
- 'createPostOnlyOrder': None,
308
- 'createReduceOnlyOrder': None,
309
- 'createStopOrder': None,
310
- 'createStopLimitOrder': None,
311
- 'createStopMarketOrder': None,
312
- 'editOrder': 'emulated',
313
- 'fetchAccounts': None,
314
- 'fetchBalance': True,
315
- 'fetchBidsAsks': None,
316
- 'fetchBorrowInterest': None,
317
- 'fetchBorrowRate': None,
318
- 'fetchBorrowRateHistory': None,
319
- 'fetchBorrowRatesPerSymbol': None,
320
- 'fetchBorrowRates': None,
321
- 'fetchCanceledOrders': None,
322
- 'fetchClosedOrder': None,
323
- 'fetchClosedOrders': None,
324
- 'fetchClosedOrdersWs': None,
325
- 'fetchConvertCurrencies': None,
326
- 'fetchConvertQuote': None,
327
- 'fetchConvertTrade': None,
328
- 'fetchConvertTradeHistory': None,
329
- 'fetchCrossBorrowRate': None,
330
- 'fetchCrossBorrowRates': None,
331
- 'fetchCurrencies': 'emulated',
332
- 'fetchCurrenciesWs': 'emulated',
333
- 'fetchDeposit': None,
334
- 'fetchDepositAddress': None,
335
- 'fetchDepositAddresses': None,
336
- 'fetchDepositAddressesByNetwork': None,
337
- 'fetchDeposits': None,
338
- 'fetchFundingHistory': None,
339
- 'fetchFundingRate': None,
340
- 'fetchFundingRateHistory': None,
341
- 'fetchFundingRates': None,
342
- 'fetchIndexOHLCV': None,
343
- 'fetchLastPrices': None,
344
- 'fetchL2OrderBook': True,
345
- 'fetchLedger': None,
346
- 'fetchLedgerEntry': None,
347
- 'fetchLeverageTiers': None,
348
- 'fetchMarketLeverageTiers': None,
349
- 'fetchMarkets': True,
350
- 'fetchMarkOHLCV': None,
351
- 'fetchMyTrades': None,
352
- 'fetchOHLCV': None,
353
- 'fetchOpenOrder': None,
354
- 'fetchOpenOrders': None,
355
- 'fetchOrder': None,
356
- 'fetchOrderBook': True,
357
- 'fetchOrderBooks': None,
358
- 'fetchOrders': None,
359
- 'fetchOrderTrades': None,
360
- 'fetchPermissions': None,
361
- 'fetchPosition': None,
362
- 'fetchPositions': None,
363
- 'fetchPositionsRisk': None,
364
- 'fetchPremiumIndexOHLCV': None,
365
- 'fetchStatus': None,
366
- 'fetchTicker': True,
367
- 'fetchTickers': None,
368
- 'fetchTime': None,
369
- 'fetchTrades': True,
370
- 'fetchTradingFee': None,
371
- 'fetchTradingFees': None,
372
- 'fetchTradingLimits': None,
373
- 'fetchTransactions': None,
374
- 'fetchTransfers': None,
375
- 'fetchWithdrawal': None,
376
- 'fetchWithdrawals': None,
377
- 'reduceMargin': None,
378
- 'setLeverage': None,
379
- 'setMargin': None,
380
- 'setMarginMode': None,
381
- 'setPositionMode': None,
382
- 'signIn': None,
383
- 'transfer': None,
384
- 'withdraw': None,
385
- 'watchOrderBook': None,
386
- 'watchOrders': None,
387
- 'watchMyTrades': None,
388
- 'watchTickers': None,
389
- 'watchTicker': None,
390
- 'watchTrades': None,
391
- 'watchTradesForSymbols': None,
392
- 'watchOrderBookForSymbols': None,
393
- 'watchOHLCVForSymbols': None,
394
- 'watchBalance': None,
395
- 'watchLiquidations': None,
396
- 'watchLiquidationsForSymbols': None,
397
- 'watchMyLiquidations': None,
398
- 'watchMyLiquidationsForSymbols': None,
399
- 'watchOHLCV': None,
400
- }
337
+ has = {}
338
+ features = {}
401
339
  precisionMode = DECIMAL_PLACES
402
340
  paddingMode = NO_PADDING
403
341
  minFundingAddressLength = 1 # used in check_address
@@ -415,7 +353,7 @@ class Exchange(object):
415
353
  rateLimitMaxTokens = 16
416
354
  rateLimitUpdateTime = 0
417
355
  enableLastHttpResponse = True
418
- enableLastJsonResponse = True
356
+ enableLastJsonResponse = False
419
357
  enableLastResponseHeaders = True
420
358
  last_http_response = None
421
359
  last_json_response = None
@@ -437,7 +375,7 @@ class Exchange(object):
437
375
  }
438
376
  synchronous = True
439
377
 
440
- def __init__(self, config={}):
378
+ def __init__(self, config: ConstructorArgs = {}):
441
379
  self.aiohttp_trust_env = self.aiohttp_trust_env or self.trust_env
442
380
  self.requests_trust_env = self.requests_trust_env or self.trust_env
443
381
 
@@ -453,6 +391,8 @@ class Exchange(object):
453
391
  self.trades = dict() if self.trades is None else self.trades
454
392
  self.transactions = dict() if self.transactions is None else self.transactions
455
393
  self.ohlcvs = dict() if self.ohlcvs is None else self.ohlcvs
394
+ self.liquidations = dict() if self.liquidations is None else self.liquidations
395
+ self.myLiquidations = dict() if self.myLiquidations is None else self.myLiquidations
456
396
  self.currencies = dict() if self.currencies is None else self.currencies
457
397
  self.options = self.get_default_options() if self.options is None else self.options # Python does not allow to define properties in run-time with setattr
458
398
  self.decimal_to_precision = decimal_to_precision
@@ -474,14 +414,10 @@ class Exchange(object):
474
414
  else:
475
415
  setattr(self, key, settings[key])
476
416
 
477
- if self.markets:
478
- self.set_markets(self.markets)
479
-
480
417
  self.after_construct()
481
418
 
482
- is_sandbox = self.safe_bool_2(self.options, 'sandbox', 'testnet', False)
483
- if is_sandbox:
484
- self.set_sandbox_mode(is_sandbox)
419
+ if self.safe_bool(config, 'sandbox') or self.safe_bool(config, 'testnet'):
420
+ self.set_sandbox_mode(True)
485
421
 
486
422
  # convert all properties from underscore notation foo_bar to camelcase notation fooBar
487
423
  cls = type(self)
@@ -501,13 +437,6 @@ class Exchange(object):
501
437
  else:
502
438
  setattr(self, camelcase, attr)
503
439
 
504
- self.tokenBucket = self.extend({
505
- 'refillRate': 1.0 / self.rateLimit if self.rateLimit > 0 else float('inf'),
506
- 'delay': 0.001,
507
- 'capacity': 1.0,
508
- 'defaultCost': 1.0,
509
- }, getattr(self, 'tokenBucket', {}))
510
-
511
440
  if not self.session and self.synchronous:
512
441
  self.session = Session()
513
442
  self.session.trust_env = self.requests_trust_env
@@ -526,8 +455,9 @@ class Exchange(object):
526
455
  def __str__(self):
527
456
  return self.name
528
457
 
529
- def describe(self):
530
- return {}
458
+ def init_throttler(self, cost=None):
459
+ # stub in sync
460
+ pass
531
461
 
532
462
  def throttle(self, cost=None):
533
463
  now = float(self.milliseconds())
@@ -569,9 +499,11 @@ class Exchange(object):
569
499
  return response_body.strip()
570
500
 
571
501
  def on_json_response(self, response_body):
572
- if self.quoteJsonNumbers:
502
+ if self.quoteJsonNumbers and orjson is None:
573
503
  return json.loads(response_body, parse_float=str, parse_int=str)
574
504
  else:
505
+ if orjson:
506
+ return orjson.loads(response_body)
575
507
  return json.loads(response_body)
576
508
 
577
509
  def fetch(self, url, method='GET', headers=None, body=None):
@@ -583,7 +515,7 @@ class Exchange(object):
583
515
  proxyUrl = self.check_proxy_url_settings(url, method, headers, body)
584
516
  if proxyUrl is not None:
585
517
  request_headers.update({'Origin': self.origin})
586
- url = proxyUrl + url
518
+ url = proxyUrl + self.url_encoder_for_proxy_url(url)
587
519
  # proxy agents
588
520
  proxies = None # set default
589
521
  httpProxy, httpsProxy, socksProxy = self.check_proxy_settings(url, method, headers, body)
@@ -648,6 +580,8 @@ class Exchange(object):
648
580
  if self.verbose:
649
581
  self.log("\nfetch Response:", self.id, method, url, http_status_code, "ResponseHeaders:", headers, "ResponseBody:", http_response)
650
582
  self.logger.debug("%s %s, Response: %s %s %s", method, url, http_status_code, headers, http_response)
583
+ if json_response and not isinstance(json_response, list) and self.returnResponseHeaders:
584
+ json_response['responseHeaders'] = headers
651
585
  response.raise_for_status()
652
586
 
653
587
  except Timeout as e:
@@ -907,11 +841,13 @@ class Exchange(object):
907
841
 
908
842
  @staticmethod
909
843
  def get_object_value_from_key_list(dictionary_or_list, key_list):
844
+ isDataArray = isinstance(dictionary_or_list, list)
845
+ isDataDict = isinstance(dictionary_or_list, dict)
910
846
  for key in key_list:
911
- if isinstance(key, str):
847
+ if isDataDict:
912
848
  if key in dictionary_or_list and dictionary_or_list[key] is not None and dictionary_or_list[key] != '':
913
849
  return dictionary_or_list[key]
914
- elif key is not None:
850
+ elif isDataArray and not isinstance(key, str):
915
851
  if (key < len(dictionary_or_list)) and (dictionary_or_list[key] is not None) and (dictionary_or_list[key] != ''):
916
852
  return dictionary_or_list[key]
917
853
  return None
@@ -973,6 +909,10 @@ class Exchange(object):
973
909
  def keysort(dictionary):
974
910
  return collections.OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
975
911
 
912
+ @staticmethod
913
+ def sort(array):
914
+ return sorted(array)
915
+
976
916
  @staticmethod
977
917
  def extend(*args):
978
918
  if args is not None:
@@ -1023,6 +963,11 @@ class Exchange(object):
1023
963
  def groupBy(array, key):
1024
964
  return Exchange.group_by(array, key)
1025
965
 
966
+
967
+ @staticmethod
968
+ def index_by_safe(array, key):
969
+ return Exchange.index_by(array, key) # wrapper for go
970
+
1026
971
  @staticmethod
1027
972
  def index_by(array, key):
1028
973
  result = {}
@@ -1068,7 +1013,7 @@ class Exchange(object):
1068
1013
  return string
1069
1014
 
1070
1015
  @staticmethod
1071
- def urlencode(params={}, doseq=False):
1016
+ def urlencode(params={}, doseq=False, sort=False):
1072
1017
  newParams = params.copy()
1073
1018
  for key, value in params.items():
1074
1019
  if isinstance(value, bool):
@@ -1101,10 +1046,10 @@ class Exchange(object):
1101
1046
  if isinstance(params, dict):
1102
1047
  for key in params:
1103
1048
  _encode_params(params[key], key)
1104
- return _urlencode.urlencode(result)
1049
+ return _urlencode.urlencode(result, quote_via=_urlencode.quote)
1105
1050
 
1106
1051
  @staticmethod
1107
- def rawencode(params={}):
1052
+ def rawencode(params={}, sort=False):
1108
1053
  return _urlencode.unquote(Exchange.urlencode(params))
1109
1054
 
1110
1055
  @staticmethod
@@ -1388,6 +1333,57 @@ class Exchange(object):
1388
1333
  encodedData = account.messages.encode_typed_data(domain, messageTypes, message)
1389
1334
  return Exchange.binary_concat(b"\x19\x01", encodedData.header, encodedData.body)
1390
1335
 
1336
+ @staticmethod
1337
+ def retrieve_stark_account (signature, accountClassHash, accountProxyClassHash):
1338
+ privateKey = get_private_key_from_eth_signature(signature)
1339
+ publicKey = private_to_stark_key(privateKey)
1340
+ calldata = [
1341
+ int(accountClassHash, 16),
1342
+ get_selector_from_name("initialize"),
1343
+ 2,
1344
+ publicKey,
1345
+ 0,
1346
+ ]
1347
+
1348
+ address = compute_address(
1349
+ class_hash=int(accountProxyClassHash, 16),
1350
+ constructor_calldata=calldata,
1351
+ salt=publicKey,
1352
+ )
1353
+ return {
1354
+ 'privateKey': privateKey,
1355
+ 'publicKey': publicKey,
1356
+ 'address': hex(address)
1357
+ }
1358
+
1359
+ @staticmethod
1360
+ def starknet_encode_structured_data (domain, messageTypes, messageData, address):
1361
+ types = list(messageTypes.keys())
1362
+ if len(types) > 1:
1363
+ raise NotSupported('starknetEncodeStructuredData only support single type')
1364
+
1365
+ request = {
1366
+ 'domain': domain,
1367
+ 'primaryType': types[0],
1368
+ 'types': Exchange.extend({
1369
+ 'StarkNetDomain': [
1370
+ {'name': "name", 'type': "felt"},
1371
+ {'name': "chainId", 'type': "felt"},
1372
+ {'name': "version", 'type': "felt"},
1373
+ ],
1374
+ }, messageTypes),
1375
+ 'message': messageData,
1376
+ }
1377
+ typedDataClass = TypedDataDataclass.from_dict(request)
1378
+ msgHash = typedDataClass.message_hash(int(address, 16))
1379
+ return msgHash
1380
+
1381
+ @staticmethod
1382
+ def starknet_sign (hash, pri):
1383
+ # // TODO: unify to ecdsa
1384
+ r, s = message_signature(hash, pri)
1385
+ return Exchange.json([hex(r), hex(s)])
1386
+
1391
1387
  @staticmethod
1392
1388
  def packb(o):
1393
1389
  return packb(o)
@@ -1449,7 +1445,7 @@ class Exchange(object):
1449
1445
  @staticmethod
1450
1446
  def eddsa(request, secret, curve='ed25519'):
1451
1447
  if isinstance(secret, str):
1452
- Exchange.encode(secret)
1448
+ secret = Exchange.encode(secret)
1453
1449
  private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) if len(secret) == 32 else load_pem_private_key(secret, None)
1454
1450
  return Exchange.binary_to_base64(private_key.sign(request))
1455
1451
 
@@ -1463,7 +1459,9 @@ class Exchange(object):
1463
1459
 
1464
1460
  @staticmethod
1465
1461
  def json(data, params=None):
1466
- return json.dumps(data, separators=(',', ':'))
1462
+ if orjson:
1463
+ return orjson.dumps(data).decode('utf-8')
1464
+ return json.dumps(data, separators=(',', ':'), cls=SafeJSONEncoder)
1467
1465
 
1468
1466
  @staticmethod
1469
1467
  def is_json_encoded_object(input):
@@ -1473,11 +1471,11 @@ class Exchange(object):
1473
1471
 
1474
1472
  @staticmethod
1475
1473
  def encode(string):
1476
- return string.encode('latin-1')
1474
+ return string.encode('utf-8')
1477
1475
 
1478
1476
  @staticmethod
1479
1477
  def decode(string):
1480
- return string.decode('latin-1')
1478
+ return string.decode('utf-8')
1481
1479
 
1482
1480
  @staticmethod
1483
1481
  def to_array(value):
@@ -1508,18 +1506,10 @@ class Exchange(object):
1508
1506
  return error
1509
1507
  return result
1510
1508
 
1511
- def check_address(self, address):
1512
- """Checks an address is not the same character repeated or an empty sequence"""
1513
- if address is None:
1514
- raise InvalidAddress(self.id + ' address is None')
1515
- if all(letter == address[0] for letter in address) or len(address) < self.minFundingAddressLength or ' ' in address:
1516
- raise InvalidAddress(self.id + ' address is invalid or has less than ' + str(self.minFundingAddressLength) + ' characters: "' + str(address) + '"')
1517
- return address
1518
-
1519
1509
  def precision_from_string(self, str):
1520
1510
  # support string formats like '1e-4'
1521
- if 'e' in str:
1522
- numStr = re.sub(r'\de', '', str)
1511
+ if 'e' in str or 'E' in str:
1512
+ numStr = re.sub(r'\d\.?\d*[eE]', '', str)
1523
1513
  return int(numStr) * -1
1524
1514
  # support integer formats (without dot) like '1', '10' etc [Note: bug in decimalToPrecision, so this should not be used atm]
1525
1515
  # if not ('.' in str):
@@ -1528,7 +1518,32 @@ class Exchange(object):
1528
1518
  parts = re.sub(r'0+$', '', str).split('.')
1529
1519
  return len(parts[1]) if len(parts) > 1 else 0
1530
1520
 
1521
+ def map_to_safe_map(self, dictionary):
1522
+ return dictionary # wrapper for go
1523
+
1524
+ def safe_map_to_map(self, dictionary):
1525
+ return dictionary # wrapper for go
1526
+
1531
1527
  def load_markets(self, reload=False, params={}):
1528
+ """
1529
+ Loads and prepares the markets for trading.
1530
+
1531
+ Args:
1532
+ reload (bool): If True, the markets will be reloaded from the exchange.
1533
+ params (dict): Additional exchange-specific parameters for the request.
1534
+
1535
+ Returns:
1536
+ dict: A dictionary of markets.
1537
+
1538
+ Raises:
1539
+ Exception: If the markets cannot be loaded or prepared.
1540
+
1541
+ Notes:
1542
+ It ensures that the markets are only loaded once, even if called multiple times.
1543
+ If the markets are already loaded and `reload` is False or not provided, it returns the existing markets.
1544
+ If a reload is in progress, it waits for completion before returning.
1545
+ If an error occurs during loading or preparation, an exception is raised.
1546
+ """
1532
1547
  if not reload:
1533
1548
  if self.markets:
1534
1549
  if not self.markets_by_id:
@@ -1537,7 +1552,10 @@ class Exchange(object):
1537
1552
  currencies = None
1538
1553
  if self.has['fetchCurrencies'] is True:
1539
1554
  currencies = self.fetch_currencies()
1555
+ self.options['cachedCurrencies'] = currencies
1540
1556
  markets = self.fetch_markets(params)
1557
+ if 'cachedCurrencies' in self.options:
1558
+ del self.options['cachedCurrencies']
1541
1559
  return self.set_markets(markets, currencies)
1542
1560
 
1543
1561
  def fetch_markets(self, params={}):
@@ -1773,6 +1791,80 @@ class Exchange(object):
1773
1791
  def create_safe_dictionary(self):
1774
1792
  return {}
1775
1793
 
1794
+ def convert_to_safe_dictionary(self, dictionary):
1795
+ return dictionary
1796
+
1797
+ def rand_number(self, size):
1798
+ return int(''.join([str(random.randint(0, 9)) for _ in range(size)]))
1799
+
1800
+ def binary_length(self, binary):
1801
+ return len(binary)
1802
+
1803
+ def get_zk_contract_signature_obj(self, seeds: str, params={}):
1804
+ if zklink_sdk is None:
1805
+ raise Exception('zklink_sdk is not installed, please do pip3 install apexomni-arm or apexomni-x86-mac or apexomni-x86-windows-linux')
1806
+
1807
+ slotId = self.safe_string(params, 'slotId')
1808
+ nonceInt = int(self.remove0x_prefix(self.hash(self.encode(slotId), 'sha256', 'hex')), 16)
1809
+
1810
+ maxUint64 = 18446744073709551615
1811
+ maxUint32 = 4294967295
1812
+
1813
+ slotId = (nonceInt % maxUint64) / maxUint32
1814
+ nonce = nonceInt % maxUint32
1815
+ accountId = int(self.safe_string(params, 'accountId'), 10) % maxUint32
1816
+
1817
+ priceStr = (Decimal(self.safe_string(params, 'price')) * Decimal(10) ** Decimal('18')).quantize(Decimal(0), rounding='ROUND_DOWN')
1818
+ sizeStr = (Decimal(self.safe_string(params, 'size')) * Decimal(10) ** Decimal('18')).quantize(Decimal(0), rounding='ROUND_DOWN')
1819
+
1820
+ takerFeeRateStr = (Decimal(self.safe_string(params, 'takerFeeRate')) * Decimal(10000)).quantize(Decimal(0), rounding='ROUND_UP')
1821
+ makerFeeRateStr = (Decimal(self.safe_string(params, 'makerFeeRate')) * Decimal(10000)).quantize(Decimal(0), rounding='ROUND_UP')
1822
+
1823
+ builder = zklink_sdk.ContractBuilder(
1824
+ int(accountId), int(0), int(slotId), int(nonce), int(self.safe_number(params, 'pairId')),
1825
+ sizeStr.__str__(), priceStr.__str__(), self.safe_string(params, 'direction') == "BUY",
1826
+ int(takerFeeRateStr), int(makerFeeRateStr), False)
1827
+
1828
+
1829
+ tx = zklink_sdk.Contract(builder)
1830
+ seedsByte = bytes.fromhex(seeds.removeprefix('0x'))
1831
+ signerSeed = zklink_sdk.ZkLinkSigner().new_from_seed(seedsByte)
1832
+ auth_data = signerSeed.sign_musig(tx.get_bytes())
1833
+ signature = auth_data.signature
1834
+ return signature
1835
+
1836
+ def get_zk_transfer_signature_obj(self, seeds: str, params={}):
1837
+ if zklink_sdk is None:
1838
+ raise Exception('zklink_sdk is not installed, please do pip3 install apexomni-arm or apexomni-x86-mac or apexomni-x86-windows-linux')
1839
+
1840
+ nonce = self.safe_string(params, 'nonce', '0')
1841
+ if self.safe_bool(params, 'isContract'):
1842
+ formattedUint32 = '4294967295'
1843
+ formattedNonce = int(self.remove0x_prefix(self.hash(self.encode(nonce), 'sha256', 'hex')), 16)
1844
+ nonce = Precise.string_mod(str(formattedNonce), formattedUint32)
1845
+
1846
+ tx_builder = zklink_sdk.TransferBuilder(
1847
+ int(self.safe_number(params, 'zkAccountId', 0)),
1848
+ self.safe_string(params, 'receiverAddress'),
1849
+ int(self.safe_number(params, 'subAccountId', 0)),
1850
+ int(self.safe_number(params, 'receiverSubAccountId', 0)),
1851
+ int(self.safe_number(params, 'tokenId', 0)),
1852
+ self.safe_string(params, 'amount', '0'),
1853
+ self.safe_string(params, 'fee', '0'),
1854
+ self.parse_to_int(nonce),
1855
+ int(self.safe_number(params, 'timestampSeconds', 0))
1856
+ )
1857
+
1858
+ tx = zklink_sdk.Transfer(tx_builder)
1859
+ seedsByte = bytes.fromhex(seeds.removeprefix('0x'))
1860
+ signerSeed = zklink_sdk.ZkLinkSigner().new_from_seed(seedsByte)
1861
+ auth_data = signerSeed.sign_musig(tx.get_bytes())
1862
+ signature = auth_data.signature
1863
+ return signature
1864
+
1865
+ def is_binary_message(self, message):
1866
+ return isinstance(message, bytes) or isinstance(message, bytearray)
1867
+
1776
1868
  # ########################################################################
1777
1869
  # ########################################################################
1778
1870
  # ########################################################################
@@ -1812,9 +1904,340 @@ class Exchange(object):
1812
1904
 
1813
1905
  # METHODS BELOW THIS LINE ARE TRANSPILED FROM JAVASCRIPT TO PYTHON AND PHP
1814
1906
 
1907
+ def describe(self) -> Any:
1908
+ return {
1909
+ 'id': None,
1910
+ 'name': None,
1911
+ 'countries': None,
1912
+ 'enableRateLimit': True,
1913
+ 'rateLimit': 2000, # milliseconds = seconds * 1000
1914
+ 'timeout': self.timeout, # milliseconds = seconds * 1000
1915
+ 'certified': False, # if certified by the CCXT dev team
1916
+ 'pro': False, # if it is integrated with CCXT Pro for WebSocket support
1917
+ 'alias': False, # whether self exchange is an alias to another exchange
1918
+ 'dex': False,
1919
+ 'has': {
1920
+ 'publicAPI': True,
1921
+ 'privateAPI': True,
1922
+ 'CORS': None,
1923
+ 'sandbox': None,
1924
+ 'spot': None,
1925
+ 'margin': None,
1926
+ 'swap': None,
1927
+ 'future': None,
1928
+ 'option': None,
1929
+ 'addMargin': None,
1930
+ 'borrowCrossMargin': None,
1931
+ 'borrowIsolatedMargin': None,
1932
+ 'borrowMargin': None,
1933
+ 'cancelAllOrders': None,
1934
+ 'cancelAllOrdersWs': None,
1935
+ 'cancelOrder': True,
1936
+ 'cancelOrderWs': None,
1937
+ 'cancelOrders': None,
1938
+ 'cancelOrdersWs': None,
1939
+ 'closeAllPositions': None,
1940
+ 'closePosition': None,
1941
+ 'createDepositAddress': None,
1942
+ 'createLimitBuyOrder': None,
1943
+ 'createLimitBuyOrderWs': None,
1944
+ 'createLimitOrder': True,
1945
+ 'createLimitOrderWs': None,
1946
+ 'createLimitSellOrder': None,
1947
+ 'createLimitSellOrderWs': None,
1948
+ 'createMarketBuyOrder': None,
1949
+ 'createMarketBuyOrderWs': None,
1950
+ 'createMarketBuyOrderWithCost': None,
1951
+ 'createMarketBuyOrderWithCostWs': None,
1952
+ 'createMarketOrder': True,
1953
+ 'createMarketOrderWs': True,
1954
+ 'createMarketOrderWithCost': None,
1955
+ 'createMarketOrderWithCostWs': None,
1956
+ 'createMarketSellOrder': None,
1957
+ 'createMarketSellOrderWs': None,
1958
+ 'createMarketSellOrderWithCost': None,
1959
+ 'createMarketSellOrderWithCostWs': None,
1960
+ 'createOrder': True,
1961
+ 'createOrderWs': None,
1962
+ 'createOrders': None,
1963
+ 'createOrderWithTakeProfitAndStopLoss': None,
1964
+ 'createOrderWithTakeProfitAndStopLossWs': None,
1965
+ 'createPostOnlyOrder': None,
1966
+ 'createPostOnlyOrderWs': None,
1967
+ 'createReduceOnlyOrder': None,
1968
+ 'createReduceOnlyOrderWs': None,
1969
+ 'createStopLimitOrder': None,
1970
+ 'createStopLimitOrderWs': None,
1971
+ 'createStopLossOrder': None,
1972
+ 'createStopLossOrderWs': None,
1973
+ 'createStopMarketOrder': None,
1974
+ 'createStopMarketOrderWs': None,
1975
+ 'createStopOrder': None,
1976
+ 'createStopOrderWs': None,
1977
+ 'createTakeProfitOrder': None,
1978
+ 'createTakeProfitOrderWs': None,
1979
+ 'createTrailingAmountOrder': None,
1980
+ 'createTrailingAmountOrderWs': None,
1981
+ 'createTrailingPercentOrder': None,
1982
+ 'createTrailingPercentOrderWs': None,
1983
+ 'createTriggerOrder': None,
1984
+ 'createTriggerOrderWs': None,
1985
+ 'deposit': None,
1986
+ 'editOrder': 'emulated',
1987
+ 'editOrders': None,
1988
+ 'editOrderWs': None,
1989
+ 'fetchAccounts': None,
1990
+ 'fetchBalance': True,
1991
+ 'fetchBalanceWs': None,
1992
+ 'fetchBidsAsks': None,
1993
+ 'fetchBorrowInterest': None,
1994
+ 'fetchBorrowRate': None,
1995
+ 'fetchBorrowRateHistories': None,
1996
+ 'fetchBorrowRateHistory': None,
1997
+ 'fetchBorrowRates': None,
1998
+ 'fetchBorrowRatesPerSymbol': None,
1999
+ 'fetchCanceledAndClosedOrders': None,
2000
+ 'fetchCanceledOrders': None,
2001
+ 'fetchClosedOrder': None,
2002
+ 'fetchClosedOrders': None,
2003
+ 'fetchClosedOrdersWs': None,
2004
+ 'fetchConvertCurrencies': None,
2005
+ 'fetchConvertQuote': None,
2006
+ 'fetchConvertTrade': None,
2007
+ 'fetchConvertTradeHistory': None,
2008
+ 'fetchCrossBorrowRate': None,
2009
+ 'fetchCrossBorrowRates': None,
2010
+ 'fetchCurrencies': 'emulated',
2011
+ 'fetchCurrenciesWs': 'emulated',
2012
+ 'fetchDeposit': None,
2013
+ 'fetchDepositAddress': None,
2014
+ 'fetchDepositAddresses': None,
2015
+ 'fetchDepositAddressesByNetwork': None,
2016
+ 'fetchDeposits': None,
2017
+ 'fetchDepositsWithdrawals': None,
2018
+ 'fetchDepositsWs': None,
2019
+ 'fetchDepositWithdrawFee': None,
2020
+ 'fetchDepositWithdrawFees': None,
2021
+ 'fetchFundingHistory': None,
2022
+ 'fetchFundingRate': None,
2023
+ 'fetchFundingRateHistory': None,
2024
+ 'fetchFundingInterval': None,
2025
+ 'fetchFundingIntervals': None,
2026
+ 'fetchFundingRates': None,
2027
+ 'fetchGreeks': None,
2028
+ 'fetchIndexOHLCV': None,
2029
+ 'fetchIsolatedBorrowRate': None,
2030
+ 'fetchIsolatedBorrowRates': None,
2031
+ 'fetchMarginAdjustmentHistory': None,
2032
+ 'fetchIsolatedPositions': None,
2033
+ 'fetchL2OrderBook': True,
2034
+ 'fetchL3OrderBook': None,
2035
+ 'fetchLastPrices': None,
2036
+ 'fetchLedger': None,
2037
+ 'fetchLedgerEntry': None,
2038
+ 'fetchLeverage': None,
2039
+ 'fetchLeverages': None,
2040
+ 'fetchLeverageTiers': None,
2041
+ 'fetchLiquidations': None,
2042
+ 'fetchLongShortRatio': None,
2043
+ 'fetchLongShortRatioHistory': None,
2044
+ 'fetchMarginMode': None,
2045
+ 'fetchMarginModes': None,
2046
+ 'fetchMarketLeverageTiers': None,
2047
+ 'fetchMarkets': True,
2048
+ 'fetchMarketsWs': None,
2049
+ 'fetchMarkOHLCV': None,
2050
+ 'fetchMyLiquidations': None,
2051
+ 'fetchMySettlementHistory': None,
2052
+ 'fetchMyTrades': None,
2053
+ 'fetchMyTradesWs': None,
2054
+ 'fetchOHLCV': None,
2055
+ 'fetchOHLCVWs': None,
2056
+ 'fetchOpenInterest': None,
2057
+ 'fetchOpenInterests': None,
2058
+ 'fetchOpenInterestHistory': None,
2059
+ 'fetchOpenOrder': None,
2060
+ 'fetchOpenOrders': None,
2061
+ 'fetchOpenOrdersWs': None,
2062
+ 'fetchOption': None,
2063
+ 'fetchOptionChain': None,
2064
+ 'fetchOrder': None,
2065
+ 'fetchOrderBook': True,
2066
+ 'fetchOrderBooks': None,
2067
+ 'fetchOrderBookWs': None,
2068
+ 'fetchOrders': None,
2069
+ 'fetchOrdersByStatus': None,
2070
+ 'fetchOrdersWs': None,
2071
+ 'fetchOrderTrades': None,
2072
+ 'fetchOrderWs': None,
2073
+ 'fetchPosition': None,
2074
+ 'fetchPositionHistory': None,
2075
+ 'fetchPositionsHistory': None,
2076
+ 'fetchPositionWs': None,
2077
+ 'fetchPositionMode': None,
2078
+ 'fetchPositions': None,
2079
+ 'fetchPositionsWs': None,
2080
+ 'fetchPositionsForSymbol': None,
2081
+ 'fetchPositionsForSymbolWs': None,
2082
+ 'fetchPositionsRisk': None,
2083
+ 'fetchPremiumIndexOHLCV': None,
2084
+ 'fetchSettlementHistory': None,
2085
+ 'fetchStatus': None,
2086
+ 'fetchTicker': True,
2087
+ 'fetchTickerWs': None,
2088
+ 'fetchTickers': None,
2089
+ 'fetchMarkPrices': None,
2090
+ 'fetchTickersWs': None,
2091
+ 'fetchTime': None,
2092
+ 'fetchTrades': True,
2093
+ 'fetchTradesWs': None,
2094
+ 'fetchTradingFee': None,
2095
+ 'fetchTradingFees': None,
2096
+ 'fetchTradingFeesWs': None,
2097
+ 'fetchTradingLimits': None,
2098
+ 'fetchTransactionFee': None,
2099
+ 'fetchTransactionFees': None,
2100
+ 'fetchTransactions': None,
2101
+ 'fetchTransfer': None,
2102
+ 'fetchTransfers': None,
2103
+ 'fetchUnderlyingAssets': None,
2104
+ 'fetchVolatilityHistory': None,
2105
+ 'fetchWithdrawAddresses': None,
2106
+ 'fetchWithdrawal': None,
2107
+ 'fetchWithdrawals': None,
2108
+ 'fetchWithdrawalsWs': None,
2109
+ 'fetchWithdrawalWhitelist': None,
2110
+ 'reduceMargin': None,
2111
+ 'repayCrossMargin': None,
2112
+ 'repayIsolatedMargin': None,
2113
+ 'setLeverage': None,
2114
+ 'setMargin': None,
2115
+ 'setMarginMode': None,
2116
+ 'setPositionMode': None,
2117
+ 'signIn': None,
2118
+ 'transfer': None,
2119
+ 'watchBalance': None,
2120
+ 'watchMyTrades': None,
2121
+ 'watchOHLCV': None,
2122
+ 'watchOHLCVForSymbols': None,
2123
+ 'watchOrderBook': None,
2124
+ 'watchBidsAsks': None,
2125
+ 'watchOrderBookForSymbols': None,
2126
+ 'watchOrders': None,
2127
+ 'watchOrdersForSymbols': None,
2128
+ 'watchPosition': None,
2129
+ 'watchPositions': None,
2130
+ 'watchStatus': None,
2131
+ 'watchTicker': None,
2132
+ 'watchTickers': None,
2133
+ 'watchTrades': None,
2134
+ 'watchTradesForSymbols': None,
2135
+ 'watchLiquidations': None,
2136
+ 'watchLiquidationsForSymbols': None,
2137
+ 'watchMyLiquidations': None,
2138
+ 'unWatchOrders': None,
2139
+ 'unWatchTrades': None,
2140
+ 'unWatchTradesForSymbols': None,
2141
+ 'unWatchOHLCVForSymbols': None,
2142
+ 'unWatchOrderBookForSymbols': None,
2143
+ 'unWatchPositions': None,
2144
+ 'unWatchOrderBook': None,
2145
+ 'unWatchTickers': None,
2146
+ 'unWatchMyTrades': None,
2147
+ 'unWatchTicker': None,
2148
+ 'unWatchOHLCV': None,
2149
+ 'watchMyLiquidationsForSymbols': None,
2150
+ 'withdraw': None,
2151
+ 'ws': None,
2152
+ },
2153
+ 'urls': {
2154
+ 'logo': None,
2155
+ 'api': None,
2156
+ 'www': None,
2157
+ 'doc': None,
2158
+ 'fees': None,
2159
+ },
2160
+ 'api': None,
2161
+ 'requiredCredentials': {
2162
+ 'apiKey': True,
2163
+ 'secret': True,
2164
+ 'uid': False,
2165
+ 'accountId': False,
2166
+ 'login': False,
2167
+ 'password': False,
2168
+ 'twofa': False, # 2-factor authentication(one-time password key)
2169
+ 'privateKey': False, # a "0x"-prefixed hexstring private key for a wallet
2170
+ 'walletAddress': False, # the wallet address "0x"-prefixed hexstring
2171
+ 'token': False, # reserved for HTTP auth in some cases
2172
+ },
2173
+ 'markets': None, # to be filled manually or by fetchMarkets
2174
+ 'currencies': {}, # to be filled manually or by fetchMarkets
2175
+ 'timeframes': None, # redefine if the exchange has.fetchOHLCV
2176
+ 'fees': {
2177
+ 'trading': {
2178
+ 'tierBased': None,
2179
+ 'percentage': None,
2180
+ 'taker': None,
2181
+ 'maker': None,
2182
+ },
2183
+ 'funding': {
2184
+ 'tierBased': None,
2185
+ 'percentage': None,
2186
+ 'withdraw': {},
2187
+ 'deposit': {},
2188
+ },
2189
+ },
2190
+ 'status': {
2191
+ 'status': 'ok',
2192
+ 'updated': None,
2193
+ 'eta': None,
2194
+ 'url': None,
2195
+ },
2196
+ 'exceptions': None,
2197
+ 'httpExceptions': {
2198
+ '422': ExchangeError,
2199
+ '418': DDoSProtection,
2200
+ '429': RateLimitExceeded,
2201
+ '404': ExchangeNotAvailable,
2202
+ '409': ExchangeNotAvailable,
2203
+ '410': ExchangeNotAvailable,
2204
+ '451': ExchangeNotAvailable,
2205
+ '500': ExchangeNotAvailable,
2206
+ '501': ExchangeNotAvailable,
2207
+ '502': ExchangeNotAvailable,
2208
+ '520': ExchangeNotAvailable,
2209
+ '521': ExchangeNotAvailable,
2210
+ '522': ExchangeNotAvailable,
2211
+ '525': ExchangeNotAvailable,
2212
+ '526': ExchangeNotAvailable,
2213
+ '400': ExchangeNotAvailable,
2214
+ '403': ExchangeNotAvailable,
2215
+ '405': ExchangeNotAvailable,
2216
+ '503': ExchangeNotAvailable,
2217
+ '530': ExchangeNotAvailable,
2218
+ '408': RequestTimeout,
2219
+ '504': RequestTimeout,
2220
+ '401': AuthenticationError,
2221
+ '407': AuthenticationError,
2222
+ '511': AuthenticationError,
2223
+ },
2224
+ 'commonCurrencies': {
2225
+ 'XBT': 'BTC',
2226
+ 'BCHSV': 'BSV',
2227
+ },
2228
+ 'precisionMode': TICK_SIZE,
2229
+ 'paddingMode': NO_PADDING,
2230
+ 'limits': {
2231
+ 'leverage': {'min': None, 'max': None},
2232
+ 'amount': {'min': None, 'max': None},
2233
+ 'price': {'min': None, 'max': None},
2234
+ 'cost': {'min': None, 'max': None},
2235
+ },
2236
+ }
2237
+
1815
2238
  def safe_bool_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: bool = None):
1816
2239
  """
1817
- * @ignore
2240
+ @ignore
1818
2241
  safely extract boolean value from dictionary or list
1819
2242
  :returns bool | None:
1820
2243
  """
@@ -1825,7 +2248,7 @@ class Exchange(object):
1825
2248
 
1826
2249
  def safe_bool_2(self, dictionary, key1: IndexType, key2: IndexType, defaultValue: bool = None):
1827
2250
  """
1828
- * @ignore
2251
+ @ignore
1829
2252
  safely extract boolean value from dictionary or list
1830
2253
  :returns bool | None:
1831
2254
  """
@@ -1833,7 +2256,7 @@ class Exchange(object):
1833
2256
 
1834
2257
  def safe_bool(self, dictionary, key: IndexType, defaultValue: bool = None):
1835
2258
  """
1836
- * @ignore
2259
+ @ignore
1837
2260
  safely extract boolean value from dictionary or list
1838
2261
  :returns bool | None:
1839
2262
  """
@@ -1841,20 +2264,21 @@ class Exchange(object):
1841
2264
 
1842
2265
  def safe_dict_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: dict = None):
1843
2266
  """
1844
- * @ignore
2267
+ @ignore
1845
2268
  safely extract a dictionary from dictionary or list
1846
2269
  :returns dict | None:
1847
2270
  """
1848
2271
  value = self.safe_value_n(dictionaryOrList, keys, defaultValue)
1849
2272
  if value is None:
1850
2273
  return defaultValue
1851
- if isinstance(value, dict):
1852
- return value
2274
+ if (isinstance(value, dict)):
2275
+ if not isinstance(value, list):
2276
+ return value
1853
2277
  return defaultValue
1854
2278
 
1855
2279
  def safe_dict(self, dictionary, key: IndexType, defaultValue: dict = None):
1856
2280
  """
1857
- * @ignore
2281
+ @ignore
1858
2282
  safely extract a dictionary from dictionary or list
1859
2283
  :returns dict | None:
1860
2284
  """
@@ -1862,7 +2286,7 @@ class Exchange(object):
1862
2286
 
1863
2287
  def safe_dict_2(self, dictionary, key1: IndexType, key2: str, defaultValue: dict = None):
1864
2288
  """
1865
- * @ignore
2289
+ @ignore
1866
2290
  safely extract a dictionary from dictionary or list
1867
2291
  :returns dict | None:
1868
2292
  """
@@ -1870,7 +2294,7 @@ class Exchange(object):
1870
2294
 
1871
2295
  def safe_list_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: List[Any] = None):
1872
2296
  """
1873
- * @ignore
2297
+ @ignore
1874
2298
  safely extract an Array from dictionary or list
1875
2299
  :returns Array | None:
1876
2300
  """
@@ -1883,7 +2307,7 @@ class Exchange(object):
1883
2307
 
1884
2308
  def safe_list_2(self, dictionaryOrList, key1: IndexType, key2: str, defaultValue: List[Any] = None):
1885
2309
  """
1886
- * @ignore
2310
+ @ignore
1887
2311
  safely extract an Array from dictionary or list
1888
2312
  :returns Array | None:
1889
2313
  """
@@ -1891,7 +2315,7 @@ class Exchange(object):
1891
2315
 
1892
2316
  def safe_list(self, dictionaryOrList, key: IndexType, defaultValue: List[Any] = None):
1893
2317
  """
1894
- * @ignore
2318
+ @ignore
1895
2319
  safely extract an Array from dictionary or list
1896
2320
  :returns Array | None:
1897
2321
  """
@@ -1904,10 +2328,21 @@ class Exchange(object):
1904
2328
  def handle_delta(self, bookside, delta):
1905
2329
  raise NotSupported(self.id + ' handleDelta not supported yet')
1906
2330
 
2331
+ def handle_deltas_with_keys(self, bookSide: Any, deltas, priceKey: IndexType = 0, amountKey: IndexType = 1, countOrIdKey: IndexType = 2):
2332
+ for i in range(0, len(deltas)):
2333
+ bidAsk = self.parse_bid_ask(deltas[i], priceKey, amountKey, countOrIdKey)
2334
+ bookSide.storeArray(bidAsk)
2335
+
1907
2336
  def get_cache_index(self, orderbook, deltas):
1908
2337
  # return the first index of the cache that can be applied to the orderbook or -1 if not possible
1909
2338
  return -1
1910
2339
 
2340
+ def arrays_concat(self, arraysOfArrays: List[Any]):
2341
+ result = []
2342
+ for i in range(0, len(arraysOfArrays)):
2343
+ result = self.array_concat(result, arraysOfArrays[i])
2344
+ return result
2345
+
1911
2346
  def find_timeframe(self, timeframe, timeframes=None):
1912
2347
  if timeframes is None:
1913
2348
  timeframes = self.timeframes
@@ -1943,58 +2378,58 @@ class Exchange(object):
1943
2378
  length = len(usedProxies)
1944
2379
  if length > 1:
1945
2380
  joinedProxyNames = ','.join(usedProxies)
1946
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from : proxyUrl, proxy_url, proxyUrlCallback, proxy_url_callback')
2381
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from : proxyUrl, proxy_url, proxyUrlCallback, proxy_url_callback')
1947
2382
  return proxyUrl
1948
2383
 
2384
+ def url_encoder_for_proxy_url(self, targetUrl: str):
2385
+ # to be overriden
2386
+ includesQuery = targetUrl.find('?') >= 0
2387
+ finalUrl = self.encode_uri_component(targetUrl) if includesQuery else targetUrl
2388
+ return finalUrl
2389
+
1949
2390
  def check_proxy_settings(self, url: Str = None, method: Str = None, headers=None, body=None):
1950
2391
  usedProxies = []
1951
2392
  httpProxy = None
1952
2393
  httpsProxy = None
1953
2394
  socksProxy = None
1954
2395
  # httpProxy
1955
- if self.value_is_defined(self.httpProxy):
2396
+ isHttpProxyDefined = self.value_is_defined(self.httpProxy)
2397
+ isHttp_proxy_defined = self.value_is_defined(self.http_proxy)
2398
+ if isHttpProxyDefined or isHttp_proxy_defined:
1956
2399
  usedProxies.append('httpProxy')
1957
- httpProxy = self.httpProxy
1958
- if self.value_is_defined(self.http_proxy):
1959
- usedProxies.append('http_proxy')
1960
- httpProxy = self.http_proxy
1961
- if self.httpProxyCallback is not None:
2400
+ httpProxy = self.httpProxy if isHttpProxyDefined else self.http_proxy
2401
+ ishttpProxyCallbackDefined = self.value_is_defined(self.httpProxyCallback)
2402
+ ishttp_proxy_callback_defined = self.value_is_defined(self.http_proxy_callback)
2403
+ if ishttpProxyCallbackDefined or ishttp_proxy_callback_defined:
1962
2404
  usedProxies.append('httpProxyCallback')
1963
- httpProxy = self.httpProxyCallback(url, method, headers, body)
1964
- if self.http_proxy_callback is not None:
1965
- usedProxies.append('http_proxy_callback')
1966
- httpProxy = self.http_proxy_callback(url, method, headers, body)
2405
+ httpProxy = self.httpProxyCallback(url, method, headers, body) if ishttpProxyCallbackDefined else self.http_proxy_callback(url, method, headers, body)
1967
2406
  # httpsProxy
1968
- if self.value_is_defined(self.httpsProxy):
2407
+ isHttpsProxyDefined = self.value_is_defined(self.httpsProxy)
2408
+ isHttps_proxy_defined = self.value_is_defined(self.https_proxy)
2409
+ if isHttpsProxyDefined or isHttps_proxy_defined:
1969
2410
  usedProxies.append('httpsProxy')
1970
- httpsProxy = self.httpsProxy
1971
- if self.value_is_defined(self.https_proxy):
1972
- usedProxies.append('https_proxy')
1973
- httpsProxy = self.https_proxy
1974
- if self.httpsProxyCallback is not None:
2411
+ httpsProxy = self.httpsProxy if isHttpsProxyDefined else self.https_proxy
2412
+ ishttpsProxyCallbackDefined = self.value_is_defined(self.httpsProxyCallback)
2413
+ ishttps_proxy_callback_defined = self.value_is_defined(self.https_proxy_callback)
2414
+ if ishttpsProxyCallbackDefined or ishttps_proxy_callback_defined:
1975
2415
  usedProxies.append('httpsProxyCallback')
1976
- httpsProxy = self.httpsProxyCallback(url, method, headers, body)
1977
- if self.https_proxy_callback is not None:
1978
- usedProxies.append('https_proxy_callback')
1979
- httpsProxy = self.https_proxy_callback(url, method, headers, body)
2416
+ httpsProxy = self.httpsProxyCallback(url, method, headers, body) if ishttpsProxyCallbackDefined else self.https_proxy_callback(url, method, headers, body)
1980
2417
  # socksProxy
1981
- if self.value_is_defined(self.socksProxy):
2418
+ isSocksProxyDefined = self.value_is_defined(self.socksProxy)
2419
+ isSocks_proxy_defined = self.value_is_defined(self.socks_proxy)
2420
+ if isSocksProxyDefined or isSocks_proxy_defined:
1982
2421
  usedProxies.append('socksProxy')
1983
- socksProxy = self.socksProxy
1984
- if self.value_is_defined(self.socks_proxy):
1985
- usedProxies.append('socks_proxy')
1986
- socksProxy = self.socks_proxy
1987
- if self.socksProxyCallback is not None:
2422
+ socksProxy = self.socksProxy if isSocksProxyDefined else self.socks_proxy
2423
+ issocksProxyCallbackDefined = self.value_is_defined(self.socksProxyCallback)
2424
+ issocks_proxy_callback_defined = self.value_is_defined(self.socks_proxy_callback)
2425
+ if issocksProxyCallbackDefined or issocks_proxy_callback_defined:
1988
2426
  usedProxies.append('socksProxyCallback')
1989
- socksProxy = self.socksProxyCallback(url, method, headers, body)
1990
- if self.socks_proxy_callback is not None:
1991
- usedProxies.append('socks_proxy_callback')
1992
- socksProxy = self.socks_proxy_callback(url, method, headers, body)
2427
+ socksProxy = self.socksProxyCallback(url, method, headers, body) if issocksProxyCallbackDefined else self.socks_proxy_callback(url, method, headers, body)
1993
2428
  # check
1994
2429
  length = len(usedProxies)
1995
2430
  if length > 1:
1996
2431
  joinedProxyNames = ','.join(usedProxies)
1997
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: httpProxy, httpsProxy, httpProxyCallback, httpsProxyCallback, socksProxy, socksProxyCallback')
2432
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: httpProxy, httpsProxy, httpProxyCallback, httpsProxyCallback, socksProxy, socksProxyCallback')
1998
2433
  return [httpProxy, httpsProxy, socksProxy]
1999
2434
 
2000
2435
  def check_ws_proxy_settings(self):
@@ -2003,36 +2438,43 @@ class Exchange(object):
2003
2438
  wssProxy = None
2004
2439
  wsSocksProxy = None
2005
2440
  # ws proxy
2006
- if self.value_is_defined(self.wsProxy):
2441
+ isWsProxyDefined = self.value_is_defined(self.wsProxy)
2442
+ is_ws_proxy_defined = self.value_is_defined(self.ws_proxy)
2443
+ if isWsProxyDefined or is_ws_proxy_defined:
2007
2444
  usedProxies.append('wsProxy')
2008
- wsProxy = self.wsProxy
2009
- if self.value_is_defined(self.ws_proxy):
2010
- usedProxies.append('ws_proxy')
2011
- wsProxy = self.ws_proxy
2445
+ wsProxy = self.wsProxy if (isWsProxyDefined) else self.ws_proxy
2012
2446
  # wss proxy
2013
- if self.value_is_defined(self.wssProxy):
2447
+ isWssProxyDefined = self.value_is_defined(self.wssProxy)
2448
+ is_wss_proxy_defined = self.value_is_defined(self.wss_proxy)
2449
+ if isWssProxyDefined or is_wss_proxy_defined:
2014
2450
  usedProxies.append('wssProxy')
2015
- wssProxy = self.wssProxy
2016
- if self.value_is_defined(self.wss_proxy):
2017
- usedProxies.append('wss_proxy')
2018
- wssProxy = self.wss_proxy
2451
+ wssProxy = self.wssProxy if (isWssProxyDefined) else self.wss_proxy
2019
2452
  # ws socks proxy
2020
- if self.value_is_defined(self.wsSocksProxy):
2453
+ isWsSocksProxyDefined = self.value_is_defined(self.wsSocksProxy)
2454
+ is_ws_socks_proxy_defined = self.value_is_defined(self.ws_socks_proxy)
2455
+ if isWsSocksProxyDefined or is_ws_socks_proxy_defined:
2021
2456
  usedProxies.append('wsSocksProxy')
2022
- wsSocksProxy = self.wsSocksProxy
2023
- if self.value_is_defined(self.ws_socks_proxy):
2024
- usedProxies.append('ws_socks_proxy')
2025
- wsSocksProxy = self.ws_socks_proxy
2457
+ wsSocksProxy = self.wsSocksProxy if (isWsSocksProxyDefined) else self.ws_socks_proxy
2026
2458
  # check
2027
2459
  length = len(usedProxies)
2028
2460
  if length > 1:
2029
2461
  joinedProxyNames = ','.join(usedProxies)
2030
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: wsProxy, wssProxy, wsSocksProxy')
2462
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: wsProxy, wssProxy, wsSocksProxy')
2031
2463
  return [wsProxy, wssProxy, wsSocksProxy]
2032
2464
 
2033
2465
  def check_conflicting_proxies(self, proxyAgentSet, proxyUrlSet):
2034
2466
  if proxyAgentSet and proxyUrlSet:
2035
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings, please use only one from : proxyUrl, httpProxy, httpsProxy, socksProxy')
2467
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings, please use only one from : proxyUrl, httpProxy, httpsProxy, socksProxy')
2468
+
2469
+ def check_address(self, address: Str = None):
2470
+ if address is None:
2471
+ raise InvalidAddress(self.id + ' address is None')
2472
+ # check the address is not the same letter like 'aaaaa' nor too short nor has a space
2473
+ uniqChars = (self.unique(self.string_to_chars_array(address)))
2474
+ length = len(uniqChars) # py transpiler trick
2475
+ if length == 1 or len(address) < self.minFundingAddressLength or address.find(' ') > -1:
2476
+ raise InvalidAddress(self.id + ' address is invalid or has less than ' + str(self.minFundingAddressLength) + ' characters: "' + str(address) + '"')
2477
+ return address
2036
2478
 
2037
2479
  def find_message_hashes(self, client, element: str):
2038
2480
  result = []
@@ -2056,9 +2498,17 @@ class Exchange(object):
2056
2498
  if fromStart:
2057
2499
  if limit > arrayLength:
2058
2500
  limit = arrayLength
2059
- array = self.array_slice(array, 0, limit) if ascending else self.array_slice(array, -limit)
2501
+ # array = self.array_slice(array, 0, limit) if ascending else self.array_slice(array, -limit)
2502
+ if ascending:
2503
+ array = self.array_slice(array, 0, limit)
2504
+ else:
2505
+ array = self.array_slice(array, -limit)
2060
2506
  else:
2061
- array = self.array_slice(array, -limit) if ascending else self.array_slice(array, 0, limit)
2507
+ # array = self.array_slice(array, -limit) if ascending else self.array_slice(array, 0, limit)
2508
+ if ascending:
2509
+ array = self.array_slice(array, -limit)
2510
+ else:
2511
+ array = self.array_slice(array, 0, limit)
2062
2512
  return array
2063
2513
 
2064
2514
  def filter_by_since_limit(self, array: List[object], since: Int = None, limit: Int = None, key: IndexType = 'timestamp', tail=False):
@@ -2092,7 +2542,7 @@ class Exchange(object):
2092
2542
  entryFiledEqualValue = entry[field] == value
2093
2543
  firstCondition = entryFiledEqualValue if valueIsDefined else True
2094
2544
  entryKeyValue = self.safe_value(entry, key)
2095
- entryKeyGESince = (entryKeyValue) and since and (entryKeyValue >= since)
2545
+ entryKeyGESince = (entryKeyValue) and (since is not None) and (entryKeyValue >= since)
2096
2546
  secondCondition = entryKeyGESince if sinceIsDefined else True
2097
2547
  if firstCondition and secondCondition:
2098
2548
  result.append(entry)
@@ -2101,6 +2551,10 @@ class Exchange(object):
2101
2551
  return self.filter_by_limit(result, limit, key, sinceIsDefined)
2102
2552
 
2103
2553
  def set_sandbox_mode(self, enabled: bool):
2554
+ """
2555
+ set the sandbox mode for the exchange
2556
+ :param boolean enabled: True to enable sandbox mode, False to disable it
2557
+ """
2104
2558
  if enabled:
2105
2559
  if 'test' in self.urls:
2106
2560
  if isinstance(self.urls['api'], str):
@@ -2111,6 +2565,8 @@ class Exchange(object):
2111
2565
  self.urls['api'] = self.clone(self.urls['test'])
2112
2566
  else:
2113
2567
  raise NotSupported(self.id + ' does not have a sandbox URL')
2568
+ # set flag
2569
+ self.isSandboxModeEnabled = True
2114
2570
  elif 'apiBackup' in self.urls:
2115
2571
  if isinstance(self.urls['api'], str):
2116
2572
  self.urls['api'] = self.urls['apiBackup']
@@ -2118,6 +2574,8 @@ class Exchange(object):
2118
2574
  self.urls['api'] = self.clone(self.urls['apiBackup'])
2119
2575
  newUrls = self.omit(self.urls, 'apiBackup')
2120
2576
  self.urls = newUrls
2577
+ # set flag
2578
+ self.isSandboxModeEnabled = False
2121
2579
 
2122
2580
  def sign(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None):
2123
2581
  return {}
@@ -2150,9 +2608,18 @@ class Exchange(object):
2150
2608
  def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2151
2609
  raise NotSupported(self.id + ' watchTrades() is not supported yet')
2152
2610
 
2611
+ def un_watch_orders(self, symbol: Str = None, params={}):
2612
+ raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
2613
+
2614
+ def un_watch_trades(self, symbol: str, params={}):
2615
+ raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
2616
+
2153
2617
  def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2154
2618
  raise NotSupported(self.id + ' watchTradesForSymbols() is not supported yet')
2155
2619
 
2620
+ def un_watch_trades_for_symbols(self, symbols: List[str], params={}):
2621
+ raise NotSupported(self.id + ' unWatchTradesForSymbols() is not supported yet')
2622
+
2156
2623
  def watch_my_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2157
2624
  raise NotSupported(self.id + ' watchMyTradesForSymbols() is not supported yet')
2158
2625
 
@@ -2162,15 +2629,27 @@ class Exchange(object):
2162
2629
  def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
2163
2630
  raise NotSupported(self.id + ' watchOHLCVForSymbols() is not supported yet')
2164
2631
 
2632
+ def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}):
2633
+ raise NotSupported(self.id + ' unWatchOHLCVForSymbols() is not supported yet')
2634
+
2165
2635
  def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}):
2166
2636
  raise NotSupported(self.id + ' watchOrderBookForSymbols() is not supported yet')
2167
2637
 
2638
+ def un_watch_order_book_for_symbols(self, symbols: List[str], params={}):
2639
+ raise NotSupported(self.id + ' unWatchOrderBookForSymbols() is not supported yet')
2640
+
2641
+ def un_watch_positions(self, symbols: Strings = None, params={}):
2642
+ raise NotSupported(self.id + ' unWatchPositions() is not supported yet')
2643
+
2168
2644
  def fetch_deposit_addresses(self, codes: Strings = None, params={}):
2169
2645
  raise NotSupported(self.id + ' fetchDepositAddresses() is not supported yet')
2170
2646
 
2171
2647
  def fetch_order_book(self, symbol: str, limit: Int = None, params={}):
2172
2648
  raise NotSupported(self.id + ' fetchOrderBook() is not supported yet')
2173
2649
 
2650
+ def fetch_order_book_ws(self, symbol: str, limit: Int = None, params={}):
2651
+ raise NotSupported(self.id + ' fetchOrderBookWs() is not supported yet')
2652
+
2174
2653
  def fetch_margin_mode(self, symbol: str, params={}):
2175
2654
  if self.has['fetchMarginModes']:
2176
2655
  marginModes = self.fetch_margin_modes([symbol], params)
@@ -2195,12 +2674,27 @@ class Exchange(object):
2195
2674
  def watch_order_book(self, symbol: str, limit: Int = None, params={}):
2196
2675
  raise NotSupported(self.id + ' watchOrderBook() is not supported yet')
2197
2676
 
2677
+ def un_watch_order_book(self, symbol: str, params={}):
2678
+ raise NotSupported(self.id + ' unWatchOrderBook() is not supported yet')
2679
+
2198
2680
  def fetch_time(self, params={}):
2199
2681
  raise NotSupported(self.id + ' fetchTime() is not supported yet')
2200
2682
 
2201
2683
  def fetch_trading_limits(self, symbols: Strings = None, params={}):
2202
2684
  raise NotSupported(self.id + ' fetchTradingLimits() is not supported yet')
2203
2685
 
2686
+ def parse_currency(self, rawCurrency: dict):
2687
+ raise NotSupported(self.id + ' parseCurrency() is not supported yet')
2688
+
2689
+ def parse_currencies(self, rawCurrencies):
2690
+ result = {}
2691
+ arr = self.to_array(rawCurrencies)
2692
+ for i in range(0, len(arr)):
2693
+ parsed = self.parse_currency(arr[i])
2694
+ code = parsed['code']
2695
+ result[code] = parsed
2696
+ return result
2697
+
2204
2698
  def parse_market(self, market: dict):
2205
2699
  raise NotSupported(self.id + ' parseMarket() is not supported yet')
2206
2700
 
@@ -2225,7 +2719,7 @@ class Exchange(object):
2225
2719
  def parse_transfer(self, transfer: dict, currency: Currency = None):
2226
2720
  raise NotSupported(self.id + ' parseTransfer() is not supported yet')
2227
2721
 
2228
- def parse_account(self, account):
2722
+ def parse_account(self, account: dict):
2229
2723
  raise NotSupported(self.id + ' parseAccount() is not supported yet')
2230
2724
 
2231
2725
  def parse_ledger_entry(self, item: dict, currency: Currency = None):
@@ -2255,16 +2749,16 @@ class Exchange(object):
2255
2749
  def parse_borrow_interest(self, info: dict, market: Market = None):
2256
2750
  raise NotSupported(self.id + ' parseBorrowInterest() is not supported yet')
2257
2751
 
2258
- def parse_isolated_borrow_rate(self, info, market: Market = None):
2752
+ def parse_isolated_borrow_rate(self, info: dict, market: Market = None):
2259
2753
  raise NotSupported(self.id + ' parseIsolatedBorrowRate() is not supported yet')
2260
2754
 
2261
- def parse_ws_trade(self, trade, market: Market = None):
2755
+ def parse_ws_trade(self, trade: dict, market: Market = None):
2262
2756
  raise NotSupported(self.id + ' parseWsTrade() is not supported yet')
2263
2757
 
2264
- def parse_ws_order(self, order, market: Market = None):
2758
+ def parse_ws_order(self, order: dict, market: Market = None):
2265
2759
  raise NotSupported(self.id + ' parseWsOrder() is not supported yet')
2266
2760
 
2267
- def parse_ws_order_trade(self, trade, market: Market = None):
2761
+ def parse_ws_order_trade(self, trade: dict, market: Market = None):
2268
2762
  raise NotSupported(self.id + ' parseWsOrderTrade() is not supported yet')
2269
2763
 
2270
2764
  def parse_ws_ohlcv(self, ohlcv, market: Market = None):
@@ -2273,6 +2767,9 @@ class Exchange(object):
2273
2767
  def fetch_funding_rates(self, symbols: Strings = None, params={}):
2274
2768
  raise NotSupported(self.id + ' fetchFundingRates() is not supported yet')
2275
2769
 
2770
+ def fetch_funding_intervals(self, symbols: Strings = None, params={}):
2771
+ raise NotSupported(self.id + ' fetchFundingIntervals() is not supported yet')
2772
+
2276
2773
  def watch_funding_rate(self, symbol: str, params={}):
2277
2774
  raise NotSupported(self.id + ' watchFundingRate() is not supported yet')
2278
2775
 
@@ -2285,13 +2782,13 @@ class Exchange(object):
2285
2782
  def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}):
2286
2783
  raise NotSupported(self.id + ' transfer() is not supported yet')
2287
2784
 
2288
- def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
2785
+ def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}):
2289
2786
  raise NotSupported(self.id + ' withdraw() is not supported yet')
2290
2787
 
2291
2788
  def create_deposit_address(self, code: str, params={}):
2292
2789
  raise NotSupported(self.id + ' createDepositAddress() is not supported yet')
2293
2790
 
2294
- def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
2791
+ def set_leverage(self, leverage: int, symbol: Str = None, params={}):
2295
2792
  raise NotSupported(self.id + ' setLeverage() is not supported yet')
2296
2793
 
2297
2794
  def fetch_leverage(self, symbol: str, params={}):
@@ -2316,6 +2813,12 @@ class Exchange(object):
2316
2813
  def set_margin(self, symbol: str, amount: float, params={}):
2317
2814
  raise NotSupported(self.id + ' setMargin() is not supported yet')
2318
2815
 
2816
+ def fetch_long_short_ratio(self, symbol: str, timeframe: Str = None, params={}):
2817
+ raise NotSupported(self.id + ' fetchLongShortRatio() is not supported yet')
2818
+
2819
+ def fetch_long_short_ratio_history(self, symbol: Str = None, timeframe: Str = None, since: Int = None, limit: Int = None, params={}):
2820
+ raise NotSupported(self.id + ' fetchLongShortRatioHistory() is not supported yet')
2821
+
2319
2822
  def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}):
2320
2823
  """
2321
2824
  fetches the history of margin added or reduced from contract isolated positions
@@ -2334,12 +2837,15 @@ class Exchange(object):
2334
2837
  def fetch_deposit_addresses_by_network(self, code: str, params={}):
2335
2838
  raise NotSupported(self.id + ' fetchDepositAddressesByNetwork() is not supported yet')
2336
2839
 
2337
- def fetch_open_interest_history(self, symbol: str, timeframe='1h', since: Int = None, limit: Int = None, params={}):
2840
+ def fetch_open_interest_history(self, symbol: str, timeframe: str = '1h', since: Int = None, limit: Int = None, params={}):
2338
2841
  raise NotSupported(self.id + ' fetchOpenInterestHistory() is not supported yet')
2339
2842
 
2340
2843
  def fetch_open_interest(self, symbol: str, params={}):
2341
2844
  raise NotSupported(self.id + ' fetchOpenInterest() is not supported yet')
2342
2845
 
2846
+ def fetch_open_interests(self, symbols: Strings = None, params={}):
2847
+ raise NotSupported(self.id + ' fetchOpenInterests() is not supported yet')
2848
+
2343
2849
  def sign_in(self, params={}):
2344
2850
  raise NotSupported(self.id + ' signIn() is not supported yet')
2345
2851
 
@@ -2358,7 +2864,7 @@ class Exchange(object):
2358
2864
  # keep self in mind:
2359
2865
  # in JS: 1 == 1.0 is True; 1 == 1.0 is True
2360
2866
  # in Python: 1 == 1.0 is True
2361
- # in PHP 1 == 1.0 is True, but 1 == 1.0 is False
2867
+ # in PHP 1 == 1.0 is True, but 1 == 1.0 is False.
2362
2868
  if stringVersion.find('.') >= 0:
2363
2869
  return float(stringVersion)
2364
2870
  return int(stringVersion)
@@ -2369,6 +2875,11 @@ class Exchange(object):
2369
2875
  res = self.parse_to_numeric((value % 1))
2370
2876
  return res == 0
2371
2877
 
2878
+ def safe_number_omit_zero(self, obj: object, key: IndexType, defaultValue: Num = None):
2879
+ value = self.safe_string(obj, key)
2880
+ final = self.parse_number(self.omit_zero(value))
2881
+ return defaultValue if (final is None) else final
2882
+
2372
2883
  def safe_integer_omit_zero(self, obj: object, key: IndexType, defaultValue: Int = None):
2373
2884
  timestamp = self.safe_integer(obj, key, defaultValue)
2374
2885
  if timestamp is None or timestamp == 0:
@@ -2376,7 +2887,113 @@ class Exchange(object):
2376
2887
  return timestamp
2377
2888
 
2378
2889
  def after_construct(self):
2890
+ # networks
2379
2891
  self.create_networks_by_id_object()
2892
+ self.features_generator()
2893
+ # init predefined markets if any
2894
+ if self.markets:
2895
+ self.set_markets(self.markets)
2896
+ # init the request rate limiter
2897
+ self.init_rest_rate_limiter()
2898
+ # sanbox mode
2899
+ isSandbox = self.safe_bool_2(self.options, 'sandbox', 'testnet', False)
2900
+ if isSandbox:
2901
+ self.set_sandbox_mode(isSandbox)
2902
+
2903
+ def init_rest_rate_limiter(self):
2904
+ if self.rateLimit is None or (self.id is not None and self.rateLimit == -1):
2905
+ raise ExchangeError(self.id + '.rateLimit property is not configured')
2906
+ refillRate = self.MAX_VALUE
2907
+ if self.rateLimit > 0:
2908
+ refillRate = 1 / self.rateLimit
2909
+ defaultBucket = {
2910
+ 'delay': 0.001,
2911
+ 'capacity': 1,
2912
+ 'cost': 1,
2913
+ 'maxCapacity': 1000,
2914
+ 'refillRate': refillRate,
2915
+ }
2916
+ existingBucket = {} if (self.tokenBucket is None) else self.tokenBucket
2917
+ self.tokenBucket = self.extend(defaultBucket, existingBucket)
2918
+ self.init_throttler()
2919
+
2920
+ def features_generator(self):
2921
+ #
2922
+ # in the exchange-specific features can be something like self, where we support 'string' aliases too:
2923
+ #
2924
+ # {
2925
+ # 'my' : {
2926
+ # 'createOrder' : {...},
2927
+ # },
2928
+ # 'swap': {
2929
+ # 'linear': {
2930
+ # 'extends': my',
2931
+ # },
2932
+ # },
2933
+ # }
2934
+ #
2935
+ if self.features is None:
2936
+ return
2937
+ # reconstruct
2938
+ initialFeatures = self.features
2939
+ self.features = {}
2940
+ unifiedMarketTypes = ['spot', 'swap', 'future', 'option']
2941
+ subTypes = ['linear', 'inverse']
2942
+ # atm only support basic methods, eg: 'createOrder', 'fetchOrder', 'fetchOrders', 'fetchMyTrades'
2943
+ for i in range(0, len(unifiedMarketTypes)):
2944
+ marketType = unifiedMarketTypes[i]
2945
+ # if marketType is not filled for self exchange, don't add that in `features`
2946
+ if not (marketType in initialFeatures):
2947
+ self.features[marketType] = None
2948
+ else:
2949
+ if marketType == 'spot':
2950
+ self.features[marketType] = self.features_mapper(initialFeatures, marketType, None)
2951
+ else:
2952
+ self.features[marketType] = {}
2953
+ for j in range(0, len(subTypes)):
2954
+ subType = subTypes[j]
2955
+ self.features[marketType][subType] = self.features_mapper(initialFeatures, marketType, subType)
2956
+
2957
+ def features_mapper(self, initialFeatures: Any, marketType: Str, subType: Str = None):
2958
+ featuresObj = initialFeatures[marketType][subType] if (subType is not None) else initialFeatures[marketType]
2959
+ # if exchange does not have that market-type(eg. future>inverse)
2960
+ if featuresObj is None:
2961
+ return None
2962
+ extendsStr: Str = self.safe_string(featuresObj, 'extends')
2963
+ if extendsStr is not None:
2964
+ featuresObj = self.omit(featuresObj, 'extends')
2965
+ extendObj = self.features_mapper(initialFeatures, extendsStr)
2966
+ featuresObj = self.deep_extend(extendObj, featuresObj)
2967
+ #
2968
+ # ### corrections ###
2969
+ #
2970
+ # createOrder
2971
+ if 'createOrder' in featuresObj:
2972
+ value = self.safe_dict(featuresObj['createOrder'], 'attachedStopLossTakeProfit')
2973
+ featuresObj['createOrder']['stopLoss'] = value
2974
+ featuresObj['createOrder']['takeProfit'] = value
2975
+ if marketType == 'spot':
2976
+ # default 'hedged': False
2977
+ featuresObj['createOrder']['hedged'] = False
2978
+ # default 'leverage': False
2979
+ if not ('leverage' in featuresObj['createOrder']):
2980
+ featuresObj['createOrder']['leverage'] = False
2981
+ # default 'GTC' to True
2982
+ if self.safe_bool(featuresObj['createOrder']['timeInForce'], 'GTC') is None:
2983
+ featuresObj['createOrder']['timeInForce']['GTC'] = True
2984
+ # other methods
2985
+ keys = list(featuresObj.keys())
2986
+ for i in range(0, len(keys)):
2987
+ key = keys[i]
2988
+ featureBlock = featuresObj[key]
2989
+ if not self.in_array(key, ['sandbox']) and featureBlock is not None:
2990
+ # default "symbolRequired" to False to all methods(except `createOrder`)
2991
+ if not ('symbolRequired' in featureBlock):
2992
+ featureBlock['symbolRequired'] = self.in_array(key, ['createOrder', 'createOrders', 'fetchOHLCV'])
2993
+ return featuresObj
2994
+
2995
+ def orderbook_checksum_message(self, symbol: Str):
2996
+ return symbol + ' = False'
2380
2997
 
2381
2998
  def create_networks_by_id_object(self):
2382
2999
  # automatically generate network-id-to-code mappings
@@ -2389,6 +3006,7 @@ class Exchange(object):
2389
3006
  'ETH': {'ERC20': 'ETH'},
2390
3007
  'TRX': {'TRC20': 'TRX'},
2391
3008
  'CRO': {'CRC20': 'CRONOS'},
3009
+ 'BRC20': {'BRC20': 'BTC'},
2392
3010
  },
2393
3011
  }
2394
3012
 
@@ -2433,6 +3051,78 @@ class Exchange(object):
2433
3051
  }
2434
3052
 
2435
3053
  def safe_currency_structure(self, currency: object):
3054
+ # derive data from networks: deposit, withdraw, active, fee, limits, precision
3055
+ networks = self.safe_dict(currency, 'networks', {})
3056
+ keys = list(networks.keys())
3057
+ length = len(keys)
3058
+ if length != 0:
3059
+ for i in range(0, length):
3060
+ key = keys[i]
3061
+ network = networks[key]
3062
+ deposit = self.safe_bool(network, 'deposit')
3063
+ currencyDeposit = self.safe_bool(currency, 'deposit')
3064
+ if currencyDeposit is None or deposit:
3065
+ currency['deposit'] = deposit
3066
+ withdraw = self.safe_bool(network, 'withdraw')
3067
+ currencyWithdraw = self.safe_bool(currency, 'withdraw')
3068
+ if currencyWithdraw is None or withdraw:
3069
+ currency['withdraw'] = withdraw
3070
+ # set network 'active' to False if D or W is disabled
3071
+ active = self.safe_bool(network, 'active')
3072
+ if active is None:
3073
+ if deposit and withdraw:
3074
+ currency['networks'][key]['active'] = True
3075
+ elif deposit is not None and withdraw is not None:
3076
+ currency['networks'][key]['active'] = False
3077
+ active = self.safe_bool(currency['networks'][key], 'active') # dict might have been updated on above lines, so access directly instead of `network` variable
3078
+ currencyActive = self.safe_bool(currency, 'active')
3079
+ if currencyActive is None or active:
3080
+ currency['active'] = active
3081
+ # find lowest fee(which is more desired)
3082
+ fee = self.safe_string(network, 'fee')
3083
+ feeMain = self.safe_string(currency, 'fee')
3084
+ if feeMain is None or Precise.string_lt(fee, feeMain):
3085
+ currency['fee'] = self.parse_number(fee)
3086
+ # find lowest precision(which is more desired)
3087
+ precision = self.safe_string(network, 'precision')
3088
+ precisionMain = self.safe_string(currency, 'precision')
3089
+ if precisionMain is None or Precise.string_gt(precision, precisionMain):
3090
+ currency['precision'] = self.parse_number(precision)
3091
+ # limits
3092
+ limits = self.safe_dict(network, 'limits')
3093
+ limitsMain = self.safe_dict(currency, 'limits')
3094
+ if limitsMain is None:
3095
+ currency['limits'] = {}
3096
+ # deposits
3097
+ limitsDeposit = self.safe_dict(limits, 'deposit')
3098
+ limitsDepositMain = self.safe_dict(limitsMain, 'deposit')
3099
+ if limitsDepositMain is None:
3100
+ currency['limits']['deposit'] = {}
3101
+ limitsDepositMin = self.safe_string(limitsDeposit, 'min')
3102
+ limitsDepositMax = self.safe_string(limitsDeposit, 'max')
3103
+ limitsDepositMinMain = self.safe_string(limitsDepositMain, 'min')
3104
+ limitsDepositMaxMain = self.safe_string(limitsDepositMain, 'max')
3105
+ # find min
3106
+ if limitsDepositMinMain is None or Precise.string_lt(limitsDepositMin, limitsDepositMinMain):
3107
+ currency['limits']['deposit']['min'] = self.parse_number(limitsDepositMin)
3108
+ # find max
3109
+ if limitsDepositMaxMain is None or Precise.string_gt(limitsDepositMax, limitsDepositMaxMain):
3110
+ currency['limits']['deposit']['max'] = self.parse_number(limitsDepositMax)
3111
+ # withdrawals
3112
+ limitsWithdraw = self.safe_dict(limits, 'withdraw')
3113
+ limitsWithdrawMain = self.safe_dict(limitsMain, 'withdraw')
3114
+ if limitsWithdrawMain is None:
3115
+ currency['limits']['withdraw'] = {}
3116
+ limitsWithdrawMin = self.safe_string(limitsWithdraw, 'min')
3117
+ limitsWithdrawMax = self.safe_string(limitsWithdraw, 'max')
3118
+ limitsWithdrawMinMain = self.safe_string(limitsWithdrawMain, 'min')
3119
+ limitsWithdrawMaxMain = self.safe_string(limitsWithdrawMain, 'max')
3120
+ # find min
3121
+ if limitsWithdrawMinMain is None or Precise.string_lt(limitsWithdrawMin, limitsWithdrawMinMain):
3122
+ currency['limits']['withdraw']['min'] = self.parse_number(limitsWithdrawMin)
3123
+ # find max
3124
+ if limitsWithdrawMaxMain is None or Precise.string_gt(limitsWithdrawMax, limitsWithdrawMaxMain):
3125
+ currency['limits']['withdraw']['max'] = self.parse_number(limitsWithdrawMax)
2436
3126
  return self.extend({
2437
3127
  'info': None,
2438
3128
  'id': None,
@@ -2459,7 +3149,7 @@ class Exchange(object):
2459
3149
  },
2460
3150
  }, currency)
2461
3151
 
2462
- def safe_market_structure(self, market=None):
3152
+ def safe_market_structure(self, market: dict = None):
2463
3153
  cleanStructure = {
2464
3154
  'id': None,
2465
3155
  'lowercaseId': None,
@@ -2514,6 +3204,10 @@ class Exchange(object):
2514
3204
  'max': None,
2515
3205
  },
2516
3206
  },
3207
+ 'marginModes': {
3208
+ 'cross': None,
3209
+ 'isolated': None,
3210
+ },
2517
3211
  'created': None,
2518
3212
  'info': None,
2519
3213
  }
@@ -2536,14 +3230,16 @@ class Exchange(object):
2536
3230
 
2537
3231
  def set_markets(self, markets, currencies=None):
2538
3232
  values = []
2539
- self.markets_by_id = {}
3233
+ self.markets_by_id = self.create_safe_dictionary()
2540
3234
  # handle marketId conflicts
2541
3235
  # we insert spot markets first
2542
3236
  marketValues = self.sort_by(self.to_array(markets), 'spot', True, True)
2543
3237
  for i in range(0, len(marketValues)):
2544
3238
  value = marketValues[i]
2545
3239
  if value['id'] in self.markets_by_id:
2546
- (self.markets_by_id[value['id']]).append(value)
3240
+ marketsByIdArray = (self.markets_by_id[value['id']])
3241
+ marketsByIdArray.append(value)
3242
+ self.markets_by_id[value['id']] = marketsByIdArray
2547
3243
  else:
2548
3244
  self.markets_by_id[value['id']] = [value]
2549
3245
  market = self.deep_extend(self.safe_market_structure(), {
@@ -2557,14 +3253,14 @@ class Exchange(object):
2557
3253
  else:
2558
3254
  market['subType'] = None
2559
3255
  values.append(market)
2560
- self.markets = self.index_by(values, 'symbol')
3256
+ self.markets = self.map_to_safe_map(self.index_by(values, 'symbol'))
2561
3257
  marketsSortedBySymbol = self.keysort(self.markets)
2562
3258
  marketsSortedById = self.keysort(self.markets_by_id)
2563
3259
  self.symbols = list(marketsSortedBySymbol.keys())
2564
3260
  self.ids = list(marketsSortedById.keys())
2565
3261
  if currencies is not None:
2566
3262
  # currencies is always None when called in constructor but not when called from loadMarkets
2567
- self.currencies = self.deep_extend(self.currencies, currencies)
3263
+ self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, currencies))
2568
3264
  else:
2569
3265
  baseCurrencies = []
2570
3266
  quoteCurrencies = []
@@ -2590,8 +3286,8 @@ class Exchange(object):
2590
3286
  quoteCurrencies.append(currency)
2591
3287
  baseCurrencies = self.sort_by(baseCurrencies, 'code', False, '')
2592
3288
  quoteCurrencies = self.sort_by(quoteCurrencies, 'code', False, '')
2593
- self.baseCurrencies = self.index_by(baseCurrencies, 'code')
2594
- self.quoteCurrencies = self.index_by(quoteCurrencies, 'code')
3289
+ self.baseCurrencies = self.map_to_safe_map(self.index_by(baseCurrencies, 'code'))
3290
+ self.quoteCurrencies = self.map_to_safe_map(self.index_by(quoteCurrencies, 'code'))
2595
3291
  allCurrencies = self.array_concat(baseCurrencies, quoteCurrencies)
2596
3292
  groupedCurrencies = self.group_by(allCurrencies, 'code')
2597
3293
  codes = list(groupedCurrencies.keys())
@@ -2608,8 +3304,8 @@ class Exchange(object):
2608
3304
  highestPrecisionCurrency = currentCurrency if (currentCurrency['precision'] > highestPrecisionCurrency['precision']) else highestPrecisionCurrency
2609
3305
  resultingCurrencies.append(highestPrecisionCurrency)
2610
3306
  sortedCurrencies = self.sort_by(resultingCurrencies, 'code')
2611
- self.currencies = self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code'))
2612
- self.currencies_by_id = self.index_by(self.currencies, 'id')
3307
+ self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code')))
3308
+ self.currencies_by_id = self.index_by_safe(self.currencies, 'id')
2613
3309
  currenciesSortedByCode = self.keysort(self.currencies)
2614
3310
  self.codes = list(currenciesSortedByCode.keys())
2615
3311
  return self.markets
@@ -2619,7 +3315,7 @@ class Exchange(object):
2619
3315
  superWithRestDescribe = self.deep_extend(extendedRestDescribe, wsBaseDescribe)
2620
3316
  return superWithRestDescribe
2621
3317
 
2622
- def safe_balance(self, balance: object):
3318
+ def safe_balance(self, balance: dict):
2623
3319
  balances = self.omit(balance, ['info', 'timestamp', 'datetime', 'free', 'used', 'total'])
2624
3320
  codes = list(balances.keys())
2625
3321
  balance['free'] = {}
@@ -2653,7 +3349,7 @@ class Exchange(object):
2653
3349
  balance['debt'] = debtBalance
2654
3350
  return balance
2655
3351
 
2656
- def safe_order(self, order: object, market: Market = None):
3352
+ def safe_order(self, order: dict, market: Market = None):
2657
3353
  # parses numbers
2658
3354
  # * it is important pass the trades rawTrades
2659
3355
  amount = self.omit_zero(self.safe_string(order, 'amount'))
@@ -2680,9 +3376,10 @@ class Exchange(object):
2680
3376
  isTriggerOrSLTpOrder = ((self.safe_string(order, 'triggerPrice') is not None or (self.safe_string(order, 'stopLossPrice') is not None)) or (self.safe_string(order, 'takeProfitPrice') is not None))
2681
3377
  if parseFilled or parseCost or shouldParseFees:
2682
3378
  rawTrades = self.safe_value(order, 'trades', trades)
2683
- oldNumber = self.number
3379
+ # oldNumber = self.number
2684
3380
  # we parse trades here!
2685
- self.number = str
3381
+ # i don't think self is needed anymore
3382
+ # self.number = str
2686
3383
  firstTrade = self.safe_value(rawTrades, 0)
2687
3384
  # parse trades if they haven't already been parsed
2688
3385
  tradesAreParsed = ((firstTrade is not None) and ('info' in firstTrade) and ('id' in firstTrade))
@@ -2690,7 +3387,7 @@ class Exchange(object):
2690
3387
  trades = self.parse_trades(rawTrades, market)
2691
3388
  else:
2692
3389
  trades = rawTrades
2693
- self.number = oldNumber
3390
+ # self.number = oldNumber; why parse trades if you read the value using `safeString` ?
2694
3391
  tradesLength = 0
2695
3392
  isArray = isinstance(trades, list)
2696
3393
  if isArray:
@@ -2897,30 +3594,22 @@ class Exchange(object):
2897
3594
  results = []
2898
3595
  if isinstance(orders, list):
2899
3596
  for i in range(0, len(orders)):
2900
- order = self.extend(self.parse_order(orders[i], market), params)
3597
+ parsed = self.parse_order(orders[i], market) # don't inline self call
3598
+ order = self.extend(parsed, params)
2901
3599
  results.append(order)
2902
3600
  else:
2903
3601
  ids = list(orders.keys())
2904
3602
  for i in range(0, len(ids)):
2905
3603
  id = ids[i]
2906
- order = self.extend(self.parse_order(self.extend({'id': id}, orders[id]), market), params)
3604
+ idExtended = self.extend({'id': id}, orders[id])
3605
+ parsedOrder = self.parse_order(idExtended, market) # don't inline these calls
3606
+ order = self.extend(parsedOrder, params)
2907
3607
  results.append(order)
2908
3608
  results = self.sort_by(results, 'timestamp')
2909
3609
  symbol = market['symbol'] if (market is not None) else None
2910
3610
  return self.filter_by_symbol_since_limit(results, symbol, since, limit)
2911
3611
 
2912
- def calculate_fee(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', params={}):
2913
- """
2914
- calculates the presumptive fee that would be charged for an order
2915
- :param str symbol: unified market symbol
2916
- :param str type: 'market' or 'limit'
2917
- :param str side: 'buy' or 'sell'
2918
- :param float amount: how much you want to trade, in units of the base currency on most exchanges, or number of contracts
2919
- :param float price: the price for the order to be filled at, in units of the quote currency
2920
- :param str takerOrMaker: 'taker' or 'maker'
2921
- :param dict params:
2922
- :returns dict: contains the rate, the percentage multiplied to the order amount to obtain the fee amount, and cost, the total value of the fee in units of the quote currency, for the order
2923
- """
3612
+ def calculate_fee_with_rate(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', feeRate: Num = None, params={}):
2924
3613
  if type == 'market' and takerOrMaker == 'maker':
2925
3614
  raise ArgumentsRequired(self.id + ' calculateFee() - you have provided incompatible arguments - "market" type order can not be "maker". Change either the "type" or the "takerOrMaker" argument to calculate the fee.')
2926
3615
  market = self.markets[symbol]
@@ -2949,7 +3638,7 @@ class Exchange(object):
2949
3638
  # even if `takerOrMaker` argument was set to 'maker', for 'market' orders we should forcefully override it to 'taker'
2950
3639
  if type == 'market':
2951
3640
  takerOrMaker = 'taker'
2952
- rate = self.safe_string(market, takerOrMaker)
3641
+ rate = self.number_to_string(feeRate) if (feeRate is not None) else self.safe_string(market, takerOrMaker)
2953
3642
  cost = Precise.string_mul(cost, rate)
2954
3643
  return {
2955
3644
  'type': takerOrMaker,
@@ -2958,7 +3647,21 @@ class Exchange(object):
2958
3647
  'cost': self.parse_number(cost),
2959
3648
  }
2960
3649
 
2961
- def safe_liquidation(self, liquidation: object, market: Market = None):
3650
+ def calculate_fee(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', params={}):
3651
+ """
3652
+ calculates the presumptive fee that would be charged for an order
3653
+ :param str symbol: unified market symbol
3654
+ :param str type: 'market' or 'limit'
3655
+ :param str side: 'buy' or 'sell'
3656
+ :param float amount: how much you want to trade, in units of the base currency on most exchanges, or number of contracts
3657
+ :param float price: the price for the order to be filled at, in units of the quote currency
3658
+ :param str takerOrMaker: 'taker' or 'maker'
3659
+ :param dict params:
3660
+ :returns dict: contains the rate, the percentage multiplied to the order amount to obtain the fee amount, and cost, the total value of the fee in units of the quote currency, for the order
3661
+ """
3662
+ return self.calculate_fee_with_rate(symbol, type, side, amount, price, takerOrMaker, None, params)
3663
+
3664
+ def safe_liquidation(self, liquidation: dict, market: Market = None):
2962
3665
  contracts = self.safe_string(liquidation, 'contracts')
2963
3666
  contractSize = self.safe_string(market, 'contractSize')
2964
3667
  price = self.safe_string(liquidation, 'price')
@@ -2975,7 +3678,7 @@ class Exchange(object):
2975
3678
  liquidation['quoteValue'] = self.parse_number(quoteValue)
2976
3679
  return liquidation
2977
3680
 
2978
- def safe_trade(self, trade: object, market: Market = None):
3681
+ def safe_trade(self, trade: dict, market: Market = None):
2979
3682
  amount = self.safe_string(trade, 'amount')
2980
3683
  price = self.safe_string(trade, 'price')
2981
3684
  cost = self.safe_string(trade, 'cost')
@@ -2989,40 +3692,68 @@ class Exchange(object):
2989
3692
  multiplyPrice = Precise.string_div('1', price)
2990
3693
  multiplyPrice = Precise.string_mul(multiplyPrice, contractSize)
2991
3694
  cost = Precise.string_mul(multiplyPrice, amount)
2992
- parseFee = self.safe_value(trade, 'fee') is None
2993
- parseFees = self.safe_value(trade, 'fees') is None
2994
- shouldParseFees = parseFee or parseFees
2995
- fees = []
2996
- fee = self.safe_value(trade, 'fee')
2997
- if shouldParseFees:
2998
- reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
2999
- reducedLength = len(reducedFees)
3000
- for i in range(0, reducedLength):
3001
- reducedFees[i]['cost'] = self.safe_number(reducedFees[i], 'cost')
3002
- if 'rate' in reducedFees[i]:
3003
- reducedFees[i]['rate'] = self.safe_number(reducedFees[i], 'rate')
3004
- if not parseFee and (reducedLength == 0):
3005
- # copy fee to avoid modification by reference
3006
- feeCopy = self.deep_extend(fee)
3007
- feeCopy['cost'] = self.safe_number(feeCopy, 'cost')
3008
- if 'rate' in feeCopy:
3009
- feeCopy['rate'] = self.safe_number(feeCopy, 'rate')
3010
- reducedFees.append(feeCopy)
3011
- if parseFees:
3012
- trade['fees'] = reducedFees
3013
- if parseFee and (reducedLength == 1):
3014
- trade['fee'] = reducedFees[0]
3015
- tradeFee = self.safe_value(trade, 'fee')
3016
- if tradeFee is not None:
3017
- tradeFee['cost'] = self.safe_number(tradeFee, 'cost')
3018
- if 'rate' in tradeFee:
3019
- tradeFee['rate'] = self.safe_number(tradeFee, 'rate')
3020
- trade['fee'] = tradeFee
3695
+ resultFee, resultFees = self.parsed_fee_and_fees(trade)
3696
+ trade['fee'] = resultFee
3697
+ trade['fees'] = resultFees
3021
3698
  trade['amount'] = self.parse_number(amount)
3022
3699
  trade['price'] = self.parse_number(price)
3023
3700
  trade['cost'] = self.parse_number(cost)
3024
3701
  return trade
3025
3702
 
3703
+ def create_ccxt_trade_id(self, timestamp=None, side=None, amount=None, price=None, takerOrMaker=None):
3704
+ # self approach is being used by multiple exchanges(mexc, woo, coinsbit, dydx, ...)
3705
+ id = None
3706
+ if timestamp is not None:
3707
+ id = self.number_to_string(timestamp)
3708
+ if side is not None:
3709
+ id += '-' + side
3710
+ if amount is not None:
3711
+ id += '-' + self.number_to_string(amount)
3712
+ if price is not None:
3713
+ id += '-' + self.number_to_string(price)
3714
+ if takerOrMaker is not None:
3715
+ id += '-' + takerOrMaker
3716
+ return id
3717
+
3718
+ def parsed_fee_and_fees(self, container: Any):
3719
+ fee = self.safe_dict(container, 'fee')
3720
+ fees = self.safe_list(container, 'fees')
3721
+ feeDefined = fee is not None
3722
+ feesDefined = fees is not None
3723
+ # parsing only if at least one of them is defined
3724
+ shouldParseFees = (feeDefined or feesDefined)
3725
+ if shouldParseFees:
3726
+ if feeDefined:
3727
+ fee = self.parse_fee_numeric(fee)
3728
+ if not feesDefined:
3729
+ # just set it directly, no further processing needed
3730
+ fees = [fee]
3731
+ # 'fees' were set, so reparse them
3732
+ reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
3733
+ reducedLength = len(reducedFees)
3734
+ for i in range(0, reducedLength):
3735
+ reducedFees[i] = self.parse_fee_numeric(reducedFees[i])
3736
+ fees = reducedFees
3737
+ if reducedLength == 1:
3738
+ fee = reducedFees[0]
3739
+ elif reducedLength == 0:
3740
+ fee = None
3741
+ # in case `fee & fees` are None, set `fees` array
3742
+ if fee is None:
3743
+ fee = {
3744
+ 'cost': None,
3745
+ 'currency': None,
3746
+ }
3747
+ if fees is None:
3748
+ fees = []
3749
+ return [fee, fees]
3750
+
3751
+ def parse_fee_numeric(self, fee: Any):
3752
+ fee['cost'] = self.safe_number(fee, 'cost') # ensure numeric
3753
+ if 'rate' in fee:
3754
+ fee['rate'] = self.safe_number(fee, 'rate')
3755
+ return fee
3756
+
3026
3757
  def find_nearest_ceiling(self, arr: List[float], providedValue: float):
3027
3758
  # i.e. findNearestCeiling([10, 30, 50], 23) returns 30
3028
3759
  length = len(arr)
@@ -3091,12 +3822,13 @@ class Exchange(object):
3091
3822
  reduced = {}
3092
3823
  for i in range(0, len(fees)):
3093
3824
  fee = fees[i]
3094
- feeCurrencyCode = self.safe_string(fee, 'currency')
3825
+ code = self.safe_string(fee, 'currency')
3826
+ feeCurrencyCode = code if (code is not None) else str(i)
3095
3827
  if feeCurrencyCode is not None:
3096
3828
  rate = self.safe_string(fee, 'rate')
3097
- cost = self.safe_value(fee, 'cost')
3098
- if Precise.string_eq(cost, '0'):
3099
- # omit zero cost fees
3829
+ cost = self.safe_string(fee, 'cost')
3830
+ if cost is None:
3831
+ # omit None cost, does not make sense, however, don't omit '0' costs, still make sense
3100
3832
  continue
3101
3833
  if not (feeCurrencyCode in reduced):
3102
3834
  reduced[feeCurrencyCode] = {}
@@ -3105,7 +3837,7 @@ class Exchange(object):
3105
3837
  reduced[feeCurrencyCode][rateKey]['cost'] = Precise.string_add(reduced[feeCurrencyCode][rateKey]['cost'], cost)
3106
3838
  else:
3107
3839
  reduced[feeCurrencyCode][rateKey] = {
3108
- 'currency': feeCurrencyCode,
3840
+ 'currency': code,
3109
3841
  'cost': cost,
3110
3842
  }
3111
3843
  if rate is not None:
@@ -3117,35 +3849,63 @@ class Exchange(object):
3117
3849
  result = self.array_concat(result, reducedFeeValues)
3118
3850
  return result
3119
3851
 
3120
- def safe_ticker(self, ticker: object, market: Market = None):
3852
+ def safe_ticker(self, ticker: dict, market: Market = None):
3121
3853
  open = self.omit_zero(self.safe_string(ticker, 'open'))
3122
- close = self.omit_zero(self.safe_string(ticker, 'close'))
3123
- last = self.omit_zero(self.safe_string(ticker, 'last'))
3854
+ close = self.omit_zero(self.safe_string_2(ticker, 'close', 'last'))
3124
3855
  change = self.omit_zero(self.safe_string(ticker, 'change'))
3125
3856
  percentage = self.omit_zero(self.safe_string(ticker, 'percentage'))
3126
3857
  average = self.omit_zero(self.safe_string(ticker, 'average'))
3127
- vwap = self.omit_zero(self.safe_string(ticker, 'vwap'))
3858
+ vwap = self.safe_string(ticker, 'vwap')
3128
3859
  baseVolume = self.safe_string(ticker, 'baseVolume')
3129
3860
  quoteVolume = self.safe_string(ticker, 'quoteVolume')
3130
3861
  if vwap is None:
3131
3862
  vwap = Precise.string_div(self.omit_zero(quoteVolume), baseVolume)
3132
- if (last is not None) and (close is None):
3133
- close = last
3134
- elif (last is None) and (close is not None):
3135
- last = close
3136
- if (last is not None) and (open is not None):
3137
- if change is None:
3138
- change = Precise.string_sub(last, open)
3139
- if average is None:
3140
- average = Precise.string_div(Precise.string_add(last, open), '2')
3141
- if (percentage is None) and (change is not None) and (open is not None) and Precise.string_gt(open, '0'):
3142
- percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3143
- if (change is None) and (percentage is not None) and (open is not None):
3144
- change = Precise.string_div(Precise.string_mul(percentage, open), '100')
3145
- if (open is None) and (last is not None) and (change is not None):
3146
- open = Precise.string_sub(last, change)
3863
+ # calculate open
3864
+ if change is not None:
3865
+ if close is None and average is not None:
3866
+ close = Precise.string_add(average, Precise.string_div(change, '2'))
3867
+ if open is None and close is not None:
3868
+ open = Precise.string_sub(close, change)
3869
+ elif percentage is not None:
3870
+ if close is None and average is not None:
3871
+ openAddClose = Precise.string_mul(average, '2')
3872
+ # openAddClose = open * (1 + (100 + percentage)/100)
3873
+ denominator = Precise.string_add('2', Precise.string_div(percentage, '100'))
3874
+ calcOpen = open if (open is not None) else Precise.string_div(openAddClose, denominator)
3875
+ close = Precise.string_mul(calcOpen, Precise.string_add('1', Precise.string_div(percentage, '100')))
3876
+ if open is None and close is not None:
3877
+ open = Precise.string_div(close, Precise.string_add('1', Precise.string_div(percentage, '100')))
3878
+ # change
3879
+ if change is None:
3880
+ if close is not None and open is not None:
3881
+ change = Precise.string_sub(close, open)
3882
+ elif close is not None and percentage is not None:
3883
+ change = Precise.string_mul(Precise.string_div(percentage, '100'), Precise.string_div(close, '100'))
3884
+ elif open is not None and percentage is not None:
3885
+ change = Precise.string_mul(open, Precise.string_div(percentage, '100'))
3886
+ # calculate things according to "open"(similar can be done with "close")
3887
+ if open is not None:
3888
+ # percentage(using change)
3889
+ if percentage is None and change is not None:
3890
+ percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3891
+ # close(using change)
3892
+ if close is None and change is not None:
3893
+ close = Precise.string_add(open, change)
3894
+ # close(using average)
3895
+ if close is None and average is not None:
3896
+ close = Precise.string_mul(average, '2')
3897
+ # average
3898
+ if average is None and close is not None:
3899
+ precision = 18
3900
+ if market is not None and self.is_tick_precision():
3901
+ marketPrecision = self.safe_dict(market, 'precision')
3902
+ precisionPrice = self.safe_string(marketPrecision, 'price')
3903
+ if precisionPrice is not None:
3904
+ precision = self.precision_from_string(precisionPrice)
3905
+ average = Precise.string_div(Precise.string_add(open, close), '2', precision)
3147
3906
  # timestamp and symbol operations don't belong in safeTicker
3148
3907
  # they should be done in the derived classes
3908
+ closeParsed = self.parse_number(self.omit_zero(close))
3149
3909
  return self.extend(ticker, {
3150
3910
  'bid': self.parse_number(self.omit_zero(self.safe_string(ticker, 'bid'))),
3151
3911
  'bidVolume': self.safe_number(ticker, 'bidVolume'),
@@ -3154,8 +3914,8 @@ class Exchange(object):
3154
3914
  'high': self.parse_number(self.omit_zero(self.safe_string(ticker, 'high'))),
3155
3915
  'low': self.parse_number(self.omit_zero(self.safe_string(ticker, 'low'))),
3156
3916
  'open': self.parse_number(self.omit_zero(open)),
3157
- 'close': self.parse_number(self.omit_zero(close)),
3158
- 'last': self.parse_number(self.omit_zero(last)),
3917
+ 'close': closeParsed,
3918
+ 'last': closeParsed,
3159
3919
  'change': self.parse_number(change),
3160
3920
  'percentage': self.parse_number(percentage),
3161
3921
  'average': self.parse_number(average),
@@ -3163,15 +3923,17 @@ class Exchange(object):
3163
3923
  'baseVolume': self.parse_number(baseVolume),
3164
3924
  'quoteVolume': self.parse_number(quoteVolume),
3165
3925
  'previousClose': self.safe_number(ticker, 'previousClose'),
3926
+ 'indexPrice': self.safe_number(ticker, 'indexPrice'),
3927
+ 'markPrice': self.safe_number(ticker, 'markPrice'),
3166
3928
  })
3167
3929
 
3168
- def fetch_borrow_rate(self, code: str, amount, params={}):
3930
+ def fetch_borrow_rate(self, code: str, amount: float, params={}):
3169
3931
  raise NotSupported(self.id + ' fetchBorrowRate is deprecated, please use fetchCrossBorrowRate or fetchIsolatedBorrowRate instead')
3170
3932
 
3171
- def repay_cross_margin(self, code: str, amount, params={}):
3933
+ def repay_cross_margin(self, code: str, amount: float, params={}):
3172
3934
  raise NotSupported(self.id + ' repayCrossMargin is not support yet')
3173
3935
 
3174
- def repay_isolated_margin(self, symbol: str, code: str, amount, params={}):
3936
+ def repay_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
3175
3937
  raise NotSupported(self.id + ' repayIsolatedMargin is not support yet')
3176
3938
 
3177
3939
  def borrow_cross_margin(self, code: str, amount: float, params={}):
@@ -3180,10 +3942,10 @@ class Exchange(object):
3180
3942
  def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
3181
3943
  raise NotSupported(self.id + ' borrowIsolatedMargin is not support yet')
3182
3944
 
3183
- def borrow_margin(self, code: str, amount, symbol: Str = None, params={}):
3945
+ def borrow_margin(self, code: str, amount: float, symbol: Str = None, params={}):
3184
3946
  raise NotSupported(self.id + ' borrowMargin is deprecated, please use borrowCrossMargin or borrowIsolatedMargin instead')
3185
3947
 
3186
- def repay_margin(self, code: str, amount, symbol: Str = None, params={}):
3948
+ def repay_margin(self, code: str, amount: float, symbol: Str = None, params={}):
3187
3949
  raise NotSupported(self.id + ' repayMargin is deprecated, please use repayCrossMargin or repayIsolatedMargin instead')
3188
3950
 
3189
3951
  def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
@@ -3230,12 +3992,18 @@ class Exchange(object):
3230
3992
  result[volume] = []
3231
3993
  for i in range(0, len(ohlcvs)):
3232
3994
  ts = ohlcvs[i][0] if ms else self.parse_to_int(ohlcvs[i][0] / 1000)
3233
- result[timestamp].append(ts)
3234
- result[open].append(ohlcvs[i][1])
3235
- result[high].append(ohlcvs[i][2])
3236
- result[low].append(ohlcvs[i][3])
3237
- result[close].append(ohlcvs[i][4])
3238
- result[volume].append(ohlcvs[i][5])
3995
+ resultTimestamp = result[timestamp]
3996
+ resultTimestamp.append(ts)
3997
+ resultOpen = result[open]
3998
+ resultOpen.append(ohlcvs[i][1])
3999
+ resultHigh = result[high]
4000
+ resultHigh.append(ohlcvs[i][2])
4001
+ resultLow = result[low]
4002
+ resultLow.append(ohlcvs[i][3])
4003
+ resultClose = result[close]
4004
+ resultClose.append(ohlcvs[i][4])
4005
+ resultVolume = result[volume]
4006
+ resultVolume.append(ohlcvs[i][5])
3239
4007
  return result
3240
4008
 
3241
4009
  def fetch_web_endpoint(self, method, endpointMethod, returnAsJson, startRegex=None, endRegex=None):
@@ -3249,14 +4017,18 @@ class Exchange(object):
3249
4017
  maxRetries = self.safe_value(options, 'webApiRetries', 10)
3250
4018
  response = None
3251
4019
  retry = 0
4020
+ shouldBreak = False
3252
4021
  while(retry < maxRetries):
3253
4022
  try:
3254
4023
  response = getattr(self, endpointMethod)({})
4024
+ shouldBreak = True
3255
4025
  break
3256
4026
  except Exception as e:
3257
4027
  retry = retry + 1
3258
4028
  if retry == maxRetries:
3259
4029
  raise e
4030
+ if shouldBreak:
4031
+ break # self is needed because of GO
3260
4032
  content = response
3261
4033
  if startRegex is not None:
3262
4034
  splitted_by_start = content.split(startRegex)
@@ -3287,6 +4059,14 @@ class Exchange(object):
3287
4059
  result.append(self.market_id(symbols[i]))
3288
4060
  return result
3289
4061
 
4062
+ def currency_ids(self, codes: Strings = None):
4063
+ if codes is None:
4064
+ return codes
4065
+ result = []
4066
+ for i in range(0, len(codes)):
4067
+ result.append(self.currency_id(codes[i]))
4068
+ return result
4069
+
3290
4070
  def markets_for_symbols(self, symbols: Strings = None):
3291
4071
  if symbols is None:
3292
4072
  return symbols
@@ -3371,7 +4151,7 @@ class Exchange(object):
3371
4151
 
3372
4152
  def network_code_to_id(self, networkCode: str, currencyCode: Str = None):
3373
4153
  """
3374
- * @ignore
4154
+ @ignore
3375
4155
  tries to convert the provided networkCode(which is expected to be an unified network code) to a network id. In order to achieve self, derived class needs to have 'options->networks' defined.
3376
4156
  :param str networkCode: unified network code
3377
4157
  :param str currencyCode: unified currency code, but self argument is not required by default, unless there is an exchange(like huobi) that needs an override of the method to be able to pass currencyCode argument additionally
@@ -3386,7 +4166,7 @@ class Exchange(object):
3386
4166
  if currencyCode is None:
3387
4167
  currencies = list(self.currencies.values())
3388
4168
  for i in range(0, len(currencies)):
3389
- currency = [i]
4169
+ currency = currencies[i]
3390
4170
  networks = self.safe_dict(currency, 'networks')
3391
4171
  network = self.safe_dict(networks, networkCode)
3392
4172
  networkId = self.safe_string(network, 'id')
@@ -3419,7 +4199,7 @@ class Exchange(object):
3419
4199
 
3420
4200
  def network_id_to_code(self, networkId: Str = None, currencyCode: Str = None):
3421
4201
  """
3422
- * @ignore
4202
+ @ignore
3423
4203
  tries to convert the provided exchange-specific networkId to an unified network Code. In order to achieve self, derived class needs to have "options['networksById']" defined.
3424
4204
  :param str networkId: exchange specific network id/title, like: TRON, Trc-20, usdt-erc20, etc
3425
4205
  :param str|None currencyCode: unified currency code, but self argument is not required by default, unless there is an exchange(like huobi) that needs an override of the method to be able to pass currencyCode argument additionally
@@ -3452,7 +4232,7 @@ class Exchange(object):
3452
4232
  defaultNetworkCode = defaultNetworks[currencyCode]
3453
4233
  else:
3454
4234
  # otherwise, try to use the global-scope 'defaultNetwork' value(even if that network is not supported by currency, it doesn't make any problem, self will be just used "at first" if currency supports self network at all)
3455
- defaultNetwork = self.safe_dict(self.options, 'defaultNetwork')
4235
+ defaultNetwork = self.safe_string(self.options, 'defaultNetwork')
3456
4236
  if defaultNetwork is not None:
3457
4237
  defaultNetworkCode = defaultNetwork
3458
4238
  return defaultNetworkCode
@@ -3473,11 +4253,11 @@ class Exchange(object):
3473
4253
  raise NotSupported(self.id + ' - ' + networkCode + ' network did not return any result for ' + currencyCode)
3474
4254
  else:
3475
4255
  # if networkCode was provided by user, we should check it after response, referenced exchange doesn't support network-code during request
3476
- networkId = networkCode if isIndexedByUnifiedNetworkCode else self.network_code_to_id(networkCode, currencyCode)
3477
- if networkId in indexedNetworkEntries:
3478
- chosenNetworkId = networkId
4256
+ networkIdOrCode = networkCode if isIndexedByUnifiedNetworkCode else self.network_code_to_id(networkCode, currencyCode)
4257
+ if networkIdOrCode in indexedNetworkEntries:
4258
+ chosenNetworkId = networkIdOrCode
3479
4259
  else:
3480
- raise NotSupported(self.id + ' - ' + networkId + ' network was not found for ' + currencyCode + ', use one of ' + ', '.join(availableNetworkIds))
4260
+ raise NotSupported(self.id + ' - ' + networkIdOrCode + ' network was not found for ' + currencyCode + ', use one of ' + ', '.join(availableNetworkIds))
3481
4261
  else:
3482
4262
  if responseNetworksLength == 0:
3483
4263
  raise NotSupported(self.id + ' - no networks were returned for ' + currencyCode)
@@ -3485,7 +4265,9 @@ class Exchange(object):
3485
4265
  # if networkCode was not provided by user, then we try to use the default network(if it was defined in "defaultNetworks"), otherwise, we just return the first network entry
3486
4266
  defaultNetworkCode = self.default_network_code(currencyCode)
3487
4267
  defaultNetworkId = defaultNetworkCode if isIndexedByUnifiedNetworkCode else self.network_code_to_id(defaultNetworkCode, currencyCode)
3488
- chosenNetworkId = defaultNetworkId if (defaultNetworkId in indexedNetworkEntries) else availableNetworkIds[0]
4268
+ if defaultNetworkId in indexedNetworkEntries:
4269
+ return defaultNetworkId
4270
+ raise NotSupported(self.id + ' - can not determine the default network, please pass param["network"] one from : ' + ', '.join(availableNetworkIds))
3489
4271
  return chosenNetworkId
3490
4272
 
3491
4273
  def safe_number_2(self, dictionary: object, key1: IndexType, key2: IndexType, d=None):
@@ -3504,15 +4286,15 @@ class Exchange(object):
3504
4286
  'nonce': None,
3505
4287
  }
3506
4288
 
3507
- def parse_ohlcvs(self, ohlcvs: List[object], market: Any = None, timeframe: str = '1m', since: Int = None, limit: Int = None):
4289
+ def parse_ohlcvs(self, ohlcvs: List[object], market: Any = None, timeframe: str = '1m', since: Int = None, limit: Int = None, tail: Bool = False):
3508
4290
  results = []
3509
4291
  for i in range(0, len(ohlcvs)):
3510
4292
  results.append(self.parse_ohlcv(ohlcvs[i], market))
3511
4293
  sorted = self.sort_by(results, 0)
3512
- return self.filter_by_since_limit(sorted, since, limit, 0)
4294
+ return self.filter_by_since_limit(sorted, since, limit, 0, tail)
3513
4295
 
3514
4296
  def parse_leverage_tiers(self, response: Any, symbols: List[str] = None, marketIdKey=None):
3515
- # marketIdKey should only be None when response is a dictionary
4297
+ # marketIdKey should only be None when response is a dictionary.
3516
4298
  symbols = self.market_symbols(symbols)
3517
4299
  tiers = {}
3518
4300
  symbolsLength = 0
@@ -3550,7 +4332,7 @@ class Exchange(object):
3550
4332
  self.options['limitsLoaded'] = self.milliseconds()
3551
4333
  return self.markets
3552
4334
 
3553
- def safe_position(self, position):
4335
+ def safe_position(self, position: dict):
3554
4336
  # simplified version of: /pull/12765/
3555
4337
  unrealizedPnlString = self.safe_string(position, 'unrealisedPnl')
3556
4338
  initialMarginString = self.safe_string(position, 'initialMargin')
@@ -3640,6 +4422,14 @@ class Exchange(object):
3640
4422
  def set_headers(self, headers):
3641
4423
  return headers
3642
4424
 
4425
+ def currency_id(self, code: str):
4426
+ currency = self.safe_dict(self.currencies, code)
4427
+ if currency is None:
4428
+ currency = self.safe_currency(code)
4429
+ if currency is not None:
4430
+ return currency['id']
4431
+ return code
4432
+
3643
4433
  def market_id(self, symbol: str):
3644
4434
  market = self.market(symbol)
3645
4435
  if market is not None:
@@ -3686,6 +4476,23 @@ class Exchange(object):
3686
4476
  params = self.omit(params, [paramName1, paramName2])
3687
4477
  return [value, params]
3688
4478
 
4479
+ def handle_request_network(self, params: dict, request: dict, exchangeSpecificKey: str, currencyCode: Str = None, isRequired: bool = False):
4480
+ """
4481
+ :param dict params: - extra parameters
4482
+ :param dict request: - existing dictionary of request
4483
+ :param str exchangeSpecificKey: - the key for chain id to be set in request
4484
+ :param dict currencyCode: - (optional) existing dictionary of request
4485
+ :param boolean isRequired: - (optional) whether that param is required to be present
4486
+ :returns dict[]: - returns [request, params] where request is the modified request object and params is the modified params object
4487
+ """
4488
+ networkCode = None
4489
+ networkCode, params = self.handle_network_code_and_params(params)
4490
+ if networkCode is not None:
4491
+ request[exchangeSpecificKey] = self.network_code_to_id(networkCode, currencyCode)
4492
+ elif isRequired:
4493
+ raise ArgumentsRequired(self.id + ' - "network" param is required for self request')
4494
+ return [request, params]
4495
+
3689
4496
  def resolve_path(self, path, params):
3690
4497
  return [
3691
4498
  self.implode_params(path, params),
@@ -3693,7 +4500,9 @@ class Exchange(object):
3693
4500
  ]
3694
4501
 
3695
4502
  def get_list_from_object_values(self, objects, key: IndexType):
3696
- newArray = self.to_array(objects)
4503
+ newArray = objects
4504
+ if not isinstance(objects, list):
4505
+ newArray = self.to_array(objects)
3697
4506
  results = []
3698
4507
  for i in range(0, len(newArray)):
3699
4508
  results.append(newArray[i][key])
@@ -3718,23 +4527,48 @@ class Exchange(object):
3718
4527
  objects = self.to_array(objects)
3719
4528
  # return all of them if no values were passed
3720
4529
  if values is None or not values:
3721
- return self.index_by(objects, key) if indexed else objects
4530
+ # return self.index_by(objects, key) if indexed else objects
4531
+ if indexed:
4532
+ return self.index_by(objects, key)
4533
+ else:
4534
+ return objects
3722
4535
  results = []
3723
4536
  for i in range(0, len(objects)):
3724
4537
  if self.in_array(objects[i][key], values):
3725
4538
  results.append(objects[i])
3726
- return self.index_by(results, key) if indexed else results
4539
+ # return self.index_by(results, key) if indexed else results
4540
+ if indexed:
4541
+ return self.index_by(results, key)
4542
+ return results
3727
4543
 
3728
4544
  def fetch2(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None, config={}):
3729
4545
  if self.enableRateLimit:
3730
4546
  cost = self.calculate_rate_limiter_cost(api, method, path, params, config)
3731
4547
  self.throttle(cost)
4548
+ retries = None
4549
+ retries, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailure', 0)
4550
+ retryDelay = None
4551
+ retryDelay, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailureDelay', 0)
3732
4552
  self.lastRestRequestTimestamp = self.milliseconds()
3733
4553
  request = self.sign(path, api, method, params, headers, body)
3734
4554
  self.last_request_headers = request['headers']
3735
4555
  self.last_request_body = request['body']
3736
4556
  self.last_request_url = request['url']
3737
- return self.fetch(request['url'], request['method'], request['headers'], request['body'])
4557
+ for i in range(0, retries + 1):
4558
+ try:
4559
+ return self.fetch(request['url'], request['method'], request['headers'], request['body'])
4560
+ except Exception as e:
4561
+ if isinstance(e, OperationFailed):
4562
+ if i < retries:
4563
+ if self.verbose:
4564
+ self.log('Request failed with the error: ' + str(e) + ', retrying ' + (i + str(1)) + ' of ' + str(retries) + '...')
4565
+ if (retryDelay is not None) and (retryDelay != 0):
4566
+ self.sleep(retryDelay)
4567
+ else:
4568
+ raise e
4569
+ else:
4570
+ raise e
4571
+ return None # self line is never reached, but exists for c# value return requirement
3738
4572
 
3739
4573
  def request(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None, config={}):
3740
4574
  return self.fetch2(path, api, method, params, headers, body, config)
@@ -3815,9 +4649,6 @@ class Exchange(object):
3815
4649
  self.cancel_order_ws(id, symbol)
3816
4650
  return self.create_order_ws(symbol, type, side, amount, price, params)
3817
4651
 
3818
- def fetch_permissions(self, params={}):
3819
- raise NotSupported(self.id + ' fetchPermissions() is not supported yet')
3820
-
3821
4652
  def fetch_position(self, symbol: str, params={}):
3822
4653
  raise NotSupported(self.id + ' fetchPosition() is not supported yet')
3823
4654
 
@@ -3895,7 +4726,7 @@ class Exchange(object):
3895
4726
  'precision': None,
3896
4727
  })
3897
4728
 
3898
- def safe_market(self, marketId: Str, market: Market = None, delimiter: Str = None, marketType: Str = None):
4729
+ def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None):
3899
4730
  result = self.safe_market_structure({
3900
4731
  'symbol': marketId,
3901
4732
  'marketId': marketId,
@@ -3934,7 +4765,7 @@ class Exchange(object):
3934
4765
 
3935
4766
  def check_required_credentials(self, error=True):
3936
4767
  """
3937
- * @ignore
4768
+ @ignore
3938
4769
  :param boolean error: raise an error that a credential is required if True
3939
4770
  :returns boolean: True if all required credentials have been set, otherwise False or an error is thrown is param error=true
3940
4771
  """
@@ -4049,21 +4880,24 @@ class Exchange(object):
4049
4880
 
4050
4881
  def handle_option_and_params_2(self, params: object, methodName1: str, optionName1: str, optionName2: str, defaultValue=None):
4051
4882
  value = None
4052
- value, params = self.handle_option_and_params(params, methodName1, optionName1, defaultValue)
4883
+ value, params = self.handle_option_and_params(params, methodName1, optionName1)
4884
+ if value is not None:
4885
+ # omit optionName2 too from params
4886
+ params = self.omit(params, optionName2)
4887
+ return [value, params]
4053
4888
  # if still None, try optionName2
4054
4889
  value2 = None
4055
- value2, params = self.handle_option_and_params(params, methodName1, optionName2, value)
4890
+ value2, params = self.handle_option_and_params(params, methodName1, optionName2, defaultValue)
4056
4891
  return [value2, params]
4057
4892
 
4058
4893
  def handle_option(self, methodName: str, optionName: str, defaultValue=None):
4059
- # eslint-disable-next-line no-unused-vars
4060
- result, empty = self.handle_option_and_params({}, methodName, optionName, defaultValue)
4061
- return result
4894
+ res = self.handle_option_and_params({}, methodName, optionName, defaultValue)
4895
+ return self.safe_value(res, 0)
4062
4896
 
4063
4897
  def handle_market_type_and_params(self, methodName: str, market: Market = None, params={}, defaultValue=None):
4064
4898
  """
4065
- * @ignore
4066
- * @param methodName the method calling handleMarketTypeAndParams
4899
+ @ignore
4900
+ @param methodName the method calling handleMarketTypeAndParams
4067
4901
  :param Market market:
4068
4902
  :param dict params:
4069
4903
  :param str [params.type]: type assigned by user
@@ -4071,20 +4905,27 @@ class Exchange(object):
4071
4905
  :param str [defaultValue]: assigned programatically in the method calling handleMarketTypeAndParams
4072
4906
  :returns [str, dict]: the market type and params with type and defaultType omitted
4073
4907
  """
4074
- defaultType = self.safe_string_2(self.options, 'defaultType', 'type', 'spot')
4075
- if defaultValue is None: # defaultValue takes precendence over exchange wide defaultType
4076
- defaultValue = defaultType
4908
+ # type from param
4909
+ type = self.safe_string_2(params, 'defaultType', 'type')
4910
+ if type is not None:
4911
+ params = self.omit(params, ['defaultType', 'type'])
4912
+ return [type, params]
4913
+ # type from market
4914
+ if market is not None:
4915
+ return [market['type'], params]
4916
+ # type from default-argument
4917
+ if defaultValue is not None:
4918
+ return [defaultValue, params]
4077
4919
  methodOptions = self.safe_dict(self.options, methodName)
4078
- methodType = defaultValue
4079
- if methodOptions is not None: # user defined methodType takes precedence over defaultValue
4920
+ if methodOptions is not None:
4080
4921
  if isinstance(methodOptions, str):
4081
- methodType = methodOptions
4922
+ return [methodOptions, params]
4082
4923
  else:
4083
- methodType = self.safe_string_2(methodOptions, 'defaultType', 'type', methodType)
4084
- marketType = methodType if (market is None) else market['type']
4085
- type = self.safe_string_2(params, 'defaultType', 'type', marketType)
4086
- params = self.omit(params, ['defaultType', 'type'])
4087
- return [type, params]
4924
+ typeFromMethod = self.safe_string_2(methodOptions, 'defaultType', 'type')
4925
+ if typeFromMethod is not None:
4926
+ return [typeFromMethod, params]
4927
+ defaultType = self.safe_string_2(self.options, 'defaultType', 'type', 'spot')
4928
+ return [defaultType, params]
4088
4929
 
4089
4930
  def handle_sub_type_and_params(self, methodName: str, market=None, params={}, defaultValue=None):
4090
4931
  subType = None
@@ -4109,7 +4950,7 @@ class Exchange(object):
4109
4950
 
4110
4951
  def handle_margin_mode_and_params(self, methodName: str, params={}, defaultValue=None):
4111
4952
  """
4112
- * @ignore
4953
+ @ignore
4113
4954
  :param dict [params]: extra parameters specific to the exchange API endpoint
4114
4955
  :returns Array: the marginMode in lowercase by params["marginMode"], params["defaultMarginMode"] self.options["marginMode"] or self.options["defaultMarginMode"]
4115
4956
  """
@@ -4158,19 +4999,33 @@ class Exchange(object):
4158
4999
  else:
4159
5000
  raise NotSupported(self.id + ' fetchTicker() is not supported yet')
4160
5001
 
5002
+ def fetch_mark_price(self, symbol: str, params={}):
5003
+ if self.has['fetchMarkPrices']:
5004
+ self.load_markets()
5005
+ market = self.market(symbol)
5006
+ symbol = market['symbol']
5007
+ tickers = self.fetch_mark_prices([symbol], params)
5008
+ ticker = self.safe_dict(tickers, symbol)
5009
+ if ticker is None:
5010
+ raise NullResponse(self.id + ' fetchMarkPrices() could not find a ticker for ' + symbol)
5011
+ else:
5012
+ return ticker
5013
+ else:
5014
+ raise NotSupported(self.id + ' fetchMarkPrices() is not supported yet')
5015
+
4161
5016
  def fetch_ticker_ws(self, symbol: str, params={}):
4162
5017
  if self.has['fetchTickersWs']:
4163
5018
  self.load_markets()
4164
5019
  market = self.market(symbol)
4165
5020
  symbol = market['symbol']
4166
- tickers = self.fetch_ticker_ws(symbol, params)
5021
+ tickers = self.fetch_tickers_ws([symbol], params)
4167
5022
  ticker = self.safe_dict(tickers, symbol)
4168
5023
  if ticker is None:
4169
- raise NullResponse(self.id + ' fetchTickers() could not find a ticker for ' + symbol)
5024
+ raise NullResponse(self.id + ' fetchTickerWs() could not find a ticker for ' + symbol)
4170
5025
  else:
4171
5026
  return ticker
4172
5027
  else:
4173
- raise NotSupported(self.id + ' fetchTicker() is not supported yet')
5028
+ raise NotSupported(self.id + ' fetchTickerWs() is not supported yet')
4174
5029
 
4175
5030
  def watch_ticker(self, symbol: str, params={}):
4176
5031
  raise NotSupported(self.id + ' watchTicker() is not supported yet')
@@ -4178,6 +5033,9 @@ class Exchange(object):
4178
5033
  def fetch_tickers(self, symbols: Strings = None, params={}):
4179
5034
  raise NotSupported(self.id + ' fetchTickers() is not supported yet')
4180
5035
 
5036
+ def fetch_mark_prices(self, symbols: Strings = None, params={}):
5037
+ raise NotSupported(self.id + ' fetchMarkPrices() is not supported yet')
5038
+
4181
5039
  def fetch_tickers_ws(self, symbols: Strings = None, params={}):
4182
5040
  raise NotSupported(self.id + ' fetchTickers() is not supported yet')
4183
5041
 
@@ -4190,6 +5048,9 @@ class Exchange(object):
4190
5048
  def watch_tickers(self, symbols: Strings = None, params={}):
4191
5049
  raise NotSupported(self.id + ' watchTickers() is not supported yet')
4192
5050
 
5051
+ def un_watch_tickers(self, symbols: Strings = None, params={}):
5052
+ raise NotSupported(self.id + ' unWatchTickers() is not supported yet')
5053
+
4193
5054
  def fetch_order(self, id: str, symbol: Str = None, params={}):
4194
5055
  raise NotSupported(self.id + ' fetchOrder() is not supported yet')
4195
5056
 
@@ -4208,7 +5069,19 @@ class Exchange(object):
4208
5069
  def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4209
5070
  raise NotSupported(self.id + ' createOrder() is not supported yet')
4210
5071
 
4211
- def create_trailing_amount_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount=None, trailingTriggerPrice=None, params={}):
5072
+ def create_convert_trade(self, id: str, fromCode: str, toCode: str, amount: Num = None, params={}):
5073
+ raise NotSupported(self.id + ' createConvertTrade() is not supported yet')
5074
+
5075
+ def fetch_convert_trade(self, id: str, code: Str = None, params={}):
5076
+ raise NotSupported(self.id + ' fetchConvertTrade() is not supported yet')
5077
+
5078
+ def fetch_convert_trade_history(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5079
+ raise NotSupported(self.id + ' fetchConvertTradeHistory() is not supported yet')
5080
+
5081
+ def fetch_position_mode(self, symbol: Str = None, params={}):
5082
+ raise NotSupported(self.id + ' fetchPositionMode() is not supported yet')
5083
+
5084
+ def create_trailing_amount_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount: Num = None, trailingTriggerPrice: Num = None, params={}):
4212
5085
  """
4213
5086
  create a trailing order by providing the symbol, type, side, amount, price and trailingAmount
4214
5087
  :param str symbol: unified symbol of the market to create an order in
@@ -4230,7 +5103,7 @@ class Exchange(object):
4230
5103
  return self.create_order(symbol, type, side, amount, price, params)
4231
5104
  raise NotSupported(self.id + ' createTrailingAmountOrder() is not supported yet')
4232
5105
 
4233
- def create_trailing_amount_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount=None, trailingTriggerPrice=None, params={}):
5106
+ def create_trailing_amount_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount: Num = None, trailingTriggerPrice: Num = None, params={}):
4234
5107
  """
4235
5108
  create a trailing order by providing the symbol, type, side, amount, price and trailingAmount
4236
5109
  :param str symbol: unified symbol of the market to create an order in
@@ -4252,7 +5125,7 @@ class Exchange(object):
4252
5125
  return self.create_order_ws(symbol, type, side, amount, price, params)
4253
5126
  raise NotSupported(self.id + ' createTrailingAmountOrderWs() is not supported yet')
4254
5127
 
4255
- def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
5128
+ def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent: Num = None, trailingTriggerPrice: Num = None, params={}):
4256
5129
  """
4257
5130
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
4258
5131
  :param str symbol: unified symbol of the market to create an order in
@@ -4274,7 +5147,7 @@ class Exchange(object):
4274
5147
  return self.create_order(symbol, type, side, amount, price, params)
4275
5148
  raise NotSupported(self.id + ' createTrailingPercentOrder() is not supported yet')
4276
5149
 
4277
- def create_trailing_percent_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
5150
+ def create_trailing_percent_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent: Num = None, trailingTriggerPrice: Num = None, params={}):
4278
5151
  """
4279
5152
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
4280
5153
  :param str symbol: unified symbol of the market to create an order in
@@ -4553,6 +5426,9 @@ class Exchange(object):
4553
5426
  def create_orders(self, orders: List[OrderRequest], params={}):
4554
5427
  raise NotSupported(self.id + ' createOrders() is not supported yet')
4555
5428
 
5429
+ def edit_orders(self, orders: List[OrderRequest], params={}):
5430
+ raise NotSupported(self.id + ' editOrders() is not supported yet')
5431
+
4556
5432
  def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4557
5433
  raise NotSupported(self.id + ' createOrderWs() is not supported yet')
4558
5434
 
@@ -4639,6 +5515,9 @@ class Exchange(object):
4639
5515
  def fetch_greeks(self, symbol: str, params={}):
4640
5516
  raise NotSupported(self.id + ' fetchGreeks() is not supported yet')
4641
5517
 
5518
+ def fetch_all_greeks(self, symbols: Strings = None, params={}):
5519
+ raise NotSupported(self.id + ' fetchAllGreeks() is not supported yet')
5520
+
4642
5521
  def fetch_option_chain(self, code: str, params={}):
4643
5522
  raise NotSupported(self.id + ' fetchOptionChain() is not supported yet')
4644
5523
 
@@ -4659,10 +5538,10 @@ class Exchange(object):
4659
5538
  """
4660
5539
  raise NotSupported(self.id + ' fetchDepositsWithdrawals() is not supported yet')
4661
5540
 
4662
- def fetch_deposits(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
5541
+ def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
4663
5542
  raise NotSupported(self.id + ' fetchDeposits() is not supported yet')
4664
5543
 
4665
- def fetch_withdrawals(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
5544
+ def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
4666
5545
  raise NotSupported(self.id + ' fetchWithdrawals() is not supported yet')
4667
5546
 
4668
5547
  def fetch_deposits_ws(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
@@ -4752,6 +5631,24 @@ class Exchange(object):
4752
5631
  def create_expired_option_market(self, symbol: str):
4753
5632
  raise NotSupported(self.id + ' createExpiredOptionMarket() is not supported yet')
4754
5633
 
5634
+ def is_leveraged_currency(self, currencyCode, checkBaseCoin: Bool = False, existingCurrencies: dict = None):
5635
+ leverageSuffixes = [
5636
+ '2L', '2S', '3L', '3S', '4L', '4S', '5L', '5S', # Leveraged Tokens(LT)
5637
+ 'UP', 'DOWN', # exchange-specific(e.g. BLVT)
5638
+ 'BULL', 'BEAR', # similar
5639
+ ]
5640
+ for i in range(0, len(leverageSuffixes)):
5641
+ leverageSuffix = leverageSuffixes[i]
5642
+ if currencyCode.endswith(leverageSuffix):
5643
+ if not checkBaseCoin:
5644
+ return True
5645
+ else:
5646
+ # check if base currency is inside dict
5647
+ baseCurrencyCode = currencyCode.replace(leverageSuffix, '')
5648
+ if baseCurrencyCode in existingCurrencies:
5649
+ return True
5650
+ return False
5651
+
4755
5652
  def handle_withdraw_tag_and_params(self, tag, params):
4756
5653
  if (tag is not None) and (isinstance(tag, dict)):
4757
5654
  params = self.extend(tag, params)
@@ -4799,10 +5696,14 @@ class Exchange(object):
4799
5696
  return self.create_order_ws(symbol, 'market', 'sell', amount, None, params)
4800
5697
 
4801
5698
  def cost_to_precision(self, symbol: str, cost):
5699
+ if cost is None:
5700
+ return None
4802
5701
  market = self.market(symbol)
4803
5702
  return self.decimal_to_precision(cost, TRUNCATE, market['precision']['price'], self.precisionMode, self.paddingMode)
4804
5703
 
4805
5704
  def price_to_precision(self, symbol: str, price):
5705
+ if price is None:
5706
+ return None
4806
5707
  market = self.market(symbol)
4807
5708
  result = self.decimal_to_precision(price, ROUND, market['precision']['price'], self.precisionMode, self.paddingMode)
4808
5709
  if result == '0':
@@ -4810,6 +5711,8 @@ class Exchange(object):
4810
5711
  return result
4811
5712
 
4812
5713
  def amount_to_precision(self, symbol: str, amount):
5714
+ if amount is None:
5715
+ return None
4813
5716
  market = self.market(symbol)
4814
5717
  result = self.decimal_to_precision(amount, TRUNCATE, market['precision']['amount'], self.precisionMode, self.paddingMode)
4815
5718
  if result == '0':
@@ -4817,6 +5720,8 @@ class Exchange(object):
4817
5720
  return result
4818
5721
 
4819
5722
  def fee_to_precision(self, symbol: str, fee):
5723
+ if fee is None:
5724
+ return None
4820
5725
  market = self.market(symbol)
4821
5726
  return self.decimal_to_precision(fee, ROUND, market['precision']['price'], self.precisionMode, self.paddingMode)
4822
5727
 
@@ -4830,7 +5735,8 @@ class Exchange(object):
4830
5735
  if precision is None:
4831
5736
  return self.force_string(fee)
4832
5737
  else:
4833
- return self.decimal_to_precision(fee, ROUND, precision, self.precisionMode, self.paddingMode)
5738
+ roundingMode = self.safe_integer(self.options, 'currencyToPrecisionRoundingMode', ROUND)
5739
+ return self.decimal_to_precision(fee, roundingMode, precision, self.precisionMode, self.paddingMode)
4834
5740
 
4835
5741
  def force_string(self, value):
4836
5742
  if not isinstance(value, str):
@@ -4856,7 +5762,7 @@ class Exchange(object):
4856
5762
 
4857
5763
  def parse_precision(self, precision: str):
4858
5764
  """
4859
- * @ignore
5765
+ @ignore
4860
5766
  :param str precision: The number of digits to the right of the decimal
4861
5767
  :returns str: a string number equal to 1e-precision
4862
5768
  """
@@ -4865,14 +5771,20 @@ class Exchange(object):
4865
5771
  precisionNumber = int(precision)
4866
5772
  if precisionNumber == 0:
4867
5773
  return '1'
4868
- parsedPrecision = '0.'
4869
- for i in range(0, precisionNumber - 1):
4870
- parsedPrecision = parsedPrecision + '0'
4871
- return parsedPrecision + '1'
5774
+ if precisionNumber > 0:
5775
+ parsedPrecision = '0.'
5776
+ for i in range(0, precisionNumber - 1):
5777
+ parsedPrecision = parsedPrecision + '0'
5778
+ return parsedPrecision + '1'
5779
+ else:
5780
+ parsedPrecision = '1'
5781
+ for i in range(0, precisionNumber * -1 - 1):
5782
+ parsedPrecision = parsedPrecision + '0'
5783
+ return parsedPrecision + '0'
4872
5784
 
4873
5785
  def integer_precision_to_amount(self, precision: Str):
4874
5786
  """
4875
- * @ignore
5787
+ @ignore
4876
5788
  handles positive & negative numbers too. parsePrecision() does not handle negative numbers, but self method handles
4877
5789
  :param str precision: The number of digits to the right of the decimal
4878
5790
  :returns str: a string number equal to 1e-precision
@@ -4910,66 +5822,66 @@ class Exchange(object):
4910
5822
 
4911
5823
  def create_post_only_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4912
5824
  if not self.has['createPostOnlyOrder']:
4913
- raise NotSupported(self.id + 'createPostOnlyOrder() is not supported yet')
5825
+ raise NotSupported(self.id + ' createPostOnlyOrder() is not supported yet')
4914
5826
  query = self.extend(params, {'postOnly': True})
4915
5827
  return self.create_order(symbol, type, side, amount, price, query)
4916
5828
 
4917
5829
  def create_post_only_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4918
5830
  if not self.has['createPostOnlyOrderWs']:
4919
- raise NotSupported(self.id + 'createPostOnlyOrderWs() is not supported yet')
5831
+ raise NotSupported(self.id + ' createPostOnlyOrderWs() is not supported yet')
4920
5832
  query = self.extend(params, {'postOnly': True})
4921
5833
  return self.create_order_ws(symbol, type, side, amount, price, query)
4922
5834
 
4923
5835
  def create_reduce_only_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4924
5836
  if not self.has['createReduceOnlyOrder']:
4925
- raise NotSupported(self.id + 'createReduceOnlyOrder() is not supported yet')
5837
+ raise NotSupported(self.id + ' createReduceOnlyOrder() is not supported yet')
4926
5838
  query = self.extend(params, {'reduceOnly': True})
4927
5839
  return self.create_order(symbol, type, side, amount, price, query)
4928
5840
 
4929
5841
  def create_reduce_only_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4930
5842
  if not self.has['createReduceOnlyOrderWs']:
4931
- raise NotSupported(self.id + 'createReduceOnlyOrderWs() is not supported yet')
5843
+ raise NotSupported(self.id + ' createReduceOnlyOrderWs() is not supported yet')
4932
5844
  query = self.extend(params, {'reduceOnly': True})
4933
5845
  return self.create_order_ws(symbol, type, side, amount, price, query)
4934
5846
 
4935
- def create_stop_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopPrice: Num = None, params={}):
5847
+ def create_stop_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4936
5848
  if not self.has['createStopOrder']:
4937
5849
  raise NotSupported(self.id + ' createStopOrder() is not supported yet')
4938
- if stopPrice is None:
5850
+ if triggerPrice is None:
4939
5851
  raise ArgumentsRequired(self.id + ' create_stop_order() requires a stopPrice argument')
4940
- query = self.extend(params, {'stopPrice': stopPrice})
5852
+ query = self.extend(params, {'stopPrice': triggerPrice})
4941
5853
  return self.create_order(symbol, type, side, amount, price, query)
4942
5854
 
4943
- def create_stop_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopPrice: Num = None, params={}):
5855
+ def create_stop_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4944
5856
  if not self.has['createStopOrderWs']:
4945
5857
  raise NotSupported(self.id + ' createStopOrderWs() is not supported yet')
4946
- if stopPrice is None:
5858
+ if triggerPrice is None:
4947
5859
  raise ArgumentsRequired(self.id + ' createStopOrderWs() requires a stopPrice argument')
4948
- query = self.extend(params, {'stopPrice': stopPrice})
5860
+ query = self.extend(params, {'stopPrice': triggerPrice})
4949
5861
  return self.create_order_ws(symbol, type, side, amount, price, query)
4950
5862
 
4951
- def create_stop_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, stopPrice: float, params={}):
5863
+ def create_stop_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, triggerPrice: float, params={}):
4952
5864
  if not self.has['createStopLimitOrder']:
4953
5865
  raise NotSupported(self.id + ' createStopLimitOrder() is not supported yet')
4954
- query = self.extend(params, {'stopPrice': stopPrice})
5866
+ query = self.extend(params, {'stopPrice': triggerPrice})
4955
5867
  return self.create_order(symbol, 'limit', side, amount, price, query)
4956
5868
 
4957
- def create_stop_limit_order_ws(self, symbol: str, side: OrderSide, amount: float, price: float, stopPrice: float, params={}):
5869
+ def create_stop_limit_order_ws(self, symbol: str, side: OrderSide, amount: float, price: float, triggerPrice: float, params={}):
4958
5870
  if not self.has['createStopLimitOrderWs']:
4959
5871
  raise NotSupported(self.id + ' createStopLimitOrderWs() is not supported yet')
4960
- query = self.extend(params, {'stopPrice': stopPrice})
5872
+ query = self.extend(params, {'stopPrice': triggerPrice})
4961
5873
  return self.create_order_ws(symbol, 'limit', side, amount, price, query)
4962
5874
 
4963
- def create_stop_market_order(self, symbol: str, side: OrderSide, amount: float, stopPrice: float, params={}):
5875
+ def create_stop_market_order(self, symbol: str, side: OrderSide, amount: float, triggerPrice: float, params={}):
4964
5876
  if not self.has['createStopMarketOrder']:
4965
5877
  raise NotSupported(self.id + ' createStopMarketOrder() is not supported yet')
4966
- query = self.extend(params, {'stopPrice': stopPrice})
5878
+ query = self.extend(params, {'stopPrice': triggerPrice})
4967
5879
  return self.create_order(symbol, 'market', side, amount, None, query)
4968
5880
 
4969
- def create_stop_market_order_ws(self, symbol: str, side: OrderSide, amount: float, stopPrice: float, params={}):
5881
+ def create_stop_market_order_ws(self, symbol: str, side: OrderSide, amount: float, triggerPrice: float, params={}):
4970
5882
  if not self.has['createStopMarketOrderWs']:
4971
5883
  raise NotSupported(self.id + ' createStopMarketOrderWs() is not supported yet')
4972
- query = self.extend(params, {'stopPrice': stopPrice})
5884
+ query = self.extend(params, {'stopPrice': triggerPrice})
4973
5885
  return self.create_order_ws(symbol, 'market', side, amount, None, query)
4974
5886
 
4975
5887
  def safe_currency_code(self, currencyId: Str, currency: Currency = None):
@@ -5047,14 +5959,16 @@ class Exchange(object):
5047
5959
  results = []
5048
5960
  if isinstance(tickers, list):
5049
5961
  for i in range(0, len(tickers)):
5050
- ticker = self.extend(self.parse_ticker(tickers[i]), params)
5962
+ parsedTicker = self.parse_ticker(tickers[i])
5963
+ ticker = self.extend(parsedTicker, params)
5051
5964
  results.append(ticker)
5052
5965
  else:
5053
5966
  marketIds = list(tickers.keys())
5054
5967
  for i in range(0, len(marketIds)):
5055
5968
  marketId = marketIds[i]
5056
5969
  market = self.safe_market(marketId)
5057
- ticker = self.extend(self.parse_ticker(tickers[marketId], market), params)
5970
+ parsed = self.parse_ticker(tickers[marketId], market)
5971
+ ticker = self.extend(parsed, params)
5058
5972
  results.append(ticker)
5059
5973
  symbols = self.market_symbols(symbols)
5060
5974
  return self.filter_by_array(results, 'symbol', symbols)
@@ -5067,7 +5981,7 @@ class Exchange(object):
5067
5981
  if codes is not None:
5068
5982
  result = self.filter_by_array(result, 'currency', codes, False)
5069
5983
  if indexed:
5070
- return self.index_by(result, 'currency')
5984
+ result = self.filter_by_array(result, 'currency', None, indexed)
5071
5985
  return result
5072
5986
 
5073
5987
  def parse_borrow_interests(self, response, market: Market = None):
@@ -5077,6 +5991,18 @@ class Exchange(object):
5077
5991
  interests.append(self.parse_borrow_interest(row, market))
5078
5992
  return interests
5079
5993
 
5994
+ def parse_borrow_rate(self, info, currency: Currency = None):
5995
+ raise NotSupported(self.id + ' parseBorrowRate() is not supported yet')
5996
+
5997
+ def parse_borrow_rate_history(self, response, code: Str, since: Int, limit: Int):
5998
+ result = []
5999
+ for i in range(0, len(response)):
6000
+ item = response[i]
6001
+ borrowRate = self.parse_borrow_rate(item)
6002
+ result.append(borrowRate)
6003
+ sorted = self.sort_by(result, 'timestamp')
6004
+ return self.filter_by_currency_since_limit(sorted, code, since, limit)
6005
+
5080
6006
  def parse_isolated_borrow_rates(self, info: Any):
5081
6007
  result = {}
5082
6008
  for i in range(0, len(info)):
@@ -5102,12 +6028,46 @@ class Exchange(object):
5102
6028
  def parse_funding_rate(self, contract: str, market: Market = None):
5103
6029
  raise NotSupported(self.id + ' parseFundingRate() is not supported yet')
5104
6030
 
5105
- def parse_funding_rates(self, response, market: Market = None):
5106
- result = {}
6031
+ def parse_funding_rates(self, response, symbols: Strings = None):
6032
+ fundingRates = {}
5107
6033
  for i in range(0, len(response)):
5108
- parsed = self.parse_funding_rate(response[i], market)
5109
- result[parsed['symbol']] = parsed
5110
- return result
6034
+ entry = response[i]
6035
+ parsed = self.parse_funding_rate(entry)
6036
+ fundingRates[parsed['symbol']] = parsed
6037
+ return self.filter_by_array(fundingRates, 'symbol', symbols)
6038
+
6039
+ def parse_long_short_ratio(self, info: dict, market: Market = None):
6040
+ raise NotSupported(self.id + ' parseLongShortRatio() is not supported yet')
6041
+
6042
+ def parse_long_short_ratio_history(self, response, market=None, since: Int = None, limit: Int = None):
6043
+ rates = []
6044
+ for i in range(0, len(response)):
6045
+ entry = response[i]
6046
+ rates.append(self.parse_long_short_ratio(entry, market))
6047
+ sorted = self.sort_by(rates, 'timestamp')
6048
+ symbol = None if (market is None) else market['symbol']
6049
+ return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
6050
+
6051
+ def handle_trigger_direction_and_params(self, params, exchangeSpecificKey: Str = None, allowEmpty: Bool = False):
6052
+ """
6053
+ @ignore
6054
+ :returns [str, dict]: the trigger-direction value and omited params
6055
+ """
6056
+ triggerDirection = self.safe_string(params, 'triggerDirection')
6057
+ exchangeSpecificDefined = (exchangeSpecificKey is not None) and (exchangeSpecificKey in params)
6058
+ if triggerDirection is not None:
6059
+ params = self.omit(params, 'triggerDirection')
6060
+ # raise exception if:
6061
+ # A) if provided value is not unified(support old "up/down" strings too)
6062
+ # B) if exchange specific "trigger direction key"(eg. "stopPriceSide") was not provided
6063
+ if not self.in_array(triggerDirection, ['ascending', 'descending', 'up', 'down', 'above', 'below']) and not exchangeSpecificDefined and not allowEmpty:
6064
+ raise ArgumentsRequired(self.id + ' createOrder() : trigger orders require params["triggerDirection"] to be either "ascending" or "descending"')
6065
+ # if old format was provided, overwrite to new
6066
+ if triggerDirection == 'up' or triggerDirection == 'above':
6067
+ triggerDirection = 'ascending'
6068
+ elif triggerDirection == 'down' or triggerDirection == 'below':
6069
+ triggerDirection = 'descending'
6070
+ return [triggerDirection, params]
5111
6071
 
5112
6072
  def handle_trigger_and_params(self, params):
5113
6073
  isTrigger = self.safe_bool_2(params, 'trigger', 'stop')
@@ -5121,7 +6081,7 @@ class Exchange(object):
5121
6081
 
5122
6082
  def is_post_only(self, isMarketOrder: bool, exchangeSpecificParam, params={}):
5123
6083
  """
5124
- * @ignore
6084
+ @ignore
5125
6085
  :param str type: Order type
5126
6086
  :param boolean exchangeSpecificParam: exchange specific postOnly
5127
6087
  :param dict [params]: exchange specific params
@@ -5146,7 +6106,7 @@ class Exchange(object):
5146
6106
 
5147
6107
  def handle_post_only(self, isMarketOrder: bool, exchangeSpecificPostOnlyOption: bool, params: Any = {}):
5148
6108
  """
5149
- * @ignore
6109
+ @ignore
5150
6110
  :param str type: Order type
5151
6111
  :param boolean exchangeSpecificBoolean: exchange specific postOnly
5152
6112
  :param dict [params]: exchange specific params
@@ -5191,7 +6151,15 @@ class Exchange(object):
5191
6151
  def parse_open_interest(self, interest, market: Market = None):
5192
6152
  raise NotSupported(self.id + ' parseOpenInterest() is not supported yet')
5193
6153
 
5194
- def parse_open_interests(self, response, market=None, since: Int = None, limit: Int = None):
6154
+ def parse_open_interests(self, response, symbols: Strings = None):
6155
+ result = {}
6156
+ for i in range(0, len(response)):
6157
+ entry = response[i]
6158
+ parsed = self.parse_open_interest(entry)
6159
+ result[parsed['symbol']] = parsed
6160
+ return self.filter_by_array(result, 'symbol', symbols)
6161
+
6162
+ def parse_open_interests_history(self, response, market=None, since: Int = None, limit: Int = None):
5195
6163
  interests = []
5196
6164
  for i in range(0, len(response)):
5197
6165
  entry = response[i]
@@ -5217,7 +6185,23 @@ class Exchange(object):
5217
6185
  else:
5218
6186
  raise NotSupported(self.id + ' fetchFundingRate() is not supported yet')
5219
6187
 
5220
- def fetch_mark_ohlcv(self, symbol, timeframe='1m', since: Int = None, limit: Int = None, params={}):
6188
+ def fetch_funding_interval(self, symbol: str, params={}):
6189
+ if self.has['fetchFundingIntervals']:
6190
+ self.load_markets()
6191
+ market = self.market(symbol)
6192
+ symbol = market['symbol']
6193
+ if not market['contract']:
6194
+ raise BadSymbol(self.id + ' fetchFundingInterval() supports contract markets only')
6195
+ rates = self.fetch_funding_intervals([symbol], params)
6196
+ rate = self.safe_value(rates, symbol)
6197
+ if rate is None:
6198
+ raise NullResponse(self.id + ' fetchFundingInterval() returned no data for ' + symbol)
6199
+ else:
6200
+ return rate
6201
+ else:
6202
+ raise NotSupported(self.id + ' fetchFundingInterval() is not supported yet')
6203
+
6204
+ def fetch_mark_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
5221
6205
  """
5222
6206
  fetches historical mark price candlestick data containing the open, high, low, and close price of a market
5223
6207
  :param str symbol: unified symbol of the market to fetch OHLCV data for
@@ -5243,7 +6227,7 @@ class Exchange(object):
5243
6227
  :param int [since]: timestamp in ms of the earliest candle to fetch
5244
6228
  :param int [limit]: the maximum amount of candles to fetch
5245
6229
  :param dict [params]: extra parameters specific to the exchange API endpoint
5246
- * @returns {} A list of candles ordered, open, high, low, close, None
6230
+ @returns {} A list of candles ordered, open, high, low, close, None
5247
6231
  """
5248
6232
  if self.has['fetchIndexOHLCV']:
5249
6233
  request: dict = {
@@ -5273,8 +6257,8 @@ class Exchange(object):
5273
6257
 
5274
6258
  def handle_time_in_force(self, params={}):
5275
6259
  """
5276
- * @ignore
5277
- * Must add timeInForce to self.options to use self method
6260
+ @ignore
6261
+ Must add timeInForce to self.options to use self method
5278
6262
  :returns str: returns the exchange specific value for timeInForce
5279
6263
  """
5280
6264
  timeInForce = self.safe_string_upper(params, 'timeInForce') # supported values GTC, IOC, PO
@@ -5287,8 +6271,8 @@ class Exchange(object):
5287
6271
 
5288
6272
  def convert_type_to_account(self, account):
5289
6273
  """
5290
- * @ignore
5291
- * Must add accountsByType to self.options to use self method
6274
+ @ignore
6275
+ Must add accountsByType to self.options to use self method
5292
6276
  :param str account: key for account name in self.options['accountsByType']
5293
6277
  :returns: the exchange specific account name or the isolated margin id for transfers
5294
6278
  """
@@ -5304,7 +6288,7 @@ class Exchange(object):
5304
6288
 
5305
6289
  def check_required_argument(self, methodName: str, argument, argumentName, options=[]):
5306
6290
  """
5307
- * @ignore
6291
+ @ignore
5308
6292
  :param str methodName: the name of the method that the argument is being checked for
5309
6293
  :param str argument: the argument's actual value provided
5310
6294
  :param str argumentName: the name of the argument being checked(for logging purposes)
@@ -5321,7 +6305,7 @@ class Exchange(object):
5321
6305
 
5322
6306
  def check_required_margin_argument(self, methodName: str, symbol: Str, marginMode: str):
5323
6307
  """
5324
- * @ignore
6308
+ @ignore
5325
6309
  :param str symbol: unified symbol of the market
5326
6310
  :param str methodName: name of the method that requires a symbol
5327
6311
  :param str marginMode: is either 'isolated' or 'cross'
@@ -5333,7 +6317,7 @@ class Exchange(object):
5333
6317
 
5334
6318
  def parse_deposit_withdraw_fees(self, response, codes: Strings = None, currencyIdKey=None):
5335
6319
  """
5336
- * @ignore
6320
+ @ignore
5337
6321
  :param object[]|dict response: unparsed response from the exchange
5338
6322
  :param str[]|None codes: the unified currency codes to fetch transactions fees for, returns all currencies when None
5339
6323
  :param str currencyIdKey: *should only be None when response is a dictionary* the object key that corresponds to the currency id
@@ -5373,7 +6357,7 @@ class Exchange(object):
5373
6357
 
5374
6358
  def assign_default_deposit_withdraw_fees(self, fee, currency=None):
5375
6359
  """
5376
- * @ignore
6360
+ @ignore
5377
6361
  Takes a depositWithdrawFee structure and assigns the default values for withdraw and deposit
5378
6362
  :param dict fee: A deposit withdraw fee structure
5379
6363
  :param dict currency: A currency structure, the response from self.currency()
@@ -5398,7 +6382,7 @@ class Exchange(object):
5398
6382
 
5399
6383
  def parse_incomes(self, incomes, market=None, since: Int = None, limit: Int = None):
5400
6384
  """
5401
- * @ignore
6385
+ @ignore
5402
6386
  parses funding fee info from exchange response
5403
6387
  :param dict[] incomes: each item describes once instance of currency being received or paid
5404
6388
  :param dict market: ccxt market
@@ -5412,7 +6396,8 @@ class Exchange(object):
5412
6396
  parsed = self.parse_income(entry, market)
5413
6397
  result.append(parsed)
5414
6398
  sorted = self.sort_by(result, 'timestamp')
5415
- return self.filter_by_since_limit(sorted, since, limit)
6399
+ symbol = self.safe_string(market, 'symbol')
6400
+ return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
5416
6401
 
5417
6402
  def get_market_from_symbols(self, symbols: Strings = None):
5418
6403
  if symbols is None:
@@ -5429,7 +6414,7 @@ class Exchange(object):
5429
6414
 
5430
6415
  def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5431
6416
  """
5432
- * @deprecated
6417
+ @deprecated
5433
6418
  *DEPRECATED* use fetchDepositsWithdrawals instead
5434
6419
  :param str code: unified currency code for the currency of the deposit/withdrawals, default is None
5435
6420
  :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
@@ -5444,14 +6429,14 @@ class Exchange(object):
5444
6429
 
5445
6430
  def filter_by_array_positions(self, objects, key: IndexType, values=None, indexed=True):
5446
6431
  """
5447
- * @ignore
6432
+ @ignore
5448
6433
  Typed wrapper for filterByArray that returns a list of positions
5449
6434
  """
5450
6435
  return self.filter_by_array(objects, key, values, indexed)
5451
6436
 
5452
6437
  def filter_by_array_tickers(self, objects, key: IndexType, values=None, indexed=True):
5453
6438
  """
5454
- * @ignore
6439
+ @ignore
5455
6440
  Typed wrapper for filterByArray that returns a dictionary of tickers
5456
6441
  """
5457
6442
  return self.filter_by_array(objects, key, values, indexed)
@@ -5471,7 +6456,7 @@ class Exchange(object):
5471
6456
  maxEntriesPerRequest = 1000 # default to 1000
5472
6457
  return [maxEntriesPerRequest, params]
5473
6458
 
5474
- def fetch_paginated_call_dynamic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}, maxEntriesPerRequest: Int = None):
6459
+ def fetch_paginated_call_dynamic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}, maxEntriesPerRequest: Int = None, removeRepeated=True):
5475
6460
  maxCalls = None
5476
6461
  maxCalls, params = self.handle_option_and_params(params, method, 'paginationCalls', 10)
5477
6462
  maxRetries = None
@@ -5479,10 +6464,12 @@ class Exchange(object):
5479
6464
  paginationDirection = None
5480
6465
  paginationDirection, params = self.handle_option_and_params(params, method, 'paginationDirection', 'backward')
5481
6466
  paginationTimestamp = None
6467
+ removeRepeatedOption = removeRepeated
6468
+ removeRepeatedOption, params = self.handle_option_and_params(params, method, 'removeRepeated', removeRepeated)
5482
6469
  calls = 0
5483
6470
  result = []
5484
6471
  errors = 0
5485
- until = self.safe_integer_2(params, 'untill', 'till') # do not omit it from params here
6472
+ until = self.safe_integer_n(params, ['until', 'untill', 'till']) # do not omit it from params here
5486
6473
  maxEntriesPerRequest, params = self.handle_max_entries_per_request_and_params(method, maxEntriesPerRequest, params)
5487
6474
  if (paginationDirection == 'forward'):
5488
6475
  if since is None:
@@ -5525,14 +6512,16 @@ class Exchange(object):
5525
6512
  errors = 0
5526
6513
  result = self.array_concat(result, response)
5527
6514
  last = self.safe_value(response, responseLength - 1)
5528
- paginationTimestamp = self.safe_integer(last, 'timestamp') - 1
6515
+ paginationTimestamp = self.safe_integer(last, 'timestamp') + 1
5529
6516
  if (until is not None) and (paginationTimestamp >= until):
5530
6517
  break
5531
6518
  except Exception as e:
5532
6519
  errors += 1
5533
6520
  if errors > maxRetries:
5534
6521
  raise e
5535
- uniqueResults = self.remove_repeated_elements_from_array(result)
6522
+ uniqueResults = result
6523
+ if removeRepeatedOption:
6524
+ uniqueResults = self.remove_repeated_elements_from_array(result)
5536
6525
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5537
6526
  return self.filter_by_since_limit(uniqueResults, since, limit, key)
5538
6527
 
@@ -5597,6 +6586,8 @@ class Exchange(object):
5597
6586
  i = 0
5598
6587
  errors = 0
5599
6588
  result = []
6589
+ timeframe = self.safe_string(params, 'timeframe')
6590
+ params = self.omit(params, 'timeframe') # reading the timeframe from the method arguments to avoid changing the signature
5600
6591
  while(i < maxCalls):
5601
6592
  try:
5602
6593
  if cursorValue is not None:
@@ -5606,8 +6597,10 @@ class Exchange(object):
5606
6597
  response = None
5607
6598
  if method == 'fetchAccounts':
5608
6599
  response = getattr(self, method)(params)
5609
- elif method == 'getLeverageTiersPaginated':
6600
+ elif method == 'getLeverageTiersPaginated' or method == 'fetchPositions':
5610
6601
  response = getattr(self, method)(symbol, params)
6602
+ elif method == 'fetchOpenInterestHistory':
6603
+ response = getattr(self, method)(symbol, timeframe, since, maxEntriesPerRequest, params)
5611
6604
  else:
5612
6605
  response = getattr(self, method)(symbol, since, maxEntriesPerRequest, params)
5613
6606
  errors = 0
@@ -5620,8 +6613,17 @@ class Exchange(object):
5620
6613
  if responseLength == 0:
5621
6614
  break
5622
6615
  result = self.array_concat(result, response)
5623
- last = self.safe_value(response, responseLength - 1)
5624
- cursorValue = self.safe_value(last['info'], cursorReceived)
6616
+ last = self.safe_dict(response, responseLength - 1)
6617
+ # cursorValue = self.safe_value(last['info'], cursorReceived)
6618
+ cursorValue = None # search for the cursor
6619
+ for j in range(0, responseLength):
6620
+ index = responseLength - j - 1
6621
+ entry = self.safe_dict(response, index)
6622
+ info = self.safe_dict(entry, 'info')
6623
+ cursor = self.safe_value(info, cursorReceived)
6624
+ if cursor is not None:
6625
+ cursorValue = cursor
6626
+ break
5625
6627
  if cursorValue is None:
5626
6628
  break
5627
6629
  lastTimestamp = self.safe_integer(last, 'timestamp')
@@ -5676,24 +6678,36 @@ class Exchange(object):
5676
6678
  return self.sort_by(result, 'id', True)
5677
6679
  return result
5678
6680
 
5679
- def remove_repeated_elements_from_array(self, input):
6681
+ def remove_repeated_elements_from_array(self, input, fallbackToTimestamp: bool = True):
6682
+ uniqueDic = {}
6683
+ uniqueResult = []
6684
+ for i in range(0, len(input)):
6685
+ entry = input[i]
6686
+ uniqValue = self.safe_string_n(entry, ['id', 'timestamp', 0]) if fallbackToTimestamp else self.safe_string(entry, 'id')
6687
+ if uniqValue is not None and not (uniqValue in uniqueDic):
6688
+ uniqueDic[uniqValue] = 1
6689
+ uniqueResult.append(entry)
6690
+ valuesLength = len(uniqueResult)
6691
+ if valuesLength > 0:
6692
+ return uniqueResult
6693
+ return input
6694
+
6695
+ def remove_repeated_trades_from_array(self, input):
5680
6696
  uniqueResult = {}
5681
6697
  for i in range(0, len(input)):
5682
6698
  entry = input[i]
5683
6699
  id = self.safe_string(entry, 'id')
5684
- if id is not None:
5685
- if self.safe_string(uniqueResult, id) is None:
5686
- uniqueResult[id] = entry
5687
- else:
5688
- timestamp = self.safe_integer_2(entry, 'timestamp', 0)
5689
- if timestamp is not None:
5690
- if self.safe_string(uniqueResult, timestamp) is None:
5691
- uniqueResult[timestamp] = entry
6700
+ if id is None:
6701
+ price = self.safe_string(entry, 'price')
6702
+ amount = self.safe_string(entry, 'amount')
6703
+ timestamp = self.safe_string(entry, 'timestamp')
6704
+ side = self.safe_string(entry, 'side')
6705
+ # unique trade identifier
6706
+ id = 't_' + str(timestamp) + '_' + side + '_' + price + '_' + amount
6707
+ if id is not None and not (id in uniqueResult):
6708
+ uniqueResult[id] = entry
5692
6709
  values = list(uniqueResult.values())
5693
- valuesLength = len(values)
5694
- if valuesLength > 0:
5695
- return values
5696
- return input
6710
+ return values
5697
6711
 
5698
6712
  def handle_until_option(self, key: str, request, params, multiplier=1):
5699
6713
  until = self.safe_integer_2(params, 'until', 'till')
@@ -5702,9 +6716,12 @@ class Exchange(object):
5702
6716
  params = self.omit(params, ['until', 'till'])
5703
6717
  return [request, params]
5704
6718
 
5705
- def safe_open_interest(self, interest, market: Market = None):
6719
+ def safe_open_interest(self, interest: dict, market: Market = None):
6720
+ symbol = self.safe_string(interest, 'symbol')
6721
+ if symbol is None:
6722
+ symbol = self.safe_string(market, 'symbol')
5706
6723
  return self.extend(interest, {
5707
- 'symbol': self.safe_string(market, 'symbol'),
6724
+ 'symbol': symbol,
5708
6725
  'baseVolume': self.safe_number(interest, 'baseVolume'), # deprecated
5709
6726
  'quoteVolume': self.safe_number(interest, 'quoteVolume'), # deprecated
5710
6727
  'openInterestAmount': self.safe_number(interest, 'openInterestAmount'),
@@ -5719,7 +6736,7 @@ class Exchange(object):
5719
6736
 
5720
6737
  def parse_liquidations(self, liquidations: List[dict], market: Market = None, since: Int = None, limit: Int = None):
5721
6738
  """
5722
- * @ignore
6739
+ @ignore
5723
6740
  parses liquidation info from the exchange response
5724
6741
  :param dict[] liquidations: each item describes an instance of a liquidation event
5725
6742
  :param dict market: ccxt market
@@ -5739,6 +6756,27 @@ class Exchange(object):
5739
6756
  def parse_greeks(self, greeks: dict, market: Market = None):
5740
6757
  raise NotSupported(self.id + ' parseGreeks() is not supported yet')
5741
6758
 
6759
+ def parse_all_greeks(self, greeks, symbols: Strings = None, params={}):
6760
+ #
6761
+ # the value of greeks is either a dict or a list
6762
+ #
6763
+ results = []
6764
+ if isinstance(greeks, list):
6765
+ for i in range(0, len(greeks)):
6766
+ parsedTicker = self.parse_greeks(greeks[i])
6767
+ greek = self.extend(parsedTicker, params)
6768
+ results.append(greek)
6769
+ else:
6770
+ marketIds = list(greeks.keys())
6771
+ for i in range(0, len(marketIds)):
6772
+ marketId = marketIds[i]
6773
+ market = self.safe_market(marketId)
6774
+ parsed = self.parse_greeks(greeks[marketId], market)
6775
+ greek = self.extend(parsed, params)
6776
+ results.append(greek)
6777
+ symbols = self.market_symbols(symbols)
6778
+ return self.filter_by_array(results, 'symbol', symbols)
6779
+
5742
6780
  def parse_option(self, chain: dict, currency: Currency = None, market: Market = None):
5743
6781
  raise NotSupported(self.id + ' parseOption() is not supported yet')
5744
6782
 
@@ -5855,7 +6893,7 @@ class Exchange(object):
5855
6893
  return reconstructedDate
5856
6894
 
5857
6895
  def convert_market_id_expire_date(self, date: str):
5858
- # parse 03JAN24 to 240103
6896
+ # parse 03JAN24 to 240103.
5859
6897
  monthMappping = {
5860
6898
  'JAN': '01',
5861
6899
  'FEB': '02',
@@ -5891,7 +6929,7 @@ class Exchange(object):
5891
6929
  """
5892
6930
  if self.has['fetchPositionsHistory']:
5893
6931
  positions = self.fetch_positions_history([symbol], since, limit, params)
5894
- return self.safe_dict(positions, 0)
6932
+ return positions
5895
6933
  else:
5896
6934
  raise NotSupported(self.id + ' fetchPositionHistory() is not supported yet')
5897
6935
 
@@ -5909,7 +6947,7 @@ class Exchange(object):
5909
6947
  def parse_margin_modification(self, data: dict, market: Market = None):
5910
6948
  raise NotSupported(self.id + ' parseMarginModification() is not supported yet')
5911
6949
 
5912
- def parse_margin_modifications(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
6950
+ def parse_margin_modifications(self, response: List[object], symbols: Strings = None, symbolKey: Str = None, marketType: MarketType = None):
5913
6951
  marginModifications = []
5914
6952
  for i in range(0, len(response)):
5915
6953
  info = response[i]
@@ -5939,3 +6977,71 @@ class Exchange(object):
5939
6977
  :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
5940
6978
  """
5941
6979
  raise NotSupported(self.id + ' fetchTransfers() is not supported yet')
6980
+
6981
+ def clean_unsubscription(self, client, subHash: str, unsubHash: str, subHashIsPrefix=False):
6982
+ if unsubHash in client.subscriptions:
6983
+ del client.subscriptions[unsubHash]
6984
+ if not subHashIsPrefix:
6985
+ if subHash in client.subscriptions:
6986
+ del client.subscriptions[subHash]
6987
+ if subHash in client.futures:
6988
+ error = UnsubscribeError(self.id + ' ' + subHash)
6989
+ client.reject(error, subHash)
6990
+ else:
6991
+ clientSubscriptions = list(client.subscriptions.keys())
6992
+ for i in range(0, len(clientSubscriptions)):
6993
+ sub = clientSubscriptions[i]
6994
+ if sub.startswith(subHash):
6995
+ del client.subscriptions[sub]
6996
+ clientFutures = list(client.futures.keys())
6997
+ for i in range(0, len(clientFutures)):
6998
+ future = clientFutures[i]
6999
+ if future.startswith(subHash):
7000
+ error = UnsubscribeError(self.id + ' ' + future)
7001
+ client.reject(error, future)
7002
+ client.resolve(True, unsubHash)
7003
+
7004
+ def clean_cache(self, subscription: dict):
7005
+ topic = self.safe_string(subscription, 'topic')
7006
+ symbols = self.safe_list(subscription, 'symbols', [])
7007
+ symbolsLength = len(symbols)
7008
+ if topic == 'ohlcv':
7009
+ symbolsAndTimeFrames = self.safe_list(subscription, 'symbolsAndTimeframes', [])
7010
+ for i in range(0, len(symbolsAndTimeFrames)):
7011
+ symbolAndTimeFrame = symbolsAndTimeFrames[i]
7012
+ symbol = self.safe_string(symbolAndTimeFrame, 0)
7013
+ timeframe = self.safe_string(symbolAndTimeFrame, 1)
7014
+ if (self.ohlcvs is not None) and (symbol in self.ohlcvs):
7015
+ if timeframe in self.ohlcvs[symbol]:
7016
+ del self.ohlcvs[symbol][timeframe]
7017
+ elif symbolsLength > 0:
7018
+ for i in range(0, len(symbols)):
7019
+ symbol = symbols[i]
7020
+ if topic == 'trades':
7021
+ if symbol in self.trades:
7022
+ del self.trades[symbol]
7023
+ elif topic == 'orderbook':
7024
+ if symbol in self.orderbooks:
7025
+ del self.orderbooks[symbol]
7026
+ elif topic == 'ticker':
7027
+ if symbol in self.tickers:
7028
+ del self.tickers[symbol]
7029
+ else:
7030
+ if topic == 'myTrades' and (self.myTrades is not None):
7031
+ self.myTrades = None
7032
+ elif topic == 'orders' and (self.orders is not None):
7033
+ self.orders = None
7034
+ elif topic == 'positions' and (self.positions is not None):
7035
+ self.positions = None
7036
+ clients = list(self.clients.values())
7037
+ for i in range(0, len(clients)):
7038
+ client = clients[i]
7039
+ futures = self.safe_dict(client, 'futures')
7040
+ if (futures is not None) and ('fetchPositionsSnapshot' in futures):
7041
+ del futures['fetchPositionsSnapshot']
7042
+ elif topic == 'ticker' and (self.tickers is not None):
7043
+ tickerSymbols = list(self.tickers.keys())
7044
+ for i in range(0, len(tickerSymbols)):
7045
+ tickerSymbol = tickerSymbols[i]
7046
+ if tickerSymbol in self.tickers:
7047
+ del self.tickers[tickerSymbol]