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