ccxt-ir 4.3.46.0.1__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 +358 -0
- ccxt/abantether.py +316 -0
- ccxt/abstract/__init__.py +0 -0
- ccxt/abstract/abantether.py +5 -0
- ccxt/abstract/ace.py +15 -0
- ccxt/abstract/afratether.py +6 -0
- ccxt/abstract/alpaca.py +70 -0
- ccxt/abstract/arzinja.py +5 -0
- ccxt/abstract/arzplus.py +7 -0
- ccxt/abstract/ascendex.py +77 -0
- ccxt/abstract/bequant.py +115 -0
- ccxt/abstract/bigone.py +45 -0
- ccxt/abstract/binance.py +712 -0
- ccxt/abstract/binancecoinm.py +712 -0
- ccxt/abstract/binanceus.py +764 -0
- ccxt/abstract/binanceusdm.py +712 -0
- ccxt/abstract/bingx.py +113 -0
- ccxt/abstract/bit2c.py +27 -0
- ccxt/abstract/bitbank.py +27 -0
- ccxt/abstract/bitbay.py +53 -0
- ccxt/abstract/bitbns.py +40 -0
- ccxt/abstract/bitcoincom.py +115 -0
- ccxt/abstract/bitfinex.py +69 -0
- ccxt/abstract/bitfinex2.py +139 -0
- ccxt/abstract/bitflyer.py +38 -0
- ccxt/abstract/bitget.py +508 -0
- ccxt/abstract/bithumb.py +32 -0
- ccxt/abstract/bitimen.py +7 -0
- ccxt/abstract/bitir.py +7 -0
- ccxt/abstract/bitmart.py +99 -0
- ccxt/abstract/bitmex.py +97 -0
- ccxt/abstract/bitopro.py +29 -0
- ccxt/abstract/bitpanda.py +35 -0
- ccxt/abstract/bitpin.py +7 -0
- ccxt/abstract/bitrue.py +72 -0
- ccxt/abstract/bitso.py +43 -0
- ccxt/abstract/bitstamp.py +258 -0
- ccxt/abstract/bitteam.py +29 -0
- ccxt/abstract/bitvavo.py +27 -0
- ccxt/abstract/bl3p.py +19 -0
- ccxt/abstract/blockchaincom.py +28 -0
- ccxt/abstract/blofin.py +37 -0
- ccxt/abstract/btcalpha.py +18 -0
- ccxt/abstract/btcbox.py +13 -0
- ccxt/abstract/btcmarkets.py +39 -0
- ccxt/abstract/btcturk.py +20 -0
- ccxt/abstract/bybit.py +298 -0
- ccxt/abstract/cex.py +33 -0
- ccxt/abstract/coinbase.py +94 -0
- ccxt/abstract/coinbaseadvanced.py +94 -0
- ccxt/abstract/coinbaseexchange.py +67 -0
- ccxt/abstract/coinbaseinternational.py +39 -0
- ccxt/abstract/coincatch.py +94 -0
- ccxt/abstract/coincheck.py +33 -0
- ccxt/abstract/coinex.py +237 -0
- ccxt/abstract/coinlist.py +54 -0
- ccxt/abstract/coinmate.py +62 -0
- ccxt/abstract/coinmetro.py +34 -0
- ccxt/abstract/coinone.py +67 -0
- ccxt/abstract/coinsph.py +54 -0
- ccxt/abstract/coinspot.py +28 -0
- ccxt/abstract/cryptocom.py +107 -0
- ccxt/abstract/currencycom.py +68 -0
- ccxt/abstract/delta.py +50 -0
- ccxt/abstract/deribit.py +125 -0
- ccxt/abstract/digifinex.py +91 -0
- ccxt/abstract/eterex.py +5 -0
- ccxt/abstract/excoino.py +7 -0
- ccxt/abstract/exir.py +8 -0
- ccxt/abstract/exmo.py +55 -0
- ccxt/abstract/exnovin.py +6 -0
- ccxt/abstract/farhadexchange.py +5 -0
- ccxt/abstract/fmfwio.py +115 -0
- ccxt/abstract/gate.py +265 -0
- ccxt/abstract/gateio.py +265 -0
- ccxt/abstract/gemini.py +58 -0
- ccxt/abstract/hashkey.py +67 -0
- ccxt/abstract/hitbtc.py +115 -0
- ccxt/abstract/hitbtc3.py +115 -0
- ccxt/abstract/hitobit.py +8 -0
- ccxt/abstract/hollaex.py +33 -0
- ccxt/abstract/htx.py +548 -0
- ccxt/abstract/huobi.py +548 -0
- ccxt/abstract/huobijp.py +114 -0
- ccxt/abstract/hyperliquid.py +6 -0
- ccxt/abstract/idex.py +26 -0
- ccxt/abstract/independentreserve.py +37 -0
- ccxt/abstract/indodax.py +26 -0
- ccxt/abstract/jibitex.py +7 -0
- ccxt/abstract/kraken.py +57 -0
- ccxt/abstract/krakenfutures.py +38 -0
- ccxt/abstract/kucoin.py +214 -0
- ccxt/abstract/kucoinfutures.py +233 -0
- ccxt/abstract/kuna.py +182 -0
- ccxt/abstract/latoken.py +56 -0
- ccxt/abstract/lbank.py +61 -0
- ccxt/abstract/luno.py +37 -0
- ccxt/abstract/lykke.py +29 -0
- ccxt/abstract/mercado.py +25 -0
- ccxt/abstract/mexc.py +178 -0
- ccxt/abstract/ndax.py +97 -0
- ccxt/abstract/nobitex.py +7 -0
- ccxt/abstract/novadax.py +29 -0
- ccxt/abstract/oceanex.py +22 -0
- ccxt/abstract/okcoin.py +74 -0
- ccxt/abstract/okexchange.py +8 -0
- ccxt/abstract/okx.py +324 -0
- ccxt/abstract/ompfinex.py +7 -0
- ccxt/abstract/onetrading.py +35 -0
- ccxt/abstract/oxfun.py +34 -0
- ccxt/abstract/p2b.py +22 -0
- ccxt/abstract/paradex.py +40 -0
- ccxt/abstract/paymium.py +28 -0
- ccxt/abstract/phemex.py +115 -0
- ccxt/abstract/poloniex.py +69 -0
- ccxt/abstract/poloniexfutures.py +48 -0
- ccxt/abstract/probit.py +23 -0
- ccxt/abstract/ramzinex.py +7 -0
- ccxt/abstract/sarmayex.py +5 -0
- ccxt/abstract/sarrafex.py +7 -0
- ccxt/abstract/tabdeal.py +7 -0
- ccxt/abstract/tetherland.py +5 -0
- ccxt/abstract/timex.py +62 -0
- ccxt/abstract/tokocrypto.py +37 -0
- ccxt/abstract/tradeogre.py +16 -0
- ccxt/abstract/twox.py +5 -0
- ccxt/abstract/ubitex.py +7 -0
- ccxt/abstract/upbit.py +38 -0
- ccxt/abstract/vertex.py +19 -0
- ccxt/abstract/wallex.py +8 -0
- ccxt/abstract/wavesexchange.py +154 -0
- ccxt/abstract/wazirx.py +30 -0
- ccxt/abstract/whitebit.py +98 -0
- ccxt/abstract/woo.py +83 -0
- ccxt/abstract/woofipro.py +119 -0
- ccxt/abstract/xt.py +152 -0
- ccxt/abstract/yobit.py +16 -0
- ccxt/abstract/zaif.py +38 -0
- ccxt/abstract/zonda.py +53 -0
- ccxt/ace.py +1012 -0
- ccxt/afratether.py +293 -0
- ccxt/alpaca.py +1083 -0
- ccxt/arzinja.py +285 -0
- ccxt/arzplus.py +412 -0
- ccxt/ascendex.py +3330 -0
- ccxt/async_support/__init__.py +337 -0
- ccxt/async_support/abantether.py +316 -0
- ccxt/async_support/ace.py +1012 -0
- ccxt/async_support/afratether.py +293 -0
- ccxt/async_support/alpaca.py +1083 -0
- ccxt/async_support/arzinja.py +285 -0
- ccxt/async_support/arzplus.py +412 -0
- ccxt/async_support/ascendex.py +3330 -0
- ccxt/async_support/base/__init__.py +1 -0
- ccxt/async_support/base/exchange.py +1966 -0
- ccxt/async_support/base/throttler.py +50 -0
- ccxt/async_support/base/ws/__init__.py +38 -0
- ccxt/async_support/base/ws/aiohttp_client.py +125 -0
- ccxt/async_support/base/ws/cache.py +212 -0
- ccxt/async_support/base/ws/client.py +193 -0
- ccxt/async_support/base/ws/fast_client.py +96 -0
- ccxt/async_support/base/ws/functions.py +59 -0
- ccxt/async_support/base/ws/future.py +58 -0
- ccxt/async_support/base/ws/order_book.py +78 -0
- ccxt/async_support/base/ws/order_book_side.py +174 -0
- ccxt/async_support/bequant.py +33 -0
- ccxt/async_support/bigone.py +2113 -0
- ccxt/async_support/binance.py +12234 -0
- ccxt/async_support/binancecoinm.py +45 -0
- ccxt/async_support/binanceus.py +211 -0
- ccxt/async_support/binanceusdm.py +58 -0
- ccxt/async_support/bingx.py +4325 -0
- ccxt/async_support/bit2c.py +866 -0
- ccxt/async_support/bitbank.py +1001 -0
- ccxt/async_support/bitbay.py +17 -0
- ccxt/async_support/bitbns.py +1154 -0
- ccxt/async_support/bitcoincom.py +17 -0
- ccxt/async_support/bitfinex.py +1617 -0
- ccxt/async_support/bitfinex2.py +3552 -0
- ccxt/async_support/bitflyer.py +995 -0
- ccxt/async_support/bitget.py +8273 -0
- ccxt/async_support/bithumb.py +1061 -0
- ccxt/async_support/bitimen.py +401 -0
- ccxt/async_support/bitir.py +490 -0
- ccxt/async_support/bitmart.py +4415 -0
- ccxt/async_support/bitmex.py +2756 -0
- ccxt/async_support/bitopro.py +1630 -0
- ccxt/async_support/bitpanda.py +16 -0
- ccxt/async_support/bitpin.py +454 -0
- ccxt/async_support/bitrue.py +3027 -0
- ccxt/async_support/bitso.py +1670 -0
- ccxt/async_support/bitstamp.py +2203 -0
- ccxt/async_support/bitteam.py +2239 -0
- ccxt/async_support/bitvavo.py +1968 -0
- ccxt/async_support/bl3p.py +485 -0
- ccxt/async_support/blockchaincom.py +1104 -0
- ccxt/async_support/blofin.py +2066 -0
- ccxt/async_support/btcalpha.py +891 -0
- ccxt/async_support/btcbox.py +544 -0
- ccxt/async_support/btcmarkets.py +1221 -0
- ccxt/async_support/btcturk.py +911 -0
- ccxt/async_support/bybit.py +8159 -0
- ccxt/async_support/cex.py +1605 -0
- ccxt/async_support/coinbase.py +4475 -0
- ccxt/async_support/coinbaseadvanced.py +17 -0
- ccxt/async_support/coinbaseexchange.py +1734 -0
- ccxt/async_support/coinbaseinternational.py +1899 -0
- ccxt/async_support/coincatch.py +5069 -0
- ccxt/async_support/coincheck.py +815 -0
- ccxt/async_support/coinex.py +5526 -0
- ccxt/async_support/coinlist.py +2243 -0
- ccxt/async_support/coinmate.py +1067 -0
- ccxt/async_support/coinmetro.py +1797 -0
- ccxt/async_support/coinone.py +1127 -0
- ccxt/async_support/coinsph.py +1850 -0
- ccxt/async_support/coinspot.py +534 -0
- ccxt/async_support/cryptocom.py +2822 -0
- ccxt/async_support/currencycom.py +1950 -0
- ccxt/async_support/delta.py +3376 -0
- ccxt/async_support/deribit.py +3437 -0
- ccxt/async_support/digifinex.py +3960 -0
- ccxt/async_support/eterex.py +286 -0
- ccxt/async_support/excoino.py +399 -0
- ccxt/async_support/exir.py +375 -0
- ccxt/async_support/exmo.py +2462 -0
- ccxt/async_support/exnovin.py +360 -0
- ccxt/async_support/farhadexchange.py +266 -0
- ccxt/async_support/fmfwio.py +34 -0
- ccxt/async_support/gate.py +6976 -0
- ccxt/async_support/gateio.py +16 -0
- ccxt/async_support/gemini.py +1825 -0
- ccxt/async_support/hashkey.py +4150 -0
- ccxt/async_support/hitbtc.py +3423 -0
- ccxt/async_support/hitbtc3.py +16 -0
- ccxt/async_support/hitobit.py +391 -0
- ccxt/async_support/hollaex.py +1813 -0
- ccxt/async_support/htx.py +8506 -0
- ccxt/async_support/huobi.py +16 -0
- ccxt/async_support/huobijp.py +1801 -0
- ccxt/async_support/hyperliquid.py +2431 -0
- ccxt/async_support/idex.py +1766 -0
- ccxt/async_support/independentreserve.py +784 -0
- ccxt/async_support/indodax.py +1247 -0
- ccxt/async_support/jibitex.py +395 -0
- ccxt/async_support/kraken.py +2894 -0
- ccxt/async_support/krakenfutures.py +2601 -0
- ccxt/async_support/kucoin.py +4602 -0
- ccxt/async_support/kucoinfutures.py +2698 -0
- ccxt/async_support/kuna.py +1841 -0
- ccxt/async_support/latoken.py +1664 -0
- ccxt/async_support/lbank.py +2683 -0
- ccxt/async_support/luno.py +1067 -0
- ccxt/async_support/lykke.py +1270 -0
- ccxt/async_support/mercado.py +842 -0
- ccxt/async_support/mexc.py +5369 -0
- ccxt/async_support/ndax.py +2354 -0
- ccxt/async_support/nobitex.py +419 -0
- ccxt/async_support/novadax.py +1484 -0
- ccxt/async_support/oceanex.py +903 -0
- ccxt/async_support/okcoin.py +2936 -0
- ccxt/async_support/okexchange.py +349 -0
- ccxt/async_support/okx.py +7827 -0
- ccxt/async_support/ompfinex.py +472 -0
- ccxt/async_support/onetrading.py +1911 -0
- ccxt/async_support/oxfun.py +2773 -0
- ccxt/async_support/p2b.py +1194 -0
- ccxt/async_support/paradex.py +2015 -0
- ccxt/async_support/paymium.py +564 -0
- ccxt/async_support/phemex.py +4473 -0
- ccxt/async_support/poloniex.py +2232 -0
- ccxt/async_support/poloniexfutures.py +1717 -0
- ccxt/async_support/probit.py +1734 -0
- ccxt/async_support/ramzinex.py +476 -0
- ccxt/async_support/sarmayex.py +357 -0
- ccxt/async_support/sarrafex.py +478 -0
- ccxt/async_support/tabdeal.py +364 -0
- ccxt/async_support/tetherland.py +349 -0
- ccxt/async_support/timex.py +1593 -0
- ccxt/async_support/tokocrypto.py +2405 -0
- ccxt/async_support/tradeogre.py +608 -0
- ccxt/async_support/twox.py +326 -0
- ccxt/async_support/ubitex.py +409 -0
- ccxt/async_support/upbit.py +1833 -0
- ccxt/async_support/vertex.py +2922 -0
- ccxt/async_support/wallex.py +445 -0
- ccxt/async_support/wavesexchange.py +2473 -0
- ccxt/async_support/wazirx.py +1224 -0
- ccxt/async_support/whitebit.py +2469 -0
- ccxt/async_support/woo.py +3114 -0
- ccxt/async_support/woofipro.py +2533 -0
- ccxt/async_support/xt.py +4454 -0
- ccxt/async_support/yobit.py +1283 -0
- ccxt/async_support/zaif.py +725 -0
- ccxt/async_support/zonda.py +1828 -0
- ccxt/base/__init__.py +27 -0
- ccxt/base/decimal_to_precision.py +174 -0
- ccxt/base/errors.py +242 -0
- ccxt/base/exchange.py +5941 -0
- ccxt/base/precise.py +287 -0
- ccxt/base/types.py +502 -0
- ccxt/bequant.py +33 -0
- ccxt/bigone.py +2112 -0
- ccxt/binance.py +12233 -0
- ccxt/binancecoinm.py +45 -0
- ccxt/binanceus.py +211 -0
- ccxt/binanceusdm.py +58 -0
- ccxt/bingx.py +4324 -0
- ccxt/bit2c.py +866 -0
- ccxt/bitbank.py +1001 -0
- ccxt/bitbay.py +17 -0
- ccxt/bitbns.py +1154 -0
- ccxt/bitcoincom.py +17 -0
- ccxt/bitfinex.py +1617 -0
- ccxt/bitfinex2.py +3552 -0
- ccxt/bitflyer.py +995 -0
- ccxt/bitget.py +8272 -0
- ccxt/bithumb.py +1061 -0
- ccxt/bitimen.py +401 -0
- ccxt/bitir.py +490 -0
- ccxt/bitmart.py +4415 -0
- ccxt/bitmex.py +2756 -0
- ccxt/bitopro.py +1630 -0
- ccxt/bitpanda.py +16 -0
- ccxt/bitpin.py +454 -0
- ccxt/bitrue.py +3026 -0
- ccxt/bitso.py +1670 -0
- ccxt/bitstamp.py +2203 -0
- ccxt/bitteam.py +2239 -0
- ccxt/bitvavo.py +1968 -0
- ccxt/bl3p.py +485 -0
- ccxt/blockchaincom.py +1104 -0
- ccxt/blofin.py +2066 -0
- ccxt/btcalpha.py +891 -0
- ccxt/btcbox.py +544 -0
- ccxt/btcmarkets.py +1221 -0
- ccxt/btcturk.py +911 -0
- ccxt/bybit.py +8158 -0
- ccxt/cex.py +1605 -0
- ccxt/coinbase.py +4474 -0
- ccxt/coinbaseadvanced.py +17 -0
- ccxt/coinbaseexchange.py +1734 -0
- ccxt/coinbaseinternational.py +1899 -0
- ccxt/coincatch.py +5069 -0
- ccxt/coincheck.py +815 -0
- ccxt/coinex.py +5525 -0
- ccxt/coinlist.py +2243 -0
- ccxt/coinmate.py +1067 -0
- ccxt/coinmetro.py +1797 -0
- ccxt/coinone.py +1127 -0
- ccxt/coinsph.py +1850 -0
- ccxt/coinspot.py +534 -0
- ccxt/cryptocom.py +2822 -0
- ccxt/currencycom.py +1950 -0
- ccxt/delta.py +3376 -0
- ccxt/deribit.py +3437 -0
- ccxt/digifinex.py +3959 -0
- ccxt/eterex.py +286 -0
- ccxt/excoino.py +399 -0
- ccxt/exir.py +375 -0
- ccxt/exmo.py +2462 -0
- ccxt/exnovin.py +360 -0
- ccxt/farhadexchange.py +266 -0
- ccxt/fmfwio.py +34 -0
- ccxt/gate.py +6975 -0
- ccxt/gateio.py +16 -0
- ccxt/gemini.py +1824 -0
- ccxt/hashkey.py +4150 -0
- ccxt/hitbtc.py +3423 -0
- ccxt/hitbtc3.py +16 -0
- ccxt/hitobit.py +391 -0
- ccxt/hollaex.py +1813 -0
- ccxt/htx.py +8505 -0
- ccxt/huobi.py +16 -0
- ccxt/huobijp.py +1801 -0
- ccxt/hyperliquid.py +2430 -0
- ccxt/idex.py +1766 -0
- ccxt/independentreserve.py +784 -0
- ccxt/indodax.py +1247 -0
- ccxt/jibitex.py +395 -0
- ccxt/kraken.py +2894 -0
- ccxt/krakenfutures.py +2601 -0
- ccxt/kucoin.py +4601 -0
- ccxt/kucoinfutures.py +2698 -0
- ccxt/kuna.py +1841 -0
- ccxt/latoken.py +1664 -0
- ccxt/lbank.py +2682 -0
- ccxt/luno.py +1067 -0
- ccxt/lykke.py +1270 -0
- ccxt/mercado.py +842 -0
- ccxt/mexc.py +5369 -0
- ccxt/ndax.py +2354 -0
- ccxt/nobitex.py +419 -0
- ccxt/novadax.py +1484 -0
- ccxt/oceanex.py +903 -0
- ccxt/okcoin.py +2936 -0
- ccxt/okexchange.py +349 -0
- ccxt/okx.py +7826 -0
- ccxt/ompfinex.py +472 -0
- ccxt/onetrading.py +1911 -0
- ccxt/oxfun.py +2772 -0
- ccxt/p2b.py +1194 -0
- ccxt/paradex.py +2015 -0
- ccxt/paymium.py +564 -0
- ccxt/phemex.py +4473 -0
- ccxt/poloniex.py +2232 -0
- ccxt/poloniexfutures.py +1717 -0
- ccxt/pro/__init__.py +149 -0
- ccxt/pro/alpaca.py +685 -0
- ccxt/pro/ascendex.py +916 -0
- ccxt/pro/bequant.py +38 -0
- ccxt/pro/binance.py +3488 -0
- ccxt/pro/binancecoinm.py +28 -0
- ccxt/pro/binanceus.py +48 -0
- ccxt/pro/binanceusdm.py +31 -0
- ccxt/pro/bingx.py +1264 -0
- ccxt/pro/bitcoincom.py +34 -0
- ccxt/pro/bitfinex.py +621 -0
- ccxt/pro/bitfinex2.py +1083 -0
- ccxt/pro/bitget.py +1692 -0
- ccxt/pro/bithumb.py +368 -0
- ccxt/pro/bitmart.py +1449 -0
- ccxt/pro/bitmex.py +1656 -0
- ccxt/pro/bitopro.py +445 -0
- ccxt/pro/bitpanda.py +15 -0
- ccxt/pro/bitrue.py +447 -0
- ccxt/pro/bitstamp.py +522 -0
- ccxt/pro/bitvavo.py +1270 -0
- ccxt/pro/blockchaincom.py +738 -0
- ccxt/pro/blofin.py +692 -0
- ccxt/pro/bybit.py +2000 -0
- ccxt/pro/cex.py +1440 -0
- ccxt/pro/coinbase.py +678 -0
- ccxt/pro/coinbaseadvanced.py +16 -0
- ccxt/pro/coinbaseexchange.py +895 -0
- ccxt/pro/coinbaseinternational.py +620 -0
- ccxt/pro/coincatch.py +1464 -0
- ccxt/pro/coincheck.py +199 -0
- ccxt/pro/coinex.py +1061 -0
- ccxt/pro/coinone.py +395 -0
- ccxt/pro/cryptocom.py +947 -0
- ccxt/pro/currencycom.py +536 -0
- ccxt/pro/deribit.py +892 -0
- ccxt/pro/exmo.py +629 -0
- ccxt/pro/gate.py +1416 -0
- ccxt/pro/gateio.py +15 -0
- ccxt/pro/gemini.py +865 -0
- ccxt/pro/hashkey.py +802 -0
- ccxt/pro/hitbtc.py +1216 -0
- ccxt/pro/hollaex.py +563 -0
- ccxt/pro/htx.py +2215 -0
- ccxt/pro/huobi.py +15 -0
- ccxt/pro/huobijp.py +570 -0
- ccxt/pro/hyperliquid.py +525 -0
- ccxt/pro/idex.py +672 -0
- ccxt/pro/independentreserve.py +270 -0
- ccxt/pro/kraken.py +1356 -0
- ccxt/pro/krakenfutures.py +1492 -0
- ccxt/pro/kucoin.py +1133 -0
- ccxt/pro/kucoinfutures.py +1081 -0
- ccxt/pro/lbank.py +843 -0
- ccxt/pro/luno.py +303 -0
- ccxt/pro/mexc.py +1122 -0
- ccxt/pro/ndax.py +506 -0
- ccxt/pro/okcoin.py +698 -0
- ccxt/pro/okx.py +1851 -0
- ccxt/pro/onetrading.py +1275 -0
- ccxt/pro/oxfun.py +950 -0
- ccxt/pro/p2b.py +419 -0
- ccxt/pro/paradex.py +352 -0
- ccxt/pro/phemex.py +1441 -0
- ccxt/pro/poloniex.py +1166 -0
- ccxt/pro/poloniexfutures.py +990 -0
- ccxt/pro/probit.py +551 -0
- ccxt/pro/upbit.py +520 -0
- ccxt/pro/vertex.py +943 -0
- ccxt/pro/wazirx.py +749 -0
- ccxt/pro/whitebit.py +864 -0
- ccxt/pro/woo.py +1078 -0
- ccxt/pro/woofipro.py +1183 -0
- ccxt/pro/xt.py +1067 -0
- ccxt/probit.py +1734 -0
- ccxt/ramzinex.py +476 -0
- ccxt/sarmayex.py +357 -0
- ccxt/sarrafex.py +478 -0
- ccxt/static_dependencies/__init__.py +1 -0
- ccxt/static_dependencies/ecdsa/__init__.py +14 -0
- ccxt/static_dependencies/ecdsa/_version.py +520 -0
- ccxt/static_dependencies/ecdsa/curves.py +56 -0
- ccxt/static_dependencies/ecdsa/der.py +221 -0
- ccxt/static_dependencies/ecdsa/ecdsa.py +310 -0
- ccxt/static_dependencies/ecdsa/ellipticcurve.py +197 -0
- ccxt/static_dependencies/ecdsa/keys.py +332 -0
- ccxt/static_dependencies/ecdsa/numbertheory.py +531 -0
- ccxt/static_dependencies/ecdsa/rfc6979.py +100 -0
- ccxt/static_dependencies/ecdsa/util.py +266 -0
- ccxt/static_dependencies/ethereum/__init__.py +7 -0
- ccxt/static_dependencies/ethereum/abi/__init__.py +16 -0
- ccxt/static_dependencies/ethereum/abi/abi.py +19 -0
- ccxt/static_dependencies/ethereum/abi/base.py +152 -0
- ccxt/static_dependencies/ethereum/abi/codec.py +217 -0
- ccxt/static_dependencies/ethereum/abi/constants.py +3 -0
- ccxt/static_dependencies/ethereum/abi/decoding.py +565 -0
- ccxt/static_dependencies/ethereum/abi/encoding.py +720 -0
- ccxt/static_dependencies/ethereum/abi/exceptions.py +139 -0
- ccxt/static_dependencies/ethereum/abi/grammar.py +443 -0
- ccxt/static_dependencies/ethereum/abi/packed.py +13 -0
- ccxt/static_dependencies/ethereum/abi/py.typed +0 -0
- ccxt/static_dependencies/ethereum/abi/registry.py +643 -0
- ccxt/static_dependencies/ethereum/abi/tools/__init__.py +3 -0
- ccxt/static_dependencies/ethereum/abi/tools/_strategies.py +230 -0
- ccxt/static_dependencies/ethereum/abi/utils/__init__.py +0 -0
- ccxt/static_dependencies/ethereum/abi/utils/numeric.py +83 -0
- ccxt/static_dependencies/ethereum/abi/utils/padding.py +27 -0
- ccxt/static_dependencies/ethereum/abi/utils/string.py +19 -0
- ccxt/static_dependencies/ethereum/account/__init__.py +3 -0
- ccxt/static_dependencies/ethereum/account/encode_typed_data/__init__.py +4 -0
- ccxt/static_dependencies/ethereum/account/encode_typed_data/encoding_and_hashing.py +239 -0
- ccxt/static_dependencies/ethereum/account/encode_typed_data/helpers.py +40 -0
- ccxt/static_dependencies/ethereum/account/messages.py +263 -0
- ccxt/static_dependencies/ethereum/account/py.typed +0 -0
- ccxt/static_dependencies/ethereum/hexbytes/__init__.py +5 -0
- ccxt/static_dependencies/ethereum/hexbytes/_utils.py +54 -0
- ccxt/static_dependencies/ethereum/hexbytes/main.py +65 -0
- ccxt/static_dependencies/ethereum/hexbytes/py.typed +0 -0
- ccxt/static_dependencies/ethereum/typing/__init__.py +63 -0
- ccxt/static_dependencies/ethereum/typing/abi.py +6 -0
- ccxt/static_dependencies/ethereum/typing/bls.py +7 -0
- ccxt/static_dependencies/ethereum/typing/discovery.py +5 -0
- ccxt/static_dependencies/ethereum/typing/encoding.py +7 -0
- ccxt/static_dependencies/ethereum/typing/enums.py +17 -0
- ccxt/static_dependencies/ethereum/typing/ethpm.py +9 -0
- ccxt/static_dependencies/ethereum/typing/evm.py +20 -0
- ccxt/static_dependencies/ethereum/typing/networks.py +1122 -0
- ccxt/static_dependencies/ethereum/typing/py.typed +0 -0
- ccxt/static_dependencies/ethereum/utils/__init__.py +115 -0
- ccxt/static_dependencies/ethereum/utils/abi.py +72 -0
- ccxt/static_dependencies/ethereum/utils/address.py +171 -0
- ccxt/static_dependencies/ethereum/utils/applicators.py +151 -0
- ccxt/static_dependencies/ethereum/utils/conversions.py +190 -0
- ccxt/static_dependencies/ethereum/utils/currency.py +107 -0
- ccxt/static_dependencies/ethereum/utils/curried/__init__.py +269 -0
- ccxt/static_dependencies/ethereum/utils/debug.py +20 -0
- ccxt/static_dependencies/ethereum/utils/decorators.py +132 -0
- ccxt/static_dependencies/ethereum/utils/encoding.py +6 -0
- ccxt/static_dependencies/ethereum/utils/exceptions.py +4 -0
- ccxt/static_dependencies/ethereum/utils/functional.py +75 -0
- ccxt/static_dependencies/ethereum/utils/hexadecimal.py +74 -0
- ccxt/static_dependencies/ethereum/utils/humanize.py +188 -0
- ccxt/static_dependencies/ethereum/utils/logging.py +159 -0
- ccxt/static_dependencies/ethereum/utils/module_loading.py +31 -0
- ccxt/static_dependencies/ethereum/utils/numeric.py +43 -0
- ccxt/static_dependencies/ethereum/utils/py.typed +0 -0
- ccxt/static_dependencies/ethereum/utils/toolz.py +76 -0
- ccxt/static_dependencies/ethereum/utils/types.py +54 -0
- ccxt/static_dependencies/ethereum/utils/typing/__init__.py +18 -0
- ccxt/static_dependencies/ethereum/utils/typing/misc.py +14 -0
- ccxt/static_dependencies/ethereum/utils/units.py +31 -0
- ccxt/static_dependencies/keccak/__init__.py +3 -0
- ccxt/static_dependencies/keccak/keccak.py +197 -0
- ccxt/static_dependencies/lark/__init__.py +38 -0
- ccxt/static_dependencies/lark/__pyinstaller/__init__.py +6 -0
- ccxt/static_dependencies/lark/__pyinstaller/hook-lark.py +14 -0
- ccxt/static_dependencies/lark/ast_utils.py +59 -0
- ccxt/static_dependencies/lark/common.py +86 -0
- ccxt/static_dependencies/lark/exceptions.py +292 -0
- ccxt/static_dependencies/lark/grammar.py +130 -0
- ccxt/static_dependencies/lark/grammars/__init__.py +0 -0
- ccxt/static_dependencies/lark/indenter.py +143 -0
- ccxt/static_dependencies/lark/lark.py +658 -0
- ccxt/static_dependencies/lark/lexer.py +678 -0
- ccxt/static_dependencies/lark/load_grammar.py +1428 -0
- ccxt/static_dependencies/lark/parse_tree_builder.py +391 -0
- ccxt/static_dependencies/lark/parser_frontends.py +257 -0
- ccxt/static_dependencies/lark/parsers/__init__.py +0 -0
- ccxt/static_dependencies/lark/parsers/cyk.py +340 -0
- ccxt/static_dependencies/lark/parsers/earley.py +314 -0
- ccxt/static_dependencies/lark/parsers/earley_common.py +42 -0
- ccxt/static_dependencies/lark/parsers/earley_forest.py +801 -0
- ccxt/static_dependencies/lark/parsers/grammar_analysis.py +203 -0
- ccxt/static_dependencies/lark/parsers/lalr_analysis.py +332 -0
- ccxt/static_dependencies/lark/parsers/lalr_interactive_parser.py +158 -0
- ccxt/static_dependencies/lark/parsers/lalr_parser.py +122 -0
- ccxt/static_dependencies/lark/parsers/lalr_parser_state.py +110 -0
- ccxt/static_dependencies/lark/parsers/xearley.py +165 -0
- ccxt/static_dependencies/lark/reconstruct.py +107 -0
- ccxt/static_dependencies/lark/tools/__init__.py +70 -0
- ccxt/static_dependencies/lark/tools/nearley.py +202 -0
- ccxt/static_dependencies/lark/tools/serialize.py +32 -0
- ccxt/static_dependencies/lark/tools/standalone.py +196 -0
- ccxt/static_dependencies/lark/tree.py +267 -0
- ccxt/static_dependencies/lark/tree_matcher.py +186 -0
- ccxt/static_dependencies/lark/tree_templates.py +180 -0
- ccxt/static_dependencies/lark/utils.py +343 -0
- ccxt/static_dependencies/lark/visitors.py +596 -0
- ccxt/static_dependencies/marshmallow/__init__.py +81 -0
- ccxt/static_dependencies/marshmallow/base.py +65 -0
- ccxt/static_dependencies/marshmallow/class_registry.py +94 -0
- ccxt/static_dependencies/marshmallow/decorators.py +231 -0
- ccxt/static_dependencies/marshmallow/error_store.py +60 -0
- ccxt/static_dependencies/marshmallow/exceptions.py +71 -0
- ccxt/static_dependencies/marshmallow/fields.py +2114 -0
- ccxt/static_dependencies/marshmallow/orderedset.py +89 -0
- ccxt/static_dependencies/marshmallow/schema.py +1228 -0
- ccxt/static_dependencies/marshmallow/types.py +12 -0
- ccxt/static_dependencies/marshmallow/utils.py +378 -0
- ccxt/static_dependencies/marshmallow/validate.py +678 -0
- ccxt/static_dependencies/marshmallow/warnings.py +2 -0
- ccxt/static_dependencies/marshmallow_dataclass/__init__.py +1047 -0
- ccxt/static_dependencies/marshmallow_dataclass/collection_field.py +51 -0
- ccxt/static_dependencies/marshmallow_dataclass/lazy_class_attribute.py +45 -0
- ccxt/static_dependencies/marshmallow_dataclass/mypy.py +71 -0
- ccxt/static_dependencies/marshmallow_dataclass/typing.py +14 -0
- ccxt/static_dependencies/marshmallow_dataclass/union_field.py +82 -0
- ccxt/static_dependencies/marshmallow_oneofschema/__init__.py +1 -0
- ccxt/static_dependencies/marshmallow_oneofschema/one_of_schema.py +193 -0
- ccxt/static_dependencies/msgpack/__init__.py +55 -0
- ccxt/static_dependencies/msgpack/exceptions.py +48 -0
- ccxt/static_dependencies/msgpack/ext.py +168 -0
- ccxt/static_dependencies/msgpack/fallback.py +951 -0
- ccxt/static_dependencies/parsimonious/__init__.py +10 -0
- ccxt/static_dependencies/parsimonious/exceptions.py +105 -0
- ccxt/static_dependencies/parsimonious/expressions.py +479 -0
- ccxt/static_dependencies/parsimonious/grammar.py +487 -0
- ccxt/static_dependencies/parsimonious/nodes.py +325 -0
- ccxt/static_dependencies/parsimonious/utils.py +40 -0
- ccxt/static_dependencies/starknet/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/data_types.py +123 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/cairo_types.py +77 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser.py +46 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser_transformer.py +138 -0
- ccxt/static_dependencies/starknet/cairo/felt.py +64 -0
- ccxt/static_dependencies/starknet/cairo/type_parser.py +121 -0
- ccxt/static_dependencies/starknet/cairo/v1/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/v1/type_parser.py +59 -0
- ccxt/static_dependencies/starknet/cairo/v2/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/v2/type_parser.py +77 -0
- ccxt/static_dependencies/starknet/ccxt_utils.py +7 -0
- ccxt/static_dependencies/starknet/common.py +15 -0
- ccxt/static_dependencies/starknet/constants.py +39 -0
- ccxt/static_dependencies/starknet/hash/__init__.py +0 -0
- ccxt/static_dependencies/starknet/hash/address.py +79 -0
- ccxt/static_dependencies/starknet/hash/compiled_class_hash_objects.py +111 -0
- ccxt/static_dependencies/starknet/hash/selector.py +16 -0
- ccxt/static_dependencies/starknet/hash/storage.py +12 -0
- ccxt/static_dependencies/starknet/hash/utils.py +78 -0
- ccxt/static_dependencies/starknet/models/__init__.py +0 -0
- ccxt/static_dependencies/starknet/models/typed_data.py +45 -0
- ccxt/static_dependencies/starknet/serialization/__init__.py +24 -0
- ccxt/static_dependencies/starknet/serialization/_calldata_reader.py +40 -0
- ccxt/static_dependencies/starknet/serialization/_context.py +142 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/__init__.py +10 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/_common.py +82 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/array_serializer.py +43 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/bool_serializer.py +37 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/byte_array_serializer.py +66 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/cairo_data_serializer.py +71 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/enum_serializer.py +71 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/felt_serializer.py +50 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/named_tuple_serializer.py +58 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/option_serializer.py +43 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/output_serializer.py +40 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/payload_serializer.py +72 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/struct_serializer.py +36 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/tuple_serializer.py +36 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/uint256_serializer.py +76 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/uint_serializer.py +100 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/unit_serializer.py +32 -0
- ccxt/static_dependencies/starknet/serialization/errors.py +10 -0
- ccxt/static_dependencies/starknet/serialization/factory.py +229 -0
- ccxt/static_dependencies/starknet/serialization/function_serialization_adapter.py +110 -0
- ccxt/static_dependencies/starknet/serialization/tuple_dataclass.py +59 -0
- ccxt/static_dependencies/starknet/utils/__init__.py +0 -0
- ccxt/static_dependencies/starknet/utils/constructor_args_translator.py +86 -0
- ccxt/static_dependencies/starknet/utils/iterable.py +13 -0
- ccxt/static_dependencies/starknet/utils/schema.py +13 -0
- ccxt/static_dependencies/starknet/utils/typed_data.py +182 -0
- ccxt/static_dependencies/starkware/__init__.py +0 -0
- ccxt/static_dependencies/starkware/crypto/__init__.py +0 -0
- ccxt/static_dependencies/starkware/crypto/fast_pedersen_hash.py +50 -0
- ccxt/static_dependencies/starkware/crypto/math_utils.py +78 -0
- ccxt/static_dependencies/starkware/crypto/signature.py +2344 -0
- ccxt/static_dependencies/starkware/crypto/utils.py +63 -0
- ccxt/static_dependencies/sympy/__init__.py +0 -0
- ccxt/static_dependencies/sympy/core/__init__.py +0 -0
- ccxt/static_dependencies/sympy/core/intfunc.py +35 -0
- ccxt/static_dependencies/sympy/external/__init__.py +0 -0
- ccxt/static_dependencies/sympy/external/gmpy.py +345 -0
- ccxt/static_dependencies/sympy/external/importtools.py +187 -0
- ccxt/static_dependencies/sympy/external/ntheory.py +637 -0
- ccxt/static_dependencies/sympy/external/pythonmpq.py +341 -0
- ccxt/static_dependencies/toolz/__init__.py +26 -0
- ccxt/static_dependencies/toolz/_signatures.py +784 -0
- ccxt/static_dependencies/toolz/_version.py +520 -0
- ccxt/static_dependencies/toolz/compatibility.py +30 -0
- ccxt/static_dependencies/toolz/curried/__init__.py +101 -0
- ccxt/static_dependencies/toolz/curried/exceptions.py +22 -0
- ccxt/static_dependencies/toolz/curried/operator.py +22 -0
- ccxt/static_dependencies/toolz/dicttoolz.py +339 -0
- ccxt/static_dependencies/toolz/functoolz.py +1049 -0
- ccxt/static_dependencies/toolz/itertoolz.py +1057 -0
- ccxt/static_dependencies/toolz/recipes.py +46 -0
- ccxt/static_dependencies/toolz/utils.py +9 -0
- ccxt/static_dependencies/typing_inspect/__init__.py +0 -0
- ccxt/static_dependencies/typing_inspect/typing_inspect.py +851 -0
- ccxt/tabdeal.py +364 -0
- ccxt/test/__init__.py +3 -0
- ccxt/test/base/__init__.py +29 -0
- ccxt/test/base/test_account.py +26 -0
- ccxt/test/base/test_balance.py +56 -0
- ccxt/test/base/test_borrow_interest.py +35 -0
- ccxt/test/base/test_borrow_rate.py +32 -0
- ccxt/test/base/test_calculate_fee.py +51 -0
- ccxt/test/base/test_crypto.py +127 -0
- ccxt/test/base/test_currency.py +76 -0
- ccxt/test/base/test_datetime.py +109 -0
- ccxt/test/base/test_decimal_to_precision.py +392 -0
- ccxt/test/base/test_deep_extend.py +68 -0
- ccxt/test/base/test_deposit_withdrawal.py +50 -0
- ccxt/test/base/test_exchange_datetime_functions.py +76 -0
- ccxt/test/base/test_funding_rate_history.py +29 -0
- ccxt/test/base/test_last_price.py +31 -0
- ccxt/test/base/test_ledger_entry.py +45 -0
- ccxt/test/base/test_ledger_item.py +48 -0
- ccxt/test/base/test_leverage_tier.py +33 -0
- ccxt/test/base/test_liquidation.py +50 -0
- ccxt/test/base/test_margin_mode.py +24 -0
- ccxt/test/base/test_margin_modification.py +35 -0
- ccxt/test/base/test_market.py +193 -0
- ccxt/test/base/test_number.py +411 -0
- ccxt/test/base/test_ohlcv.py +33 -0
- ccxt/test/base/test_open_interest.py +32 -0
- ccxt/test/base/test_order.py +64 -0
- ccxt/test/base/test_order_book.py +69 -0
- ccxt/test/base/test_position.py +60 -0
- ccxt/test/base/test_shared_methods.py +353 -0
- ccxt/test/base/test_status.py +24 -0
- ccxt/test/base/test_throttle.py +126 -0
- ccxt/test/base/test_ticker.py +92 -0
- ccxt/test/base/test_trade.py +47 -0
- ccxt/test/base/test_trading_fee.py +26 -0
- ccxt/test/base/test_transaction.py +39 -0
- ccxt/test/test_async.py +1649 -0
- ccxt/test/test_sync.py +1648 -0
- ccxt/test/tests_async.py +1558 -0
- ccxt/test/tests_helpers.py +287 -0
- ccxt/test/tests_init.py +39 -0
- ccxt/test/tests_sync.py +1555 -0
- ccxt/tetherland.py +349 -0
- ccxt/timex.py +1593 -0
- ccxt/tokocrypto.py +2405 -0
- ccxt/tradeogre.py +608 -0
- ccxt/twox.py +326 -0
- ccxt/ubitex.py +409 -0
- ccxt/upbit.py +1833 -0
- ccxt/vertex.py +2922 -0
- ccxt/wallex.py +445 -0
- ccxt/wavesexchange.py +2472 -0
- ccxt/wazirx.py +1224 -0
- ccxt/whitebit.py +2469 -0
- ccxt/woo.py +3114 -0
- ccxt/woofipro.py +2533 -0
- ccxt/xt.py +4453 -0
- ccxt/yobit.py +1283 -0
- ccxt/zaif.py +725 -0
- ccxt/zonda.py +1828 -0
- ccxt_ir-4.3.46.0.1.dist-info/LICENSE.txt +21 -0
- ccxt_ir-4.3.46.0.1.dist-info/METADATA +655 -0
- ccxt_ir-4.3.46.0.1.dist-info/RECORD +772 -0
- ccxt_ir-4.3.46.0.1.dist-info/WHEEL +6 -0
- ccxt_ir-4.3.46.0.1.dist-info/top_level.txt +1 -0
ccxt/pro/binance.py
ADDED
|
@@ -0,0 +1,3488 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
4
|
+
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
5
|
+
|
|
6
|
+
import ccxt.async_support
|
|
7
|
+
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
|
|
8
|
+
import hashlib
|
|
9
|
+
from ccxt.base.types import Balances, Int, Liquidation, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade
|
|
10
|
+
from ccxt.async_support.base.ws.client import Client
|
|
11
|
+
from typing import List
|
|
12
|
+
from ccxt.base.errors import ArgumentsRequired
|
|
13
|
+
from ccxt.base.errors import BadRequest
|
|
14
|
+
from ccxt.base.errors import NotSupported
|
|
15
|
+
from ccxt.base.errors import InvalidNonce
|
|
16
|
+
from ccxt.base.precise import Precise
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class binance(ccxt.async_support.binance):
|
|
20
|
+
|
|
21
|
+
def describe(self):
|
|
22
|
+
return self.deep_extend(super(binance, self).describe(), {
|
|
23
|
+
'has': {
|
|
24
|
+
'ws': True,
|
|
25
|
+
'watchBalance': True,
|
|
26
|
+
'watchLiquidations': True,
|
|
27
|
+
'watchLiquidationsForSymbols': True,
|
|
28
|
+
'watchMyLiquidations': True,
|
|
29
|
+
'watchMyLiquidationsForSymbols': True,
|
|
30
|
+
'watchBidsAsks': True,
|
|
31
|
+
'watchMyTrades': True,
|
|
32
|
+
'watchOHLCV': True,
|
|
33
|
+
'watchOHLCVForSymbols': False,
|
|
34
|
+
'watchOrderBook': True,
|
|
35
|
+
'watchOrderBookForSymbols': True,
|
|
36
|
+
'watchOrders': True,
|
|
37
|
+
'watchOrdersForSymbols': True,
|
|
38
|
+
'watchPositions': True,
|
|
39
|
+
'watchTicker': True,
|
|
40
|
+
'watchTickers': True,
|
|
41
|
+
'watchTrades': True,
|
|
42
|
+
'watchTradesForSymbols': True,
|
|
43
|
+
'createOrderWs': True,
|
|
44
|
+
'editOrderWs': True,
|
|
45
|
+
'cancelOrderWs': True,
|
|
46
|
+
'cancelOrdersWs': False,
|
|
47
|
+
'cancelAllOrdersWs': True,
|
|
48
|
+
'fetchBalanceWs': True,
|
|
49
|
+
'fetchDepositsWs': False,
|
|
50
|
+
'fetchMarketsWs': False,
|
|
51
|
+
'fetchMyTradesWs': True,
|
|
52
|
+
'fetchOHLCVWs': True,
|
|
53
|
+
'fetchOrderBookWs': True,
|
|
54
|
+
'fetchOpenOrdersWs': True,
|
|
55
|
+
'fetchOrderWs': True,
|
|
56
|
+
'fetchOrdersWs': True,
|
|
57
|
+
'fetchPositionWs': True,
|
|
58
|
+
'fetchPositionForSymbolWs': True,
|
|
59
|
+
'fetchPositionsWs': True,
|
|
60
|
+
'fetchTickerWs': True,
|
|
61
|
+
'fetchTradesWs': True,
|
|
62
|
+
'fetchTradingFeesWs': False,
|
|
63
|
+
'fetchWithdrawalsWs': False,
|
|
64
|
+
},
|
|
65
|
+
'urls': {
|
|
66
|
+
'test': {
|
|
67
|
+
'ws': {
|
|
68
|
+
'spot': 'wss://testnet.binance.vision/ws',
|
|
69
|
+
'margin': 'wss://testnet.binance.vision/ws',
|
|
70
|
+
'future': 'wss://fstream.binancefuture.com/ws',
|
|
71
|
+
'delivery': 'wss://dstream.binancefuture.com/ws',
|
|
72
|
+
'ws-api': {
|
|
73
|
+
'spot': 'wss://testnet.binance.vision/ws-api/v3',
|
|
74
|
+
'future': 'wss://testnet.binancefuture.com/ws-fapi/v1',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
'api': {
|
|
79
|
+
'ws': {
|
|
80
|
+
'spot': 'wss://stream.binance.com:9443/ws',
|
|
81
|
+
'margin': 'wss://stream.binance.com:9443/ws',
|
|
82
|
+
'future': 'wss://fstream.binance.com/ws',
|
|
83
|
+
'delivery': 'wss://dstream.binance.com/ws',
|
|
84
|
+
'ws-api': {
|
|
85
|
+
'spot': 'wss://ws-api.binance.com:443/ws-api/v3',
|
|
86
|
+
'future': 'wss://ws-fapi.binance.com/ws-fapi/v1',
|
|
87
|
+
},
|
|
88
|
+
'papi': 'wss://fstream.binance.com/pm/ws',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'streaming': {
|
|
93
|
+
'keepAlive': 180000,
|
|
94
|
+
},
|
|
95
|
+
'options': {
|
|
96
|
+
'returnRateLimits': False,
|
|
97
|
+
'streamLimits': {
|
|
98
|
+
'spot': 50, # max 1024
|
|
99
|
+
'margin': 50, # max 1024
|
|
100
|
+
'future': 50, # max 200
|
|
101
|
+
'delivery': 50, # max 200
|
|
102
|
+
},
|
|
103
|
+
'subscriptionLimitByStream': {
|
|
104
|
+
'spot': 200,
|
|
105
|
+
'margin': 200,
|
|
106
|
+
'future': 200,
|
|
107
|
+
'delivery': 200,
|
|
108
|
+
},
|
|
109
|
+
'streamBySubscriptionsHash': self.create_safe_dictionary(),
|
|
110
|
+
'streamIndex': -1,
|
|
111
|
+
# get updates every 1000ms or 100ms
|
|
112
|
+
# or every 0ms in real-time for futures
|
|
113
|
+
'watchOrderBookRate': 100,
|
|
114
|
+
'liquidationsLimit': 1000,
|
|
115
|
+
'myLiquidationsLimit': 1000,
|
|
116
|
+
'tradesLimit': 1000,
|
|
117
|
+
'ordersLimit': 1000,
|
|
118
|
+
'OHLCVLimit': 1000,
|
|
119
|
+
'requestId': self.create_safe_dictionary(),
|
|
120
|
+
'watchOrderBookLimit': 1000, # default limit
|
|
121
|
+
'watchTrades': {
|
|
122
|
+
'name': 'trade', # 'trade' or 'aggTrade'
|
|
123
|
+
},
|
|
124
|
+
'watchTicker': {
|
|
125
|
+
'name': 'ticker', # ticker or miniTicker or ticker_<window_size>
|
|
126
|
+
},
|
|
127
|
+
'watchTickers': {
|
|
128
|
+
'name': 'ticker', # ticker or miniTicker or ticker_<window_size>
|
|
129
|
+
},
|
|
130
|
+
'watchOHLCV': {
|
|
131
|
+
'name': 'kline', # or indexPriceKline or markPriceKline(coin-m futures)
|
|
132
|
+
},
|
|
133
|
+
'watchOrderBook': {
|
|
134
|
+
'maxRetries': 3,
|
|
135
|
+
},
|
|
136
|
+
'watchBalance': {
|
|
137
|
+
'fetchBalanceSnapshot': False, # or True
|
|
138
|
+
'awaitBalanceSnapshot': True, # whether to wait for the balance snapshot before providing updates
|
|
139
|
+
},
|
|
140
|
+
'watchLiquidationsForSymbols': {
|
|
141
|
+
'defaultType': 'swap',
|
|
142
|
+
},
|
|
143
|
+
'watchPositions': {
|
|
144
|
+
'fetchPositionsSnapshot': True, # or False
|
|
145
|
+
'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates
|
|
146
|
+
},
|
|
147
|
+
'wallet': 'wb', # wb = wallet balance, cw = cross balance
|
|
148
|
+
'listenKeyRefreshRate': 1200000, # 20 mins
|
|
149
|
+
'ws': {
|
|
150
|
+
'cost': 5,
|
|
151
|
+
},
|
|
152
|
+
'tickerChannelsMap': {
|
|
153
|
+
'24hrTicker': 'ticker',
|
|
154
|
+
'24hrMiniTicker': 'miniTicker',
|
|
155
|
+
# rolling window tickers
|
|
156
|
+
'1hTicker': 'ticker_1h',
|
|
157
|
+
'4hTicker': 'ticker_4h',
|
|
158
|
+
'1dTicker': 'ticker_1d',
|
|
159
|
+
'bookTicker': 'bookTicker',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
def request_id(self, url):
|
|
165
|
+
options = self.safe_dict(self.options, 'requestId', self.create_safe_dictionary())
|
|
166
|
+
previousValue = self.safe_integer(options, url, 0)
|
|
167
|
+
newValue = self.sum(previousValue, 1)
|
|
168
|
+
self.options['requestId'][url] = newValue
|
|
169
|
+
return newValue
|
|
170
|
+
|
|
171
|
+
def stream(self, type: Str, subscriptionHash: Str, numSubscriptions=1):
|
|
172
|
+
streamBySubscriptionsHash = self.safe_dict(self.options, 'streamBySubscriptionsHash', self.create_safe_dictionary())
|
|
173
|
+
stream = self.safe_string(streamBySubscriptionsHash, subscriptionHash)
|
|
174
|
+
if stream is None:
|
|
175
|
+
streamIndex = self.safe_integer(self.options, 'streamIndex', -1)
|
|
176
|
+
streamLimits = self.safe_value(self.options, 'streamLimits')
|
|
177
|
+
streamLimit = self.safe_integer(streamLimits, type)
|
|
178
|
+
streamIndex = streamIndex + 1
|
|
179
|
+
normalizedIndex = streamIndex % streamLimit
|
|
180
|
+
self.options['streamIndex'] = streamIndex
|
|
181
|
+
stream = self.number_to_string(normalizedIndex)
|
|
182
|
+
self.options['streamBySubscriptionsHash'][subscriptionHash] = stream
|
|
183
|
+
subscriptionsByStreams = self.safe_value(self.options, 'numSubscriptionsByStream')
|
|
184
|
+
if subscriptionsByStreams is None:
|
|
185
|
+
self.options['numSubscriptionsByStream'] = self.create_safe_dictionary()
|
|
186
|
+
subscriptionsByStream = self.safe_integer(self.options['numSubscriptionsByStream'], stream, 0)
|
|
187
|
+
newNumSubscriptions = subscriptionsByStream + numSubscriptions
|
|
188
|
+
subscriptionLimitByStream = self.safe_integer(self.options['subscriptionLimitByStream'], type, 200)
|
|
189
|
+
if newNumSubscriptions > subscriptionLimitByStream:
|
|
190
|
+
raise BadRequest(self.id + ' reached the limit of subscriptions by stream. Increase the number of streams, or increase the stream limit or subscription limit by stream if the exchange allows.')
|
|
191
|
+
self.options['numSubscriptionsByStream'][stream] = subscriptionsByStream + numSubscriptions
|
|
192
|
+
return stream
|
|
193
|
+
|
|
194
|
+
async def watch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
|
|
195
|
+
"""
|
|
196
|
+
watch the public liquidations of a trading pair
|
|
197
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#liquidation-order-streams
|
|
198
|
+
:see: https://binance-docs.github.io/apidocs/delivery/en/#liquidation-order-streams
|
|
199
|
+
:param str symbol: unified CCXT market symbol
|
|
200
|
+
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
201
|
+
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
202
|
+
:param dict [params]: exchange specific parameters for the bitmex api endpoint
|
|
203
|
+
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
|
|
204
|
+
"""
|
|
205
|
+
return self.watch_liquidations_for_symbols([symbol], since, limit, params)
|
|
206
|
+
|
|
207
|
+
async def watch_liquidations_for_symbols(self, symbols: List[str] = None, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
|
|
208
|
+
"""
|
|
209
|
+
watch the public liquidations of a trading pair
|
|
210
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#all-market-liquidation-order-streams
|
|
211
|
+
:see: https://binance-docs.github.io/apidocs/delivery/en/#all-market-liquidation-order-streams
|
|
212
|
+
:param str symbol: unified CCXT market symbol
|
|
213
|
+
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
214
|
+
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
215
|
+
:param dict [params]: exchange specific parameters for the bitmex api endpoint
|
|
216
|
+
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
|
|
217
|
+
"""
|
|
218
|
+
await self.load_markets()
|
|
219
|
+
subscriptionHashes = []
|
|
220
|
+
messageHashes = []
|
|
221
|
+
streamHash = 'liquidations'
|
|
222
|
+
symbols = self.market_symbols(symbols, None, True, True)
|
|
223
|
+
if self.is_empty(symbols):
|
|
224
|
+
subscriptionHashes.append('!' + 'forceOrder@arr')
|
|
225
|
+
messageHashes.append('liquidations')
|
|
226
|
+
else:
|
|
227
|
+
for i in range(0, len(symbols)):
|
|
228
|
+
market = self.market(symbols[i])
|
|
229
|
+
subscriptionHashes.append(market['id'] + '@forceOrder')
|
|
230
|
+
messageHashes.append('liquidations::' + symbols[i])
|
|
231
|
+
streamHash += '::' + ','.join(symbols)
|
|
232
|
+
firstMarket = self.get_market_from_symbols(symbols)
|
|
233
|
+
type = None
|
|
234
|
+
type, params = self.handle_market_type_and_params('watchLiquidationsForSymbols', firstMarket, params)
|
|
235
|
+
if type == 'spot':
|
|
236
|
+
raise BadRequest(self.id + 'watchLiquidationsForSymbols is not supported for swap symbols')
|
|
237
|
+
subType = None
|
|
238
|
+
subType, params = self.handle_sub_type_and_params('watchLiquidationsForSymbols', firstMarket, params)
|
|
239
|
+
if self.isLinear(type, subType):
|
|
240
|
+
type = 'future'
|
|
241
|
+
elif self.isInverse(type, subType):
|
|
242
|
+
type = 'delivery'
|
|
243
|
+
numSubscriptions = len(subscriptionHashes)
|
|
244
|
+
url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, numSubscriptions)
|
|
245
|
+
requestId = self.request_id(url)
|
|
246
|
+
request = {
|
|
247
|
+
'method': 'SUBSCRIBE',
|
|
248
|
+
'params': subscriptionHashes,
|
|
249
|
+
'id': requestId,
|
|
250
|
+
}
|
|
251
|
+
subscribe = {
|
|
252
|
+
'id': requestId,
|
|
253
|
+
}
|
|
254
|
+
newLiquidations = await self.watch_multiple(url, messageHashes, self.extend(request, params), subscriptionHashes, subscribe)
|
|
255
|
+
if self.newUpdates:
|
|
256
|
+
return newLiquidations
|
|
257
|
+
return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit, True)
|
|
258
|
+
|
|
259
|
+
def handle_liquidation(self, client: Client, message):
|
|
260
|
+
#
|
|
261
|
+
# future
|
|
262
|
+
# {
|
|
263
|
+
# "e":"forceOrder",
|
|
264
|
+
# "E":1698871323061,
|
|
265
|
+
# "o":{
|
|
266
|
+
# "s":"BTCUSDT",
|
|
267
|
+
# "S":"BUY",
|
|
268
|
+
# "o":"LIMIT",
|
|
269
|
+
# "f":"IOC",
|
|
270
|
+
# "q":"1.437",
|
|
271
|
+
# "p":"35100.81",
|
|
272
|
+
# "ap":"34959.70",
|
|
273
|
+
# "X":"FILLED",
|
|
274
|
+
# "l":"1.437",
|
|
275
|
+
# "z":"1.437",
|
|
276
|
+
# "T":1698871323059
|
|
277
|
+
# }
|
|
278
|
+
# }
|
|
279
|
+
# delivery
|
|
280
|
+
# {
|
|
281
|
+
# "e":"forceOrder", # Event Type
|
|
282
|
+
# "E": 1591154240950, # Event Time
|
|
283
|
+
# "o":{
|
|
284
|
+
# "s":"BTCUSD_200925", # Symbol
|
|
285
|
+
# "ps": "BTCUSD", # Pair
|
|
286
|
+
# "S":"SELL", # Side
|
|
287
|
+
# "o":"LIMIT", # Order Type
|
|
288
|
+
# "f":"IOC", # Time in Force
|
|
289
|
+
# "q":"1", # Original Quantity
|
|
290
|
+
# "p":"9425.5", # Price
|
|
291
|
+
# "ap":"9496.5", # Average Price
|
|
292
|
+
# "X":"FILLED", # Order Status
|
|
293
|
+
# "l":"1", # Order Last Filled Quantity
|
|
294
|
+
# "z":"1", # Order Filled Accumulated Quantity
|
|
295
|
+
# "T": 1591154240949, # Order Trade Time
|
|
296
|
+
# }
|
|
297
|
+
# }
|
|
298
|
+
#
|
|
299
|
+
rawLiquidation = self.safe_value(message, 'o', {})
|
|
300
|
+
marketId = self.safe_string(rawLiquidation, 's')
|
|
301
|
+
market = self.safe_market(marketId, None, '', 'contract')
|
|
302
|
+
symbol = market['symbol']
|
|
303
|
+
liquidation = self.parse_ws_liquidation(rawLiquidation, market)
|
|
304
|
+
liquidations = self.safe_value(self.liquidations, symbol)
|
|
305
|
+
if liquidations is None:
|
|
306
|
+
limit = self.safe_integer(self.options, 'liquidationsLimit', 1000)
|
|
307
|
+
liquidations = ArrayCache(limit)
|
|
308
|
+
liquidations.append(liquidation)
|
|
309
|
+
self.liquidations[symbol] = liquidations
|
|
310
|
+
client.resolve([liquidation], 'liquidations')
|
|
311
|
+
client.resolve([liquidation], 'liquidations::' + symbol)
|
|
312
|
+
|
|
313
|
+
def parse_ws_liquidation(self, liquidation, market=None):
|
|
314
|
+
#
|
|
315
|
+
# future
|
|
316
|
+
# {
|
|
317
|
+
# "s":"BTCUSDT",
|
|
318
|
+
# "S":"BUY",
|
|
319
|
+
# "o":"LIMIT",
|
|
320
|
+
# "f":"IOC",
|
|
321
|
+
# "q":"1.437",
|
|
322
|
+
# "p":"35100.81",
|
|
323
|
+
# "ap":"34959.70",
|
|
324
|
+
# "X":"FILLED",
|
|
325
|
+
# "l":"1.437",
|
|
326
|
+
# "z":"1.437",
|
|
327
|
+
# "T":1698871323059
|
|
328
|
+
# }
|
|
329
|
+
# delivery
|
|
330
|
+
# {
|
|
331
|
+
# "s":"BTCUSD_200925", # Symbol
|
|
332
|
+
# "ps": "BTCUSD", # Pair
|
|
333
|
+
# "S":"SELL", # Side
|
|
334
|
+
# "o":"LIMIT", # Order Type
|
|
335
|
+
# "f":"IOC", # Time in Force
|
|
336
|
+
# "q":"1", # Original Quantity
|
|
337
|
+
# "p":"9425.5", # Price
|
|
338
|
+
# "ap":"9496.5", # Average Price
|
|
339
|
+
# "X":"FILLED", # Order Status
|
|
340
|
+
# "l":"1", # Order Last Filled Quantity
|
|
341
|
+
# "z":"1", # Order Filled Accumulated Quantity
|
|
342
|
+
# "T": 1591154240949, # Order Trade Time
|
|
343
|
+
# }
|
|
344
|
+
# myLiquidation
|
|
345
|
+
# {
|
|
346
|
+
# "s":"BTCUSDT", # Symbol
|
|
347
|
+
# "c":"TEST", # Client Order Id
|
|
348
|
+
# # special client order id:
|
|
349
|
+
# # starts with "autoclose-": liquidation order
|
|
350
|
+
# # "adl_autoclose": ADL auto close order
|
|
351
|
+
# # "settlement_autoclose-": settlement order for delisting or delivery
|
|
352
|
+
# "S":"SELL", # Side
|
|
353
|
+
# "o":"TRAILING_STOP_MARKET", # Order Type
|
|
354
|
+
# "f":"GTC", # Time in Force
|
|
355
|
+
# "q":"0.001", # Original Quantity
|
|
356
|
+
# "p":"0", # Original Price
|
|
357
|
+
# "ap":"0", # Average Price
|
|
358
|
+
# "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order
|
|
359
|
+
# "x":"NEW", # Execution Type
|
|
360
|
+
# "X":"NEW", # Order Status
|
|
361
|
+
# "i":8886774, # Order Id
|
|
362
|
+
# "l":"0", # Order Last Filled Quantity
|
|
363
|
+
# "z":"0", # Order Filled Accumulated Quantity
|
|
364
|
+
# "L":"0", # Last Filled Price
|
|
365
|
+
# "N":"USDT", # Commission Asset, will not push if no commission
|
|
366
|
+
# "n":"0", # Commission, will not push if no commission
|
|
367
|
+
# "T":1568879465650, # Order Trade Time
|
|
368
|
+
# "t":0, # Trade Id
|
|
369
|
+
# "b":"0", # Bids Notional
|
|
370
|
+
# "a":"9.91", # Ask Notional
|
|
371
|
+
# "m":false, # Is self trade the maker side?
|
|
372
|
+
# "R":false, # Is self reduce only
|
|
373
|
+
# "wt":"CONTRACT_PRICE", # Stop Price Working Type
|
|
374
|
+
# "ot":"TRAILING_STOP_MARKET",// Original Order Type
|
|
375
|
+
# "ps":"LONG", # Position Side
|
|
376
|
+
# "cp":false, # If Close-All, pushed with conditional order
|
|
377
|
+
# "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order
|
|
378
|
+
# "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
|
379
|
+
# "pP": False, # If price protection is turned on
|
|
380
|
+
# "si": 0, # ignore
|
|
381
|
+
# "ss": 0, # ignore
|
|
382
|
+
# "rp":"0", # Realized Profit of the trade
|
|
383
|
+
# "V":"EXPIRE_TAKER", # STP mode
|
|
384
|
+
# "pm":"OPPONENT", # Price match mode
|
|
385
|
+
# "gtd":0 # TIF GTD order auto cancel time
|
|
386
|
+
# }
|
|
387
|
+
#
|
|
388
|
+
marketId = self.safe_string(liquidation, 's')
|
|
389
|
+
market = self.safe_market(marketId, market)
|
|
390
|
+
timestamp = self.safe_integer(liquidation, 'T')
|
|
391
|
+
return self.safe_liquidation({
|
|
392
|
+
'info': liquidation,
|
|
393
|
+
'symbol': self.safe_symbol(marketId, market),
|
|
394
|
+
'contracts': self.safe_number(liquidation, 'l'),
|
|
395
|
+
'contractSize': self.safe_number(market, 'contractSize'),
|
|
396
|
+
'price': self.safe_number(liquidation, 'ap'),
|
|
397
|
+
'baseValue': None,
|
|
398
|
+
'quoteValue': None,
|
|
399
|
+
'timestamp': timestamp,
|
|
400
|
+
'datetime': self.iso8601(timestamp),
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
async def watch_my_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
|
|
404
|
+
"""
|
|
405
|
+
watch the private liquidations of a trading pair
|
|
406
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#event-order-update
|
|
407
|
+
:see: https://binance-docs.github.io/apidocs/delivery/en/#event-order-update
|
|
408
|
+
:param str symbol: unified CCXT market symbol
|
|
409
|
+
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
410
|
+
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
411
|
+
:param dict [params]: exchange specific parameters for the bitmex api endpoint
|
|
412
|
+
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
|
|
413
|
+
"""
|
|
414
|
+
return self.watch_my_liquidations_for_symbols([symbol], since, limit, params)
|
|
415
|
+
|
|
416
|
+
async def watch_my_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
|
|
417
|
+
"""
|
|
418
|
+
watch the private liquidations of a trading pair
|
|
419
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#event-order-update
|
|
420
|
+
:see: https://binance-docs.github.io/apidocs/delivery/en/#event-order-update
|
|
421
|
+
:param str symbol: unified CCXT market symbol
|
|
422
|
+
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
423
|
+
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
424
|
+
:param dict [params]: exchange specific parameters for the bitmex api endpoint
|
|
425
|
+
:returns dict: an array of `liquidation structures <https://github.com/ccxt/ccxt/wiki/Manual#liquidation-structure>`
|
|
426
|
+
"""
|
|
427
|
+
await self.load_markets()
|
|
428
|
+
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
429
|
+
market = self.get_market_from_symbols(symbols)
|
|
430
|
+
messageHashes = ['myLiquidations']
|
|
431
|
+
if not self.is_empty(symbols):
|
|
432
|
+
for i in range(0, len(symbols)):
|
|
433
|
+
symbol = symbols[i]
|
|
434
|
+
messageHashes.append('myLiquidations::' + symbol)
|
|
435
|
+
type = None
|
|
436
|
+
type, params = self.handle_market_type_and_params('watchMyLiquidationsForSymbols', market, params)
|
|
437
|
+
subType = None
|
|
438
|
+
subType, params = self.handle_sub_type_and_params('watchMyLiquidationsForSymbols', market, params)
|
|
439
|
+
if self.isLinear(type, subType):
|
|
440
|
+
type = 'future'
|
|
441
|
+
elif self.isInverse(type, subType):
|
|
442
|
+
type = 'delivery'
|
|
443
|
+
await self.authenticate(params)
|
|
444
|
+
url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey']
|
|
445
|
+
message = None
|
|
446
|
+
newLiquidations = await self.watch_multiple(url, messageHashes, message, [type])
|
|
447
|
+
if self.newUpdates:
|
|
448
|
+
return newLiquidations
|
|
449
|
+
return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit)
|
|
450
|
+
|
|
451
|
+
def handle_my_liquidation(self, client: Client, message):
|
|
452
|
+
#
|
|
453
|
+
# {
|
|
454
|
+
# "s":"BTCUSDT", # Symbol
|
|
455
|
+
# "c":"TEST", # Client Order Id
|
|
456
|
+
# # special client order id:
|
|
457
|
+
# # starts with "autoclose-": liquidation order
|
|
458
|
+
# # "adl_autoclose": ADL auto close order
|
|
459
|
+
# # "settlement_autoclose-": settlement order for delisting or delivery
|
|
460
|
+
# "S":"SELL", # Side
|
|
461
|
+
# "o":"TRAILING_STOP_MARKET", # Order Type
|
|
462
|
+
# "f":"GTC", # Time in Force
|
|
463
|
+
# "q":"0.001", # Original Quantity
|
|
464
|
+
# "p":"0", # Original Price
|
|
465
|
+
# "ap":"0", # Average Price
|
|
466
|
+
# "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order
|
|
467
|
+
# "x":"NEW", # Execution Type
|
|
468
|
+
# "X":"NEW", # Order Status
|
|
469
|
+
# "i":8886774, # Order Id
|
|
470
|
+
# "l":"0", # Order Last Filled Quantity
|
|
471
|
+
# "z":"0", # Order Filled Accumulated Quantity
|
|
472
|
+
# "L":"0", # Last Filled Price
|
|
473
|
+
# "N":"USDT", # Commission Asset, will not push if no commission
|
|
474
|
+
# "n":"0", # Commission, will not push if no commission
|
|
475
|
+
# "T":1568879465650, # Order Trade Time
|
|
476
|
+
# "t":0, # Trade Id
|
|
477
|
+
# "b":"0", # Bids Notional
|
|
478
|
+
# "a":"9.91", # Ask Notional
|
|
479
|
+
# "m":false, # Is self trade the maker side?
|
|
480
|
+
# "R":false, # Is self reduce only
|
|
481
|
+
# "wt":"CONTRACT_PRICE", # Stop Price Working Type
|
|
482
|
+
# "ot":"TRAILING_STOP_MARKET",// Original Order Type
|
|
483
|
+
# "ps":"LONG", # Position Side
|
|
484
|
+
# "cp":false, # If Close-All, pushed with conditional order
|
|
485
|
+
# "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order
|
|
486
|
+
# "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
|
487
|
+
# "pP": False, # If price protection is turned on
|
|
488
|
+
# "si": 0, # ignore
|
|
489
|
+
# "ss": 0, # ignore
|
|
490
|
+
# "rp":"0", # Realized Profit of the trade
|
|
491
|
+
# "V":"EXPIRE_TAKER", # STP mode
|
|
492
|
+
# "pm":"OPPONENT", # Price match mode
|
|
493
|
+
# "gtd":0 # TIF GTD order auto cancel time
|
|
494
|
+
# }
|
|
495
|
+
#
|
|
496
|
+
orderType = self.safe_string(message, 'o')
|
|
497
|
+
if orderType != 'LIQUIDATION':
|
|
498
|
+
return
|
|
499
|
+
marketId = self.safe_string(message, 's')
|
|
500
|
+
market = self.safe_market(marketId)
|
|
501
|
+
symbol = self.safe_symbol(marketId)
|
|
502
|
+
liquidation = self.parse_ws_liquidation(message, market)
|
|
503
|
+
myLiquidations = self.safe_value(self.myLiquidations, symbol)
|
|
504
|
+
if myLiquidations is None:
|
|
505
|
+
limit = self.safe_integer(self.options, 'myLiquidationsLimit', 1000)
|
|
506
|
+
myLiquidations = ArrayCache(limit)
|
|
507
|
+
myLiquidations.append(liquidation)
|
|
508
|
+
self.myLiquidations[symbol] = myLiquidations
|
|
509
|
+
client.resolve([liquidation], 'myLiquidations')
|
|
510
|
+
client.resolve([liquidation], 'myLiquidations::' + symbol)
|
|
511
|
+
|
|
512
|
+
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
513
|
+
"""
|
|
514
|
+
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
515
|
+
:param str symbol: unified symbol of the market to fetch the order book for
|
|
516
|
+
:param int [limit]: the maximum amount of order book entries to return
|
|
517
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
518
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
519
|
+
"""
|
|
520
|
+
#
|
|
521
|
+
# todo add support for <levels>-snapshots(depth)
|
|
522
|
+
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams # <symbol>@depth<levels>@100ms or <symbol>@depth<levels>(1000ms)
|
|
523
|
+
# valid <levels> are 5, 10, or 20
|
|
524
|
+
#
|
|
525
|
+
# default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000
|
|
526
|
+
#
|
|
527
|
+
# notice the differences between trading futures and spot trading
|
|
528
|
+
# the algorithms use different urls in step 1
|
|
529
|
+
# delta caching and merging also differs in steps 4, 5, 6
|
|
530
|
+
#
|
|
531
|
+
# spot/margin
|
|
532
|
+
# https://binance-docs.github.io/apidocs/spot/en/#how-to-manage-a-local-order-book-correctly
|
|
533
|
+
#
|
|
534
|
+
# 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth.
|
|
535
|
+
# 2. Buffer the events you receive from the stream.
|
|
536
|
+
# 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 .
|
|
537
|
+
# 4. Drop any event where u is <= lastUpdateId in the snapshot.
|
|
538
|
+
# 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1.
|
|
539
|
+
# 6. While listening to the stream, each new event's U should be equal to the previous event's u+1.
|
|
540
|
+
# 7. The data in each event is the absolute quantity for a price level.
|
|
541
|
+
# 8. If the quantity is 0, remove the price level.
|
|
542
|
+
# 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal.
|
|
543
|
+
#
|
|
544
|
+
# futures
|
|
545
|
+
# https://binance-docs.github.io/apidocs/futures/en/#how-to-manage-a-local-order-book-correctly
|
|
546
|
+
#
|
|
547
|
+
# 1. Open a stream to wss://fstream.binance.com/stream?streams=btcusdt@depth.
|
|
548
|
+
# 2. Buffer the events you receive from the stream. For same price, latest received update covers the previous one.
|
|
549
|
+
# 3. Get a depth snapshot from https://fapi.binance.com/fapi/v1/depth?symbol=BTCUSDT&limit=1000 .
|
|
550
|
+
# 4. Drop any event where u is < lastUpdateId in the snapshot.
|
|
551
|
+
# 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
|
|
552
|
+
# 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3.
|
|
553
|
+
# 7. The data in each event is the absolute quantity for a price level.
|
|
554
|
+
# 8. If the quantity is 0, remove the price level.
|
|
555
|
+
# 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal.
|
|
556
|
+
#
|
|
557
|
+
return await self.watch_order_book_for_symbols([symbol], limit, params)
|
|
558
|
+
|
|
559
|
+
async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
|
|
560
|
+
"""
|
|
561
|
+
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
562
|
+
:param str[] symbols: unified array of symbols
|
|
563
|
+
:param int [limit]: the maximum amount of order book entries to return
|
|
564
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
565
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
566
|
+
"""
|
|
567
|
+
await self.load_markets()
|
|
568
|
+
symbols = self.market_symbols(symbols, None, False, True, True)
|
|
569
|
+
firstMarket = self.market(symbols[0])
|
|
570
|
+
type = firstMarket['type']
|
|
571
|
+
if firstMarket['contract']:
|
|
572
|
+
type = 'future' if firstMarket['linear'] else 'delivery'
|
|
573
|
+
name = 'depth'
|
|
574
|
+
streamHash = 'multipleOrderbook'
|
|
575
|
+
if symbols is not None:
|
|
576
|
+
symbolsLength = len(symbols)
|
|
577
|
+
if symbolsLength > 200:
|
|
578
|
+
raise BadRequest(self.id + ' watchOrderBookForSymbols() accepts 200 symbols at most. To watch more symbols call watchOrderBookForSymbols() multiple times')
|
|
579
|
+
streamHash += '::' + ','.join(symbols)
|
|
580
|
+
watchOrderBookRate = self.safe_string(self.options, 'watchOrderBookRate', '100')
|
|
581
|
+
subParams = []
|
|
582
|
+
messageHashes = []
|
|
583
|
+
for i in range(0, len(symbols)):
|
|
584
|
+
symbol = symbols[i]
|
|
585
|
+
market = self.market(symbol)
|
|
586
|
+
messageHash = market['lowercaseId'] + '@' + name
|
|
587
|
+
messageHashes.append(messageHash)
|
|
588
|
+
symbolHash = messageHash + '@' + watchOrderBookRate + 'ms'
|
|
589
|
+
subParams.append(symbolHash)
|
|
590
|
+
messageHashesLength = len(messageHashes)
|
|
591
|
+
url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, messageHashesLength)
|
|
592
|
+
requestId = self.request_id(url)
|
|
593
|
+
request: dict = {
|
|
594
|
+
'method': 'SUBSCRIBE',
|
|
595
|
+
'params': subParams,
|
|
596
|
+
'id': requestId,
|
|
597
|
+
}
|
|
598
|
+
subscription: dict = {
|
|
599
|
+
'id': str(requestId),
|
|
600
|
+
'name': name,
|
|
601
|
+
'symbols': symbols,
|
|
602
|
+
'method': self.handle_order_book_subscription,
|
|
603
|
+
'limit': limit,
|
|
604
|
+
'type': type,
|
|
605
|
+
'params': params,
|
|
606
|
+
}
|
|
607
|
+
message = self.extend(request, params)
|
|
608
|
+
orderbook = await self.watch_multiple(url, messageHashes, message, messageHashes, subscription)
|
|
609
|
+
return orderbook.limit()
|
|
610
|
+
|
|
611
|
+
async def fetch_order_book_ws(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
612
|
+
"""
|
|
613
|
+
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
614
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#order-book-2
|
|
615
|
+
:param str symbol: unified symbol of the market to fetch the order book for
|
|
616
|
+
:param int [limit]: the maximum amount of order book entries to return
|
|
617
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
618
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
619
|
+
"""
|
|
620
|
+
await self.load_markets()
|
|
621
|
+
market = self.market(symbol)
|
|
622
|
+
payload: dict = {
|
|
623
|
+
'symbol': market['id'],
|
|
624
|
+
}
|
|
625
|
+
if limit is not None:
|
|
626
|
+
payload['limit'] = limit
|
|
627
|
+
marketType = self.get_market_type('fetchOrderBookWs', market, params)
|
|
628
|
+
if marketType != 'future':
|
|
629
|
+
raise BadRequest(self.id + ' fetchOrderBookWs only supports swap markets')
|
|
630
|
+
url = self.urls['api']['ws']['ws-api'][marketType]
|
|
631
|
+
requestId = self.request_id(url)
|
|
632
|
+
messageHash = str(requestId)
|
|
633
|
+
returnRateLimits = False
|
|
634
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'createOrderWs', 'returnRateLimits', False)
|
|
635
|
+
payload['returnRateLimits'] = returnRateLimits
|
|
636
|
+
params = self.omit(params, 'test')
|
|
637
|
+
message: dict = {
|
|
638
|
+
'id': messageHash,
|
|
639
|
+
'method': 'depth',
|
|
640
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
641
|
+
}
|
|
642
|
+
subscription: dict = {
|
|
643
|
+
'method': self.handle_fetch_order_book,
|
|
644
|
+
}
|
|
645
|
+
orderbook = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
646
|
+
orderbook['symbol'] = market['symbol']
|
|
647
|
+
return orderbook
|
|
648
|
+
|
|
649
|
+
def handle_fetch_order_book(self, client: Client, message):
|
|
650
|
+
#
|
|
651
|
+
# {
|
|
652
|
+
# "id":"51e2affb-0aba-4821-ba75-f2625006eb43",
|
|
653
|
+
# "status":200,
|
|
654
|
+
# "result":{
|
|
655
|
+
# "lastUpdateId":1027024,
|
|
656
|
+
# "E":1589436922972,
|
|
657
|
+
# "T":1589436922959,
|
|
658
|
+
# "bids":[
|
|
659
|
+
# [
|
|
660
|
+
# "4.00000000",
|
|
661
|
+
# "431.00000000"
|
|
662
|
+
# ]
|
|
663
|
+
# ],
|
|
664
|
+
# "asks":[
|
|
665
|
+
# [
|
|
666
|
+
# "4.00000200",
|
|
667
|
+
# "12.00000000"
|
|
668
|
+
# ]
|
|
669
|
+
# ]
|
|
670
|
+
# }
|
|
671
|
+
# }
|
|
672
|
+
#
|
|
673
|
+
messageHash = self.safe_string(message, 'id')
|
|
674
|
+
result = self.safe_dict(message, 'result')
|
|
675
|
+
timestamp = self.safe_integer(result, 'T')
|
|
676
|
+
orderbook = self.parse_order_book(result, None, timestamp)
|
|
677
|
+
orderbook['nonce'] = self.safe_integer_2(result, 'lastUpdateId', 'u')
|
|
678
|
+
client.resolve(orderbook, messageHash)
|
|
679
|
+
|
|
680
|
+
async def fetch_order_book_snapshot(self, client, message, subscription):
|
|
681
|
+
name = self.safe_string(subscription, 'name')
|
|
682
|
+
symbol = self.safe_string(subscription, 'symbol')
|
|
683
|
+
market = self.market(symbol)
|
|
684
|
+
messageHash = market['lowercaseId'] + '@' + name
|
|
685
|
+
try:
|
|
686
|
+
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
|
|
687
|
+
type = self.safe_value(subscription, 'type')
|
|
688
|
+
limit = self.safe_integer(subscription, 'limit', defaultLimit)
|
|
689
|
+
params = self.safe_value(subscription, 'params')
|
|
690
|
+
# 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 .
|
|
691
|
+
# todo: self is a synch blocking call - make it async
|
|
692
|
+
# default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000
|
|
693
|
+
snapshot = await self.fetch_rest_order_book_safe(symbol, limit, params)
|
|
694
|
+
if self.safe_value(self.orderbooks, symbol) is None:
|
|
695
|
+
# if the orderbook is dropped before the snapshot is received
|
|
696
|
+
return
|
|
697
|
+
orderbook = self.orderbooks[symbol]
|
|
698
|
+
orderbook.reset(snapshot)
|
|
699
|
+
# unroll the accumulated deltas
|
|
700
|
+
messages = orderbook.cache
|
|
701
|
+
for i in range(0, len(messages)):
|
|
702
|
+
messageItem = messages[i]
|
|
703
|
+
U = self.safe_integer(messageItem, 'U')
|
|
704
|
+
u = self.safe_integer(messageItem, 'u')
|
|
705
|
+
pu = self.safe_integer(messageItem, 'pu')
|
|
706
|
+
if type == 'future':
|
|
707
|
+
# 4. Drop any event where u is < lastUpdateId in the snapshot
|
|
708
|
+
if u < orderbook['nonce']:
|
|
709
|
+
continue
|
|
710
|
+
# 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
|
|
711
|
+
if (U <= orderbook['nonce']) and (u >= orderbook['nonce']) or (pu == orderbook['nonce']):
|
|
712
|
+
self.handle_order_book_message(client, messageItem, orderbook)
|
|
713
|
+
else:
|
|
714
|
+
# 4. Drop any event where u is <= lastUpdateId in the snapshot
|
|
715
|
+
if u <= orderbook['nonce']:
|
|
716
|
+
continue
|
|
717
|
+
# 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1
|
|
718
|
+
if ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce']):
|
|
719
|
+
self.handle_order_book_message(client, messageItem, orderbook)
|
|
720
|
+
self.orderbooks[symbol] = orderbook
|
|
721
|
+
client.resolve(orderbook, messageHash)
|
|
722
|
+
except Exception as e:
|
|
723
|
+
del client.subscriptions[messageHash]
|
|
724
|
+
client.reject(e, messageHash)
|
|
725
|
+
|
|
726
|
+
def handle_delta(self, bookside, delta):
|
|
727
|
+
price = self.safe_float(delta, 0)
|
|
728
|
+
amount = self.safe_float(delta, 1)
|
|
729
|
+
bookside.store(price, amount)
|
|
730
|
+
|
|
731
|
+
def handle_deltas(self, bookside, deltas):
|
|
732
|
+
for i in range(0, len(deltas)):
|
|
733
|
+
self.handle_delta(bookside, deltas[i])
|
|
734
|
+
|
|
735
|
+
def handle_order_book_message(self, client: Client, message, orderbook):
|
|
736
|
+
u = self.safe_integer(message, 'u')
|
|
737
|
+
self.handle_deltas(orderbook['asks'], self.safe_value(message, 'a', []))
|
|
738
|
+
self.handle_deltas(orderbook['bids'], self.safe_value(message, 'b', []))
|
|
739
|
+
orderbook['nonce'] = u
|
|
740
|
+
timestamp = self.safe_integer(message, 'E')
|
|
741
|
+
orderbook['timestamp'] = timestamp
|
|
742
|
+
orderbook['datetime'] = self.iso8601(timestamp)
|
|
743
|
+
return orderbook
|
|
744
|
+
|
|
745
|
+
def handle_order_book(self, client: Client, message):
|
|
746
|
+
#
|
|
747
|
+
# initial snapshot is fetched with ccxt's fetchOrderBook
|
|
748
|
+
# the feed does not include a snapshot, just the deltas
|
|
749
|
+
#
|
|
750
|
+
# {
|
|
751
|
+
# "e": "depthUpdate", # Event type
|
|
752
|
+
# "E": 1577554482280, # Event time
|
|
753
|
+
# "s": "BNBBTC", # Symbol
|
|
754
|
+
# "U": 157, # First update ID in event
|
|
755
|
+
# "u": 160, # Final update ID in event
|
|
756
|
+
# "b": [ # bids
|
|
757
|
+
# ["0.0024", "10"], # price, size
|
|
758
|
+
# ],
|
|
759
|
+
# "a": [ # asks
|
|
760
|
+
# ["0.0026", "100"], # price, size
|
|
761
|
+
# ]
|
|
762
|
+
# }
|
|
763
|
+
#
|
|
764
|
+
isTestnetSpot = client.url.find('testnet') > 0
|
|
765
|
+
isSpotMainNet = client.url.find('/stream.binance.') > 0
|
|
766
|
+
isSpot = isTestnetSpot or isSpotMainNet
|
|
767
|
+
marketType = 'spot' if isSpot else 'contract'
|
|
768
|
+
marketId = self.safe_string(message, 's')
|
|
769
|
+
market = self.safe_market(marketId, None, None, marketType)
|
|
770
|
+
symbol = market['symbol']
|
|
771
|
+
name = 'depth'
|
|
772
|
+
messageHash = market['lowercaseId'] + '@' + name
|
|
773
|
+
if not (symbol in self.orderbooks):
|
|
774
|
+
#
|
|
775
|
+
# https://github.com/ccxt/ccxt/issues/6672
|
|
776
|
+
#
|
|
777
|
+
# Sometimes Binance sends the first delta before the subscription
|
|
778
|
+
# confirmation arrives. At that point the orderbook is not
|
|
779
|
+
# initialized yet and the snapshot has not been requested yet
|
|
780
|
+
# therefore it is safe to drop these premature messages.
|
|
781
|
+
#
|
|
782
|
+
return
|
|
783
|
+
orderbook = self.orderbooks[symbol]
|
|
784
|
+
nonce = self.safe_integer(orderbook, 'nonce')
|
|
785
|
+
if nonce is None:
|
|
786
|
+
# 2. Buffer the events you receive from the stream.
|
|
787
|
+
orderbook.cache.append(message)
|
|
788
|
+
else:
|
|
789
|
+
try:
|
|
790
|
+
U = self.safe_integer(message, 'U')
|
|
791
|
+
u = self.safe_integer(message, 'u')
|
|
792
|
+
pu = self.safe_integer(message, 'pu')
|
|
793
|
+
if pu is None:
|
|
794
|
+
# spot
|
|
795
|
+
# 4. Drop any event where u is <= lastUpdateId in the snapshot
|
|
796
|
+
if u > orderbook['nonce']:
|
|
797
|
+
timestamp = self.safe_integer(orderbook, 'timestamp')
|
|
798
|
+
conditional = None
|
|
799
|
+
if timestamp is None:
|
|
800
|
+
# 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1
|
|
801
|
+
conditional = ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce'])
|
|
802
|
+
else:
|
|
803
|
+
# 6. While listening to the stream, each new event's U should be equal to the previous event's u+1.
|
|
804
|
+
conditional = ((U - 1) == orderbook['nonce'])
|
|
805
|
+
if conditional:
|
|
806
|
+
self.handle_order_book_message(client, message, orderbook)
|
|
807
|
+
if nonce < orderbook['nonce']:
|
|
808
|
+
client.resolve(orderbook, messageHash)
|
|
809
|
+
else:
|
|
810
|
+
checksum = self.safe_bool(self.options, 'checksum', True)
|
|
811
|
+
if checksum:
|
|
812
|
+
# todo: client.reject from handleOrderBookMessage properly
|
|
813
|
+
raise InvalidNonce(self.id + ' handleOrderBook received an out-of-order nonce')
|
|
814
|
+
else:
|
|
815
|
+
# future
|
|
816
|
+
# 4. Drop any event where u is < lastUpdateId in the snapshot
|
|
817
|
+
if u >= orderbook['nonce']:
|
|
818
|
+
# 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
|
|
819
|
+
# 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3
|
|
820
|
+
if (U <= orderbook['nonce']) or (pu == orderbook['nonce']):
|
|
821
|
+
self.handle_order_book_message(client, message, orderbook)
|
|
822
|
+
if nonce <= orderbook['nonce']:
|
|
823
|
+
client.resolve(orderbook, messageHash)
|
|
824
|
+
else:
|
|
825
|
+
checksum = self.safe_bool(self.options, 'checksum', True)
|
|
826
|
+
if checksum:
|
|
827
|
+
# todo: client.reject from handleOrderBookMessage properly
|
|
828
|
+
raise InvalidNonce(self.id + ' handleOrderBook received an out-of-order nonce')
|
|
829
|
+
except Exception as e:
|
|
830
|
+
del self.orderbooks[symbol]
|
|
831
|
+
del client.subscriptions[messageHash]
|
|
832
|
+
client.reject(e, messageHash)
|
|
833
|
+
|
|
834
|
+
def handle_order_book_subscription(self, client: Client, message, subscription):
|
|
835
|
+
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
|
|
836
|
+
# messageHash = self.safe_string(subscription, 'messageHash')
|
|
837
|
+
symbolOfSubscription = self.safe_string(subscription, 'symbol') # watchOrderBook
|
|
838
|
+
symbols = self.safe_value(subscription, 'symbols', [symbolOfSubscription]) # watchOrderBookForSymbols
|
|
839
|
+
limit = self.safe_integer(subscription, 'limit', defaultLimit)
|
|
840
|
+
# handle list of symbols
|
|
841
|
+
for i in range(0, len(symbols)):
|
|
842
|
+
symbol = symbols[i]
|
|
843
|
+
if symbol in self.orderbooks:
|
|
844
|
+
del self.orderbooks[symbol]
|
|
845
|
+
self.orderbooks[symbol] = self.order_book({}, limit)
|
|
846
|
+
subscription = self.extend(subscription, {'symbol': symbol})
|
|
847
|
+
# fetch the snapshot in a separate async call
|
|
848
|
+
self.spawn(self.fetch_order_book_snapshot, client, message, subscription)
|
|
849
|
+
|
|
850
|
+
def handle_subscription_status(self, client: Client, message):
|
|
851
|
+
#
|
|
852
|
+
# {
|
|
853
|
+
# "result": null,
|
|
854
|
+
# "id": 1574649734450
|
|
855
|
+
# }
|
|
856
|
+
#
|
|
857
|
+
id = self.safe_string(message, 'id')
|
|
858
|
+
subscriptionsById = self.index_by(client.subscriptions, 'id')
|
|
859
|
+
subscription = self.safe_value(subscriptionsById, id, {})
|
|
860
|
+
method = self.safe_value(subscription, 'method')
|
|
861
|
+
if method is not None:
|
|
862
|
+
method(client, message, subscription)
|
|
863
|
+
return message
|
|
864
|
+
|
|
865
|
+
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
866
|
+
"""
|
|
867
|
+
get the list of most recent trades for a list of symbols
|
|
868
|
+
:param str[] symbols: unified symbol of the market to fetch trades for
|
|
869
|
+
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
870
|
+
:param int [limit]: the maximum amount of trades to fetch
|
|
871
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
872
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
873
|
+
"""
|
|
874
|
+
await self.load_markets()
|
|
875
|
+
symbols = self.market_symbols(symbols, None, False, True, True)
|
|
876
|
+
streamHash = 'multipleTrades'
|
|
877
|
+
if symbols is not None:
|
|
878
|
+
symbolsLength = len(symbols)
|
|
879
|
+
if symbolsLength > 200:
|
|
880
|
+
raise BadRequest(self.id + ' watchTradesForSymbols() accepts 200 symbols at most. To watch more symbols call watchTradesForSymbols() multiple times')
|
|
881
|
+
streamHash += '::' + ','.join(symbols)
|
|
882
|
+
options = self.safe_value(self.options, 'watchTradesForSymbols', {})
|
|
883
|
+
name = self.safe_string(options, 'name', 'trade')
|
|
884
|
+
firstMarket = self.market(symbols[0])
|
|
885
|
+
type = firstMarket['type']
|
|
886
|
+
if firstMarket['contract']:
|
|
887
|
+
type = 'future' if firstMarket['linear'] else 'delivery'
|
|
888
|
+
subParams = []
|
|
889
|
+
for i in range(0, len(symbols)):
|
|
890
|
+
symbol = symbols[i]
|
|
891
|
+
market = self.market(symbol)
|
|
892
|
+
currentMessageHash = market['lowercaseId'] + '@' + name
|
|
893
|
+
subParams.append(currentMessageHash)
|
|
894
|
+
query = self.omit(params, 'type')
|
|
895
|
+
subParamsLength = len(subParams)
|
|
896
|
+
url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, subParamsLength)
|
|
897
|
+
requestId = self.request_id(url)
|
|
898
|
+
request: dict = {
|
|
899
|
+
'method': 'SUBSCRIBE',
|
|
900
|
+
'params': subParams,
|
|
901
|
+
'id': requestId,
|
|
902
|
+
}
|
|
903
|
+
subscribe: dict = {
|
|
904
|
+
'id': requestId,
|
|
905
|
+
}
|
|
906
|
+
trades = await self.watch_multiple(url, subParams, self.extend(request, query), subParams, subscribe)
|
|
907
|
+
if self.newUpdates:
|
|
908
|
+
first = self.safe_value(trades, 0)
|
|
909
|
+
tradeSymbol = self.safe_string(first, 'symbol')
|
|
910
|
+
limit = trades.getLimit(tradeSymbol, limit)
|
|
911
|
+
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
912
|
+
|
|
913
|
+
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
914
|
+
"""
|
|
915
|
+
get the list of most recent trades for a particular symbol
|
|
916
|
+
:param str symbol: unified symbol of the market to fetch trades for
|
|
917
|
+
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
918
|
+
:param int [limit]: the maximum amount of trades to fetch
|
|
919
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
920
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
921
|
+
"""
|
|
922
|
+
return await self.watch_trades_for_symbols([symbol], since, limit, params)
|
|
923
|
+
|
|
924
|
+
def parse_ws_trade(self, trade, market=None) -> Trade:
|
|
925
|
+
#
|
|
926
|
+
# public watchTrades
|
|
927
|
+
#
|
|
928
|
+
# {
|
|
929
|
+
# "e": "trade", # event type
|
|
930
|
+
# "E": 1579481530911, # event time
|
|
931
|
+
# "s": "ETHBTC", # symbol
|
|
932
|
+
# "t": 158410082, # trade id
|
|
933
|
+
# "p": "0.01914100", # price
|
|
934
|
+
# "q": "0.00700000", # quantity
|
|
935
|
+
# "b": 586187049, # buyer order id
|
|
936
|
+
# "a": 586186710, # seller order id
|
|
937
|
+
# "T": 1579481530910, # trade time
|
|
938
|
+
# "m": False, # is the buyer the market maker
|
|
939
|
+
# "M": True # binance docs say it should be ignored
|
|
940
|
+
# }
|
|
941
|
+
#
|
|
942
|
+
# {
|
|
943
|
+
# "e": "aggTrade", # Event type
|
|
944
|
+
# "E": 123456789, # Event time
|
|
945
|
+
# "s": "BNBBTC", # Symbol
|
|
946
|
+
# "a": 12345, # Aggregate trade ID
|
|
947
|
+
# "p": "0.001", # Price
|
|
948
|
+
# "q": "100", # Quantity
|
|
949
|
+
# "f": 100, # First trade ID
|
|
950
|
+
# "l": 105, # Last trade ID
|
|
951
|
+
# "T": 123456785, # Trade time
|
|
952
|
+
# "m": True, # Is the buyer the market maker?
|
|
953
|
+
# "M": True # Ignore
|
|
954
|
+
# }
|
|
955
|
+
#
|
|
956
|
+
# private watchMyTrades spot
|
|
957
|
+
#
|
|
958
|
+
# {
|
|
959
|
+
# "e": "executionReport",
|
|
960
|
+
# "E": 1611063861489,
|
|
961
|
+
# "s": "BNBUSDT",
|
|
962
|
+
# "c": "m4M6AD5MF3b1ERe65l4SPq",
|
|
963
|
+
# "S": "BUY",
|
|
964
|
+
# "o": "MARKET",
|
|
965
|
+
# "f": "GTC",
|
|
966
|
+
# "q": "2.00000000",
|
|
967
|
+
# "p": "0.00000000",
|
|
968
|
+
# "P": "0.00000000",
|
|
969
|
+
# "F": "0.00000000",
|
|
970
|
+
# "g": -1,
|
|
971
|
+
# "C": '',
|
|
972
|
+
# "x": "TRADE",
|
|
973
|
+
# "X": "PARTIALLY_FILLED",
|
|
974
|
+
# "r": "NONE",
|
|
975
|
+
# "i": 1296882607,
|
|
976
|
+
# "l": "0.33200000",
|
|
977
|
+
# "z": "0.33200000",
|
|
978
|
+
# "L": "46.86600000",
|
|
979
|
+
# "n": "0.00033200",
|
|
980
|
+
# "N": "BNB",
|
|
981
|
+
# "T": 1611063861488,
|
|
982
|
+
# "t": 109747654,
|
|
983
|
+
# "I": 2696953381,
|
|
984
|
+
# "w": False,
|
|
985
|
+
# "m": False,
|
|
986
|
+
# "M": True,
|
|
987
|
+
# "O": 1611063861488,
|
|
988
|
+
# "Z": "15.55951200",
|
|
989
|
+
# "Y": "15.55951200",
|
|
990
|
+
# "Q": "0.00000000"
|
|
991
|
+
# }
|
|
992
|
+
#
|
|
993
|
+
# private watchMyTrades future/delivery
|
|
994
|
+
#
|
|
995
|
+
# {
|
|
996
|
+
# "s": "BTCUSDT",
|
|
997
|
+
# "c": "pb2jD6ZQHpfzSdUac8VqMK",
|
|
998
|
+
# "S": "SELL",
|
|
999
|
+
# "o": "MARKET",
|
|
1000
|
+
# "f": "GTC",
|
|
1001
|
+
# "q": "0.001",
|
|
1002
|
+
# "p": "0",
|
|
1003
|
+
# "ap": "33468.46000",
|
|
1004
|
+
# "sp": "0",
|
|
1005
|
+
# "x": "TRADE",
|
|
1006
|
+
# "X": "FILLED",
|
|
1007
|
+
# "i": 13351197194,
|
|
1008
|
+
# "l": "0.001",
|
|
1009
|
+
# "z": "0.001",
|
|
1010
|
+
# "L": "33468.46",
|
|
1011
|
+
# "n": "0.00027086",
|
|
1012
|
+
# "N": "BNB",
|
|
1013
|
+
# "T": 1612095165362,
|
|
1014
|
+
# "t": 458032604,
|
|
1015
|
+
# "b": "0",
|
|
1016
|
+
# "a": "0",
|
|
1017
|
+
# "m": False,
|
|
1018
|
+
# "R": False,
|
|
1019
|
+
# "wt": "CONTRACT_PRICE",
|
|
1020
|
+
# "ot": "MARKET",
|
|
1021
|
+
# "ps": "BOTH",
|
|
1022
|
+
# "cp": False,
|
|
1023
|
+
# "rp": "0.00335000",
|
|
1024
|
+
# "pP": False,
|
|
1025
|
+
# "si": 0,
|
|
1026
|
+
# "ss": 0
|
|
1027
|
+
# }
|
|
1028
|
+
#
|
|
1029
|
+
executionType = self.safe_string(trade, 'x')
|
|
1030
|
+
isTradeExecution = (executionType == 'TRADE')
|
|
1031
|
+
if not isTradeExecution:
|
|
1032
|
+
return self.parse_trade(trade, market)
|
|
1033
|
+
id = self.safe_string_2(trade, 't', 'a')
|
|
1034
|
+
timestamp = self.safe_integer(trade, 'T')
|
|
1035
|
+
price = self.safe_string_2(trade, 'L', 'p')
|
|
1036
|
+
amount = self.safe_string(trade, 'q')
|
|
1037
|
+
if isTradeExecution:
|
|
1038
|
+
amount = self.safe_string(trade, 'l', amount)
|
|
1039
|
+
cost = self.safe_string(trade, 'Y')
|
|
1040
|
+
if cost is None:
|
|
1041
|
+
if (price is not None) and (amount is not None):
|
|
1042
|
+
cost = Precise.string_mul(price, amount)
|
|
1043
|
+
marketId = self.safe_string(trade, 's')
|
|
1044
|
+
marketType = 'contract' if ('ps' in trade) else 'spot'
|
|
1045
|
+
symbol = self.safe_symbol(marketId, None, None, marketType)
|
|
1046
|
+
side = self.safe_string_lower(trade, 'S')
|
|
1047
|
+
takerOrMaker = None
|
|
1048
|
+
orderId = self.safe_string(trade, 'i')
|
|
1049
|
+
if 'm' in trade:
|
|
1050
|
+
if side is None:
|
|
1051
|
+
side = 'sell' if trade['m'] else 'buy' # self is reversed intentionally
|
|
1052
|
+
takerOrMaker = 'maker' if trade['m'] else 'taker'
|
|
1053
|
+
fee = None
|
|
1054
|
+
feeCost = self.safe_string(trade, 'n')
|
|
1055
|
+
if feeCost is not None:
|
|
1056
|
+
feeCurrencyId = self.safe_string(trade, 'N')
|
|
1057
|
+
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
|
|
1058
|
+
fee = {
|
|
1059
|
+
'cost': feeCost,
|
|
1060
|
+
'currency': feeCurrencyCode,
|
|
1061
|
+
}
|
|
1062
|
+
type = self.safe_string_lower(trade, 'o')
|
|
1063
|
+
return self.safe_trade({
|
|
1064
|
+
'info': trade,
|
|
1065
|
+
'timestamp': timestamp,
|
|
1066
|
+
'datetime': self.iso8601(timestamp),
|
|
1067
|
+
'symbol': symbol,
|
|
1068
|
+
'id': id,
|
|
1069
|
+
'order': orderId,
|
|
1070
|
+
'type': type,
|
|
1071
|
+
'takerOrMaker': takerOrMaker,
|
|
1072
|
+
'side': side,
|
|
1073
|
+
'price': price,
|
|
1074
|
+
'amount': amount,
|
|
1075
|
+
'cost': cost,
|
|
1076
|
+
'fee': fee,
|
|
1077
|
+
})
|
|
1078
|
+
|
|
1079
|
+
def handle_trade(self, client: Client, message):
|
|
1080
|
+
# the trade streams push raw trade information in real-time
|
|
1081
|
+
# each trade has a unique buyer and seller
|
|
1082
|
+
isSpot = ((client.url.find('wss://stream.binance.com') > -1) or (client.url.find('/testnet.binance') > -1))
|
|
1083
|
+
marketType = 'spot' if (isSpot) else 'contract'
|
|
1084
|
+
marketId = self.safe_string(message, 's')
|
|
1085
|
+
market = self.safe_market(marketId, None, None, marketType)
|
|
1086
|
+
symbol = market['symbol']
|
|
1087
|
+
lowerCaseId = self.safe_string_lower(message, 's')
|
|
1088
|
+
event = self.safe_string(message, 'e')
|
|
1089
|
+
messageHash = lowerCaseId + '@' + event
|
|
1090
|
+
trade = self.parse_ws_trade(message, market)
|
|
1091
|
+
tradesArray = self.safe_value(self.trades, symbol)
|
|
1092
|
+
if tradesArray is None:
|
|
1093
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
1094
|
+
tradesArray = ArrayCache(limit)
|
|
1095
|
+
tradesArray.append(trade)
|
|
1096
|
+
self.trades[symbol] = tradesArray
|
|
1097
|
+
client.resolve(tradesArray, messageHash)
|
|
1098
|
+
|
|
1099
|
+
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
1100
|
+
"""
|
|
1101
|
+
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
1102
|
+
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
1103
|
+
:param str timeframe: the length of time each candle represents
|
|
1104
|
+
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
1105
|
+
:param int [limit]: the maximum amount of candles to fetch
|
|
1106
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1107
|
+
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
1108
|
+
"""
|
|
1109
|
+
await self.load_markets()
|
|
1110
|
+
market = self.market(symbol)
|
|
1111
|
+
marketId = market['lowercaseId']
|
|
1112
|
+
interval = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
1113
|
+
options = self.safe_value(self.options, 'watchOHLCV', {})
|
|
1114
|
+
nameOption = self.safe_string(options, 'name', 'kline')
|
|
1115
|
+
name = self.safe_string(params, 'name', nameOption)
|
|
1116
|
+
if name == 'indexPriceKline':
|
|
1117
|
+
marketId = marketId.replace('_perp', '')
|
|
1118
|
+
# weird behavior for index price kline we can't use the perp suffix
|
|
1119
|
+
params = self.omit(params, 'name')
|
|
1120
|
+
messageHash = marketId + '@' + name + '_' + interval
|
|
1121
|
+
type = market['type']
|
|
1122
|
+
if market['contract']:
|
|
1123
|
+
type = 'future' if market['linear'] else 'delivery'
|
|
1124
|
+
url = self.urls['api']['ws'][type] + '/' + self.stream(type, messageHash)
|
|
1125
|
+
requestId = self.request_id(url)
|
|
1126
|
+
request: dict = {
|
|
1127
|
+
'method': 'SUBSCRIBE',
|
|
1128
|
+
'params': [
|
|
1129
|
+
messageHash,
|
|
1130
|
+
],
|
|
1131
|
+
'id': requestId,
|
|
1132
|
+
}
|
|
1133
|
+
subscribe: dict = {
|
|
1134
|
+
'id': requestId,
|
|
1135
|
+
}
|
|
1136
|
+
ohlcv = await self.watch(url, messageHash, self.extend(request, params), messageHash, subscribe)
|
|
1137
|
+
if self.newUpdates:
|
|
1138
|
+
limit = ohlcv.getLimit(symbol, limit)
|
|
1139
|
+
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
|
1140
|
+
|
|
1141
|
+
def handle_ohlcv(self, client: Client, message):
|
|
1142
|
+
#
|
|
1143
|
+
# {
|
|
1144
|
+
# "e": "kline",
|
|
1145
|
+
# "E": 1579482921215,
|
|
1146
|
+
# "s": "ETHBTC",
|
|
1147
|
+
# "k": {
|
|
1148
|
+
# "t": 1579482900000,
|
|
1149
|
+
# "T": 1579482959999,
|
|
1150
|
+
# "s": "ETHBTC",
|
|
1151
|
+
# "i": "1m",
|
|
1152
|
+
# "f": 158411535,
|
|
1153
|
+
# "L": 158411550,
|
|
1154
|
+
# "o": "0.01913200",
|
|
1155
|
+
# "c": "0.01913500",
|
|
1156
|
+
# "h": "0.01913700",
|
|
1157
|
+
# "l": "0.01913200",
|
|
1158
|
+
# "v": "5.08400000",
|
|
1159
|
+
# "n": 16,
|
|
1160
|
+
# "x": False,
|
|
1161
|
+
# "q": "0.09728060",
|
|
1162
|
+
# "V": "3.30200000",
|
|
1163
|
+
# "Q": "0.06318500",
|
|
1164
|
+
# "B": "0"
|
|
1165
|
+
# }
|
|
1166
|
+
# }
|
|
1167
|
+
#
|
|
1168
|
+
event = self.safe_string(message, 'e')
|
|
1169
|
+
eventMap: dict = {
|
|
1170
|
+
'indexPrice_kline': 'indexPriceKline',
|
|
1171
|
+
'markPrice_kline': 'markPriceKline',
|
|
1172
|
+
}
|
|
1173
|
+
event = self.safe_string(eventMap, event, event)
|
|
1174
|
+
kline = self.safe_value(message, 'k')
|
|
1175
|
+
marketId = self.safe_string_2(kline, 's', 'ps')
|
|
1176
|
+
if event == 'indexPriceKline':
|
|
1177
|
+
# indexPriceKline doesn't have the _PERP suffix
|
|
1178
|
+
marketId = self.safe_string(message, 'ps')
|
|
1179
|
+
lowercaseMarketId = marketId.lower()
|
|
1180
|
+
interval = self.safe_string(kline, 'i')
|
|
1181
|
+
# use a reverse lookup in a static map instead
|
|
1182
|
+
timeframe = self.find_timeframe(interval)
|
|
1183
|
+
messageHash = lowercaseMarketId + '@' + event + '_' + interval
|
|
1184
|
+
parsed = [
|
|
1185
|
+
self.safe_integer(kline, 't'),
|
|
1186
|
+
self.safe_float(kline, 'o'),
|
|
1187
|
+
self.safe_float(kline, 'h'),
|
|
1188
|
+
self.safe_float(kline, 'l'),
|
|
1189
|
+
self.safe_float(kline, 'c'),
|
|
1190
|
+
self.safe_float(kline, 'v'),
|
|
1191
|
+
]
|
|
1192
|
+
isSpot = ((client.url.find('/stream') > -1) or (client.url.find('/testnet.binance') > -1))
|
|
1193
|
+
marketType = 'spot' if (isSpot) else 'contract'
|
|
1194
|
+
symbol = self.safe_symbol(marketId, None, None, marketType)
|
|
1195
|
+
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
|
1196
|
+
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
|
|
1197
|
+
if stored is None:
|
|
1198
|
+
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
1199
|
+
stored = ArrayCacheByTimestamp(limit)
|
|
1200
|
+
self.ohlcvs[symbol][timeframe] = stored
|
|
1201
|
+
stored.append(parsed)
|
|
1202
|
+
client.resolve(stored, messageHash)
|
|
1203
|
+
|
|
1204
|
+
async def fetch_ticker_ws(self, symbol: str, params={}) -> Ticker:
|
|
1205
|
+
"""
|
|
1206
|
+
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
1207
|
+
:see: https://binance-docs.github.io/apidocs/voptions/en/#24hr-ticker-price-change-statistics
|
|
1208
|
+
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
1209
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1210
|
+
:param str [params.method]: method to use can be ticker.price or ticker.book
|
|
1211
|
+
:param boolean [params.returnRateLimits]: return the rate limits for the exchange
|
|
1212
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
1213
|
+
"""
|
|
1214
|
+
await self.load_markets()
|
|
1215
|
+
market = self.market(symbol)
|
|
1216
|
+
payload: dict = {
|
|
1217
|
+
'symbol': market['id'],
|
|
1218
|
+
}
|
|
1219
|
+
type = self.get_market_type('fetchTickerWs', market, params)
|
|
1220
|
+
if type != 'future':
|
|
1221
|
+
raise BadRequest(self.id + ' fetchTickerWs only supports swap markets')
|
|
1222
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
1223
|
+
requestId = self.request_id(url)
|
|
1224
|
+
messageHash = str(requestId)
|
|
1225
|
+
subscription: dict = {
|
|
1226
|
+
'method': self.handle_ticker_ws,
|
|
1227
|
+
}
|
|
1228
|
+
returnRateLimits = False
|
|
1229
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchTickerWs', 'returnRateLimits', False)
|
|
1230
|
+
payload['returnRateLimits'] = returnRateLimits
|
|
1231
|
+
params = self.omit(params, 'test')
|
|
1232
|
+
method = None
|
|
1233
|
+
method, params = self.handle_option_and_params(params, 'fetchTickerWs', 'method', 'ticker.book')
|
|
1234
|
+
message: dict = {
|
|
1235
|
+
'id': messageHash,
|
|
1236
|
+
'method': method,
|
|
1237
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
1238
|
+
}
|
|
1239
|
+
ticker = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
1240
|
+
return ticker
|
|
1241
|
+
|
|
1242
|
+
async def fetch_ohlcv_ws(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
1243
|
+
"""
|
|
1244
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#klines
|
|
1245
|
+
query historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
1246
|
+
:param str symbol: unified symbol of the market to query OHLCV data for
|
|
1247
|
+
:param str timeframe: the length of time each candle represents
|
|
1248
|
+
:param int since: timestamp in ms of the earliest candle to fetch
|
|
1249
|
+
:param int limit: the maximum amount of candles to fetch
|
|
1250
|
+
:param dict params: extra parameters specific to the exchange API endpoint
|
|
1251
|
+
:param int params['until']: timestamp in ms of the earliest candle to fetch
|
|
1252
|
+
*
|
|
1253
|
+
* EXCHANGE SPECIFIC PARAMETERS
|
|
1254
|
+
:param str params['timeZone']: default=0(UTC)
|
|
1255
|
+
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
1256
|
+
"""
|
|
1257
|
+
await self.load_markets()
|
|
1258
|
+
market = self.market(symbol)
|
|
1259
|
+
marketType = self.get_market_type('fetchOHLCVWs', market, params)
|
|
1260
|
+
if marketType != 'spot' and marketType != 'future':
|
|
1261
|
+
raise BadRequest(self.id + ' fetchOHLCVWs only supports spot or swap markets')
|
|
1262
|
+
url = self.urls['api']['ws']['ws-api'][marketType]
|
|
1263
|
+
requestId = self.request_id(url)
|
|
1264
|
+
messageHash = str(requestId)
|
|
1265
|
+
returnRateLimits = False
|
|
1266
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchOHLCVWs', 'returnRateLimits', False)
|
|
1267
|
+
payload: dict = {
|
|
1268
|
+
'symbol': self.market_id(symbol),
|
|
1269
|
+
'returnRateLimits': returnRateLimits,
|
|
1270
|
+
'interval': self.timeframes[timeframe],
|
|
1271
|
+
}
|
|
1272
|
+
until = self.safe_integer(params, 'until')
|
|
1273
|
+
params = self.omit(params, 'until')
|
|
1274
|
+
if since is not None:
|
|
1275
|
+
payload['startTime'] = since
|
|
1276
|
+
if limit is not None:
|
|
1277
|
+
payload['limit'] = limit
|
|
1278
|
+
if until is not None:
|
|
1279
|
+
payload['endTime'] = until
|
|
1280
|
+
message: dict = {
|
|
1281
|
+
'id': messageHash,
|
|
1282
|
+
'method': 'klines',
|
|
1283
|
+
'params': self.extend(payload, params),
|
|
1284
|
+
}
|
|
1285
|
+
subscription: dict = {
|
|
1286
|
+
'method': self.handle_fetch_ohlcv,
|
|
1287
|
+
}
|
|
1288
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
1289
|
+
|
|
1290
|
+
def handle_fetch_ohlcv(self, client: Client, message):
|
|
1291
|
+
#
|
|
1292
|
+
# {
|
|
1293
|
+
# "id": "1dbbeb56-8eea-466a-8f6e-86bdcfa2fc0b",
|
|
1294
|
+
# "status": 200,
|
|
1295
|
+
# "result": [
|
|
1296
|
+
# [
|
|
1297
|
+
# 1655971200000, # Kline open time
|
|
1298
|
+
# "0.01086000", # Open price
|
|
1299
|
+
# "0.01086600", # High price
|
|
1300
|
+
# "0.01083600", # Low price
|
|
1301
|
+
# "0.01083800", # Close price
|
|
1302
|
+
# "2290.53800000", # Volume
|
|
1303
|
+
# 1655974799999, # Kline close time
|
|
1304
|
+
# "24.85074442", # Quote asset volume
|
|
1305
|
+
# 2283, # Number of trades
|
|
1306
|
+
# "1171.64000000", # Taker buy base asset volume
|
|
1307
|
+
# "12.71225884", # Taker buy quote asset volume
|
|
1308
|
+
# "0" # Unused field, ignore
|
|
1309
|
+
# ]
|
|
1310
|
+
# ],
|
|
1311
|
+
# "rateLimits": [
|
|
1312
|
+
# {
|
|
1313
|
+
# "rateLimitType": "REQUEST_WEIGHT",
|
|
1314
|
+
# "interval": "MINUTE",
|
|
1315
|
+
# "intervalNum": 1,
|
|
1316
|
+
# "limit": 6000,
|
|
1317
|
+
# "count": 2
|
|
1318
|
+
# }
|
|
1319
|
+
# ]
|
|
1320
|
+
# }
|
|
1321
|
+
#
|
|
1322
|
+
result = self.safe_list(message, 'result')
|
|
1323
|
+
parsed = self.parse_ohlcvs(result)
|
|
1324
|
+
# use a reverse lookup in a static map instead
|
|
1325
|
+
messageHash = self.safe_string(message, 'id')
|
|
1326
|
+
client.resolve(parsed, messageHash)
|
|
1327
|
+
|
|
1328
|
+
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
1329
|
+
"""
|
|
1330
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
1331
|
+
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
1332
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1333
|
+
:param str [params.name]: stream to use can be ticker or miniTicker
|
|
1334
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
1335
|
+
"""
|
|
1336
|
+
await self.load_markets()
|
|
1337
|
+
symbol = self.symbol(symbol)
|
|
1338
|
+
tickers = await self.watch_tickers([symbol], self.extend(params, {'callerMethodName': 'watchTicker'}))
|
|
1339
|
+
return tickers[symbol]
|
|
1340
|
+
|
|
1341
|
+
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
1342
|
+
"""
|
|
1343
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
|
1344
|
+
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
1345
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1346
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
1347
|
+
"""
|
|
1348
|
+
channelName = None
|
|
1349
|
+
channelName, params = self.handle_option_and_params(params, 'watchTickers', 'name', 'ticker')
|
|
1350
|
+
if channelName == 'bookTicker':
|
|
1351
|
+
raise BadRequest(self.id + ' deprecation notice - to subscribe for bids-asks, use watch_bids_asks() method instead')
|
|
1352
|
+
newTickers = await self.watch_multi_ticker_helper('watchTickers', channelName, symbols, params)
|
|
1353
|
+
if self.newUpdates:
|
|
1354
|
+
return newTickers
|
|
1355
|
+
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
1356
|
+
|
|
1357
|
+
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
|
|
1358
|
+
"""
|
|
1359
|
+
:see: https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-book-ticker-streams
|
|
1360
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#all-book-tickers-stream
|
|
1361
|
+
:see: https://binance-docs.github.io/apidocs/delivery/en/#all-book-tickers-stream
|
|
1362
|
+
watches best bid & ask for symbols
|
|
1363
|
+
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
|
1364
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1365
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
1366
|
+
"""
|
|
1367
|
+
result = await self.watch_multi_ticker_helper('watchBidsAsks', 'bookTicker', symbols, params)
|
|
1368
|
+
if self.newUpdates:
|
|
1369
|
+
return result
|
|
1370
|
+
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
1371
|
+
|
|
1372
|
+
async def watch_multi_ticker_helper(self, methodName, channelName: str, symbols: Strings = None, params={}):
|
|
1373
|
+
await self.load_markets()
|
|
1374
|
+
symbols = self.market_symbols(symbols, None, True, False, True)
|
|
1375
|
+
firstMarket = None
|
|
1376
|
+
marketType = None
|
|
1377
|
+
symbolsDefined = (symbols is not None)
|
|
1378
|
+
if symbolsDefined:
|
|
1379
|
+
firstMarket = self.market(symbols[0])
|
|
1380
|
+
marketType, params = self.handle_market_type_and_params(methodName, firstMarket, params)
|
|
1381
|
+
subType = None
|
|
1382
|
+
subType, params = self.handle_sub_type_and_params(methodName, firstMarket, params)
|
|
1383
|
+
rawMarketType = None
|
|
1384
|
+
if self.isLinear(marketType, subType):
|
|
1385
|
+
rawMarketType = 'future'
|
|
1386
|
+
elif self.isInverse(marketType, subType):
|
|
1387
|
+
rawMarketType = 'delivery'
|
|
1388
|
+
elif marketType == 'spot':
|
|
1389
|
+
rawMarketType = marketType
|
|
1390
|
+
else:
|
|
1391
|
+
raise NotSupported(self.id + ' ' + methodName + '() does not support options markets')
|
|
1392
|
+
isBidAsk = (channelName == 'bookTicker')
|
|
1393
|
+
subscriptionArgs = []
|
|
1394
|
+
messageHashes = []
|
|
1395
|
+
if symbolsDefined:
|
|
1396
|
+
for i in range(0, len(symbols)):
|
|
1397
|
+
symbol = symbols[i]
|
|
1398
|
+
market = self.market(symbol)
|
|
1399
|
+
subscriptionArgs.append(market['lowercaseId'] + '@' + channelName)
|
|
1400
|
+
messageHashes.append(self.get_message_hash(channelName, market['symbol'], isBidAsk))
|
|
1401
|
+
else:
|
|
1402
|
+
if isBidAsk:
|
|
1403
|
+
if marketType == 'spot':
|
|
1404
|
+
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires symbols for self channel for spot markets')
|
|
1405
|
+
subscriptionArgs.append('!' + channelName)
|
|
1406
|
+
else:
|
|
1407
|
+
subscriptionArgs.append('!' + channelName + '@arr')
|
|
1408
|
+
messageHashes.append(self.get_message_hash(channelName, None, isBidAsk))
|
|
1409
|
+
streamHash = channelName
|
|
1410
|
+
if symbolsDefined:
|
|
1411
|
+
streamHash = channelName + '::' + ','.join(symbols)
|
|
1412
|
+
url = self.urls['api']['ws'][rawMarketType] + '/' + self.stream(rawMarketType, streamHash)
|
|
1413
|
+
requestId = self.request_id(url)
|
|
1414
|
+
request: dict = {
|
|
1415
|
+
'method': 'SUBSCRIBE',
|
|
1416
|
+
'params': subscriptionArgs,
|
|
1417
|
+
'id': requestId,
|
|
1418
|
+
}
|
|
1419
|
+
subscribe: dict = {
|
|
1420
|
+
'id': requestId,
|
|
1421
|
+
}
|
|
1422
|
+
result = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), subscriptionArgs, subscribe)
|
|
1423
|
+
# for efficiency, we have two type of returned structure here - if symbols array was provided, then individual
|
|
1424
|
+
# ticker dict comes in, otherwise all-tickers dict comes in
|
|
1425
|
+
if not symbolsDefined:
|
|
1426
|
+
return result
|
|
1427
|
+
else:
|
|
1428
|
+
newDict: dict = {}
|
|
1429
|
+
newDict[result['symbol']] = result
|
|
1430
|
+
return newDict
|
|
1431
|
+
|
|
1432
|
+
def parse_ws_ticker(self, message, marketType):
|
|
1433
|
+
#
|
|
1434
|
+
# ticker
|
|
1435
|
+
# {
|
|
1436
|
+
# "e": "24hrTicker", # event type
|
|
1437
|
+
# "E": 1579485598569, # event time
|
|
1438
|
+
# "s": "ETHBTC", # symbol
|
|
1439
|
+
# "p": "-0.00004000", # price change
|
|
1440
|
+
# "P": "-0.209", # price change percent
|
|
1441
|
+
# "w": "0.01920495", # weighted average price
|
|
1442
|
+
# "x": "0.01916500", # the price of the first trade before the 24hr rolling window
|
|
1443
|
+
# "c": "0.01912500", # last(closing) price
|
|
1444
|
+
# "Q": "0.10400000", # last quantity
|
|
1445
|
+
# "b": "0.01912200", # best bid
|
|
1446
|
+
# "B": "4.10400000", # best bid quantity
|
|
1447
|
+
# "a": "0.01912500", # best ask
|
|
1448
|
+
# "A": "0.00100000", # best ask quantity
|
|
1449
|
+
# "o": "0.01916500", # open price
|
|
1450
|
+
# "h": "0.01956500", # high price
|
|
1451
|
+
# "l": "0.01887700", # low price
|
|
1452
|
+
# "v": "173518.11900000", # base volume
|
|
1453
|
+
# "q": "3332.40703994", # quote volume
|
|
1454
|
+
# "O": 1579399197842, # open time
|
|
1455
|
+
# "C": 1579485597842, # close time
|
|
1456
|
+
# "F": 158251292, # first trade id
|
|
1457
|
+
# "L": 158414513, # last trade id
|
|
1458
|
+
# "n": 163222, # total number of trades
|
|
1459
|
+
# }
|
|
1460
|
+
#
|
|
1461
|
+
# miniTicker
|
|
1462
|
+
# {
|
|
1463
|
+
# "e": "24hrMiniTicker",
|
|
1464
|
+
# "E": 1671617114585,
|
|
1465
|
+
# "s": "MOBBUSD",
|
|
1466
|
+
# "c": "0.95900000",
|
|
1467
|
+
# "o": "0.91200000",
|
|
1468
|
+
# "h": "1.04000000",
|
|
1469
|
+
# "l": "0.89400000",
|
|
1470
|
+
# "v": "2109995.32000000",
|
|
1471
|
+
# "q": "2019254.05788000"
|
|
1472
|
+
# }
|
|
1473
|
+
# fetchTickerWs
|
|
1474
|
+
# {
|
|
1475
|
+
# "symbol":"BTCUSDT",
|
|
1476
|
+
# "price":"72606.70",
|
|
1477
|
+
# "time":1712526204284
|
|
1478
|
+
# }
|
|
1479
|
+
# fetchTickerWs - ticker.book
|
|
1480
|
+
# {
|
|
1481
|
+
# "lastUpdateId":1027024,
|
|
1482
|
+
# "symbol":"BTCUSDT",
|
|
1483
|
+
# "bidPrice":"4.00000000",
|
|
1484
|
+
# "bidQty":"431.00000000",
|
|
1485
|
+
# "askPrice":"4.00000200",
|
|
1486
|
+
# "askQty":"9.00000000",
|
|
1487
|
+
# "time":1589437530011,
|
|
1488
|
+
# }
|
|
1489
|
+
#
|
|
1490
|
+
event = self.safe_string(message, 'e', 'bookTicker')
|
|
1491
|
+
if event == '24hrTicker':
|
|
1492
|
+
event = 'ticker'
|
|
1493
|
+
timestamp = None
|
|
1494
|
+
if event == 'bookTicker':
|
|
1495
|
+
# take the event timestamp, if available, for spot tickers it is not
|
|
1496
|
+
timestamp = self.safe_integer_2(message, 'E', 'time')
|
|
1497
|
+
else:
|
|
1498
|
+
# take the timestamp of the closing price for candlestick streams
|
|
1499
|
+
timestamp = self.safe_integer_n(message, ['C', 'E', 'time'])
|
|
1500
|
+
marketId = self.safe_string_2(message, 's', 'symbol')
|
|
1501
|
+
symbol = self.safe_symbol(marketId, None, None, marketType)
|
|
1502
|
+
market = self.safe_market(marketId, None, None, marketType)
|
|
1503
|
+
last = self.safe_string_2(message, 'c', 'price')
|
|
1504
|
+
return self.safe_ticker({
|
|
1505
|
+
'symbol': symbol,
|
|
1506
|
+
'timestamp': timestamp,
|
|
1507
|
+
'datetime': self.iso8601(timestamp),
|
|
1508
|
+
'high': self.safe_string(message, 'h'),
|
|
1509
|
+
'low': self.safe_string(message, 'l'),
|
|
1510
|
+
'bid': self.safe_string_2(message, 'b', 'bidPrice'),
|
|
1511
|
+
'bidVolume': self.safe_string_2(message, 'B', 'bidQty'),
|
|
1512
|
+
'ask': self.safe_string_2(message, 'a', 'askPrice'),
|
|
1513
|
+
'askVolume': self.safe_string_2(message, 'A', 'askQty'),
|
|
1514
|
+
'vwap': self.safe_string(message, 'w'),
|
|
1515
|
+
'open': self.safe_string(message, 'o'),
|
|
1516
|
+
'close': last,
|
|
1517
|
+
'last': last,
|
|
1518
|
+
'previousClose': self.safe_string(message, 'x'), # previous day close
|
|
1519
|
+
'change': self.safe_string(message, 'p'),
|
|
1520
|
+
'percentage': self.safe_string(message, 'P'),
|
|
1521
|
+
'average': None,
|
|
1522
|
+
'baseVolume': self.safe_string(message, 'v'),
|
|
1523
|
+
'quoteVolume': self.safe_string(message, 'q'),
|
|
1524
|
+
'info': message,
|
|
1525
|
+
}, market)
|
|
1526
|
+
|
|
1527
|
+
def handle_ticker_ws(self, client: Client, message):
|
|
1528
|
+
#
|
|
1529
|
+
# ticker.price
|
|
1530
|
+
# {
|
|
1531
|
+
# "id":"1",
|
|
1532
|
+
# "status":200,
|
|
1533
|
+
# "result":{
|
|
1534
|
+
# "symbol":"BTCUSDT",
|
|
1535
|
+
# "price":"73178.50",
|
|
1536
|
+
# "time":1712527052374
|
|
1537
|
+
# }
|
|
1538
|
+
# }
|
|
1539
|
+
# ticker.book
|
|
1540
|
+
# {
|
|
1541
|
+
# "id":"9d32157c-a556-4d27-9866-66760a174b57",
|
|
1542
|
+
# "status":200,
|
|
1543
|
+
# "result":{
|
|
1544
|
+
# "lastUpdateId":1027024,
|
|
1545
|
+
# "symbol":"BTCUSDT",
|
|
1546
|
+
# "bidPrice":"4.00000000",
|
|
1547
|
+
# "bidQty":"431.00000000",
|
|
1548
|
+
# "askPrice":"4.00000200",
|
|
1549
|
+
# "askQty":"9.00000000",
|
|
1550
|
+
# "time":1589437530011 # Transaction time
|
|
1551
|
+
# }
|
|
1552
|
+
# }
|
|
1553
|
+
#
|
|
1554
|
+
messageHash = self.safe_string(message, 'id')
|
|
1555
|
+
result = self.safe_value(message, 'result', {})
|
|
1556
|
+
ticker = self.parse_ws_ticker(result, 'future')
|
|
1557
|
+
client.resolve(ticker, messageHash)
|
|
1558
|
+
|
|
1559
|
+
def handle_bids_asks(self, client: Client, message):
|
|
1560
|
+
#
|
|
1561
|
+
# arrives one symbol dict or array of symbol dicts
|
|
1562
|
+
#
|
|
1563
|
+
# {
|
|
1564
|
+
# "u": 7488717758,
|
|
1565
|
+
# "s": "BTCUSDT",
|
|
1566
|
+
# "b": "28621.74000000",
|
|
1567
|
+
# "B": "1.43278800",
|
|
1568
|
+
# "a": "28621.75000000",
|
|
1569
|
+
# "A": "2.52500800"
|
|
1570
|
+
# }
|
|
1571
|
+
#
|
|
1572
|
+
self.handle_tickers_and_bids_asks(client, message, 'bidasks')
|
|
1573
|
+
|
|
1574
|
+
def handle_tickers(self, client: Client, message):
|
|
1575
|
+
#
|
|
1576
|
+
# arrives one symbol dict or array of symbol dicts
|
|
1577
|
+
#
|
|
1578
|
+
# {
|
|
1579
|
+
# "e": "24hrTicker", # event type
|
|
1580
|
+
# "E": 1579485598569, # event time
|
|
1581
|
+
# "s": "ETHBTC", # symbol
|
|
1582
|
+
# "p": "-0.00004000", # price change
|
|
1583
|
+
# "P": "-0.209", # price change percent
|
|
1584
|
+
# "w": "0.01920495", # weighted average price
|
|
1585
|
+
# "x": "0.01916500", # the price of the first trade before the 24hr rolling window
|
|
1586
|
+
# "c": "0.01912500", # last(closing) price
|
|
1587
|
+
# "Q": "0.10400000", # last quantity
|
|
1588
|
+
# "b": "0.01912200", # best bid
|
|
1589
|
+
# "B": "4.10400000", # best bid quantity
|
|
1590
|
+
# "a": "0.01912500", # best ask
|
|
1591
|
+
# "A": "0.00100000", # best ask quantity
|
|
1592
|
+
# "o": "0.01916500", # open price
|
|
1593
|
+
# "h": "0.01956500", # high price
|
|
1594
|
+
# "l": "0.01887700", # low price
|
|
1595
|
+
# "v": "173518.11900000", # base volume
|
|
1596
|
+
# "q": "3332.40703994", # quote volume
|
|
1597
|
+
# "O": 1579399197842, # open time
|
|
1598
|
+
# "C": 1579485597842, # close time
|
|
1599
|
+
# "F": 158251292, # first trade id
|
|
1600
|
+
# "L": 158414513, # last trade id
|
|
1601
|
+
# "n": 163222, # total number of trades
|
|
1602
|
+
# }
|
|
1603
|
+
#
|
|
1604
|
+
self.handle_tickers_and_bids_asks(client, message, 'tickers')
|
|
1605
|
+
|
|
1606
|
+
def handle_tickers_and_bids_asks(self, client: Client, message, methodType):
|
|
1607
|
+
isSpot = ((client.url.find('/stream') > -1) or (client.url.find('/testnet.binance') > -1))
|
|
1608
|
+
marketType = 'spot' if (isSpot) else 'contract'
|
|
1609
|
+
isBidAsk = (methodType == 'bidasks')
|
|
1610
|
+
channelName = None
|
|
1611
|
+
resolvedMessageHashes = []
|
|
1612
|
+
rawTickers = []
|
|
1613
|
+
newTickers: dict = {}
|
|
1614
|
+
if isinstance(message, list):
|
|
1615
|
+
rawTickers = message
|
|
1616
|
+
else:
|
|
1617
|
+
rawTickers.append(message)
|
|
1618
|
+
for i in range(0, len(rawTickers)):
|
|
1619
|
+
ticker = rawTickers[i]
|
|
1620
|
+
event = self.safe_string(ticker, 'e')
|
|
1621
|
+
if isBidAsk:
|
|
1622
|
+
event = 'bookTicker' # in `handleMessage`, bookTicker doesn't have identifier, so manually set here
|
|
1623
|
+
channelName = self.safe_string(self.options['tickerChannelsMap'], event, event)
|
|
1624
|
+
if channelName is None:
|
|
1625
|
+
continue
|
|
1626
|
+
parsedTicker = self.parse_ws_ticker(ticker, marketType)
|
|
1627
|
+
symbol = parsedTicker['symbol']
|
|
1628
|
+
newTickers[symbol] = parsedTicker
|
|
1629
|
+
if isBidAsk:
|
|
1630
|
+
self.bidsasks[symbol] = parsedTicker
|
|
1631
|
+
else:
|
|
1632
|
+
self.tickers[symbol] = parsedTicker
|
|
1633
|
+
messageHash = self.get_message_hash(channelName, symbol, isBidAsk)
|
|
1634
|
+
resolvedMessageHashes.append(messageHash)
|
|
1635
|
+
client.resolve(parsedTicker, messageHash)
|
|
1636
|
+
# resolve batch endpoint
|
|
1637
|
+
length = len(resolvedMessageHashes)
|
|
1638
|
+
if length > 0:
|
|
1639
|
+
batchMessageHash = self.get_message_hash(channelName, None, isBidAsk)
|
|
1640
|
+
client.resolve(newTickers, batchMessageHash)
|
|
1641
|
+
|
|
1642
|
+
def get_message_hash(self, channelName: str, symbol: Str, isBidAsk: bool):
|
|
1643
|
+
prefix = 'bidask' if isBidAsk else 'ticker'
|
|
1644
|
+
if symbol is not None:
|
|
1645
|
+
return prefix + ':' + channelName + '@' + symbol
|
|
1646
|
+
else:
|
|
1647
|
+
return prefix + 's' + ':' + channelName
|
|
1648
|
+
|
|
1649
|
+
def sign_params(self, params={}):
|
|
1650
|
+
self.check_required_credentials()
|
|
1651
|
+
extendedParams = self.extend({
|
|
1652
|
+
'timestamp': self.nonce(),
|
|
1653
|
+
'apiKey': self.apiKey,
|
|
1654
|
+
}, params)
|
|
1655
|
+
defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
|
|
1656
|
+
if defaultRecvWindow is not None:
|
|
1657
|
+
params['recvWindow'] = defaultRecvWindow
|
|
1658
|
+
recvWindow = self.safe_integer(params, 'recvWindow')
|
|
1659
|
+
if recvWindow is not None:
|
|
1660
|
+
params['recvWindow'] = recvWindow
|
|
1661
|
+
extendedParams = self.keysort(extendedParams)
|
|
1662
|
+
query = self.urlencode(extendedParams)
|
|
1663
|
+
signature = None
|
|
1664
|
+
if self.secret.find('PRIVATE KEY') > -1:
|
|
1665
|
+
if len(self.secret) > 120:
|
|
1666
|
+
signature = self.rsa(query, self.secret, 'sha256')
|
|
1667
|
+
else:
|
|
1668
|
+
signature = self.eddsa(self.encode(query), self.secret, 'ed25519')
|
|
1669
|
+
else:
|
|
1670
|
+
signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
|
|
1671
|
+
extendedParams['signature'] = signature
|
|
1672
|
+
return extendedParams
|
|
1673
|
+
|
|
1674
|
+
async def authenticate(self, params={}):
|
|
1675
|
+
time = self.milliseconds()
|
|
1676
|
+
type = None
|
|
1677
|
+
type, params = self.handle_market_type_and_params('authenticate', None, params)
|
|
1678
|
+
subType = None
|
|
1679
|
+
subType, params = self.handle_sub_type_and_params('authenticate', None, params)
|
|
1680
|
+
isPortfolioMargin = None
|
|
1681
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'authenticate', 'papi', 'portfolioMargin', False)
|
|
1682
|
+
if self.isLinear(type, subType):
|
|
1683
|
+
type = 'future'
|
|
1684
|
+
elif self.isInverse(type, subType):
|
|
1685
|
+
type = 'delivery'
|
|
1686
|
+
marginMode = None
|
|
1687
|
+
marginMode, params = self.handle_margin_mode_and_params('authenticate', params)
|
|
1688
|
+
isIsolatedMargin = (marginMode == 'isolated')
|
|
1689
|
+
isCrossMargin = (marginMode == 'cross') or (marginMode is None)
|
|
1690
|
+
symbol = self.safe_string(params, 'symbol')
|
|
1691
|
+
params = self.omit(params, 'symbol')
|
|
1692
|
+
options = self.safe_value(self.options, type, {})
|
|
1693
|
+
lastAuthenticatedTime = self.safe_integer(options, 'lastAuthenticatedTime', 0)
|
|
1694
|
+
listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000)
|
|
1695
|
+
delay = self.sum(listenKeyRefreshRate, 10000)
|
|
1696
|
+
if time - lastAuthenticatedTime > delay:
|
|
1697
|
+
response = None
|
|
1698
|
+
if isPortfolioMargin:
|
|
1699
|
+
response = await self.papiPostListenKey(params)
|
|
1700
|
+
elif type == 'future':
|
|
1701
|
+
response = await self.fapiPrivatePostListenKey(params)
|
|
1702
|
+
elif type == 'delivery':
|
|
1703
|
+
response = await self.dapiPrivatePostListenKey(params)
|
|
1704
|
+
elif type == 'margin' and isCrossMargin:
|
|
1705
|
+
response = await self.sapiPostUserDataStream(params)
|
|
1706
|
+
elif isIsolatedMargin:
|
|
1707
|
+
if symbol is None:
|
|
1708
|
+
raise ArgumentsRequired(self.id + ' authenticate() requires a symbol argument for isolated margin mode')
|
|
1709
|
+
marketId = self.market_id(symbol)
|
|
1710
|
+
params = self.extend(params, {'symbol': marketId})
|
|
1711
|
+
response = await self.sapiPostUserDataStreamIsolated(params)
|
|
1712
|
+
else:
|
|
1713
|
+
response = await self.publicPostUserDataStream(params)
|
|
1714
|
+
self.options[type] = self.extend(options, {
|
|
1715
|
+
'listenKey': self.safe_string(response, 'listenKey'),
|
|
1716
|
+
'lastAuthenticatedTime': time,
|
|
1717
|
+
})
|
|
1718
|
+
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
|
|
1719
|
+
|
|
1720
|
+
async def keep_alive_listen_key(self, params={}):
|
|
1721
|
+
# https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot
|
|
1722
|
+
type = self.safe_string_2(self.options, 'defaultType', 'authenticate', 'spot')
|
|
1723
|
+
type = self.safe_string(params, 'type', type)
|
|
1724
|
+
isPortfolioMargin = None
|
|
1725
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'keepAliveListenKey', 'papi', 'portfolioMargin', False)
|
|
1726
|
+
subTypeInfo = self.handle_sub_type_and_params('keepAliveListenKey', None, params)
|
|
1727
|
+
subType = subTypeInfo[0]
|
|
1728
|
+
if self.isLinear(type, subType):
|
|
1729
|
+
type = 'future'
|
|
1730
|
+
elif self.isInverse(type, subType):
|
|
1731
|
+
type = 'delivery'
|
|
1732
|
+
options = self.safe_value(self.options, type, {})
|
|
1733
|
+
listenKey = self.safe_string(options, 'listenKey')
|
|
1734
|
+
if listenKey is None:
|
|
1735
|
+
# A network error happened: we can't renew a listen key that does not exist.
|
|
1736
|
+
return
|
|
1737
|
+
request: dict = {}
|
|
1738
|
+
symbol = self.safe_string(params, 'symbol')
|
|
1739
|
+
params = self.omit(params, ['type', 'symbol'])
|
|
1740
|
+
time = self.milliseconds()
|
|
1741
|
+
try:
|
|
1742
|
+
if isPortfolioMargin:
|
|
1743
|
+
await self.papiPutListenKey(self.extend(request, params))
|
|
1744
|
+
elif type == 'future':
|
|
1745
|
+
await self.fapiPrivatePutListenKey(self.extend(request, params))
|
|
1746
|
+
elif type == 'delivery':
|
|
1747
|
+
await self.dapiPrivatePutListenKey(self.extend(request, params))
|
|
1748
|
+
else:
|
|
1749
|
+
request['listenKey'] = listenKey
|
|
1750
|
+
if type == 'margin':
|
|
1751
|
+
request['symbol'] = symbol
|
|
1752
|
+
await self.sapiPutUserDataStream(self.extend(request, params))
|
|
1753
|
+
else:
|
|
1754
|
+
await self.publicPutUserDataStream(self.extend(request, params))
|
|
1755
|
+
except Exception as error:
|
|
1756
|
+
urlType = type
|
|
1757
|
+
if isPortfolioMargin:
|
|
1758
|
+
urlType = 'papi'
|
|
1759
|
+
url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey']
|
|
1760
|
+
client = self.client(url)
|
|
1761
|
+
messageHashes = list(client.futures.keys())
|
|
1762
|
+
for i in range(0, len(messageHashes)):
|
|
1763
|
+
messageHash = messageHashes[i]
|
|
1764
|
+
client.reject(error, messageHash)
|
|
1765
|
+
self.options[type] = self.extend(options, {
|
|
1766
|
+
'listenKey': None,
|
|
1767
|
+
'lastAuthenticatedTime': 0,
|
|
1768
|
+
})
|
|
1769
|
+
return
|
|
1770
|
+
self.options[type] = self.extend(options, {
|
|
1771
|
+
'listenKey': listenKey,
|
|
1772
|
+
'lastAuthenticatedTime': time,
|
|
1773
|
+
})
|
|
1774
|
+
# whether or not to schedule another listenKey keepAlive request
|
|
1775
|
+
clients = list(self.clients.values())
|
|
1776
|
+
listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000)
|
|
1777
|
+
for i in range(0, len(clients)):
|
|
1778
|
+
client = clients[i]
|
|
1779
|
+
subscriptionKeys = list(client.subscriptions.keys())
|
|
1780
|
+
for j in range(0, len(subscriptionKeys)):
|
|
1781
|
+
subscribeType = subscriptionKeys[j]
|
|
1782
|
+
if subscribeType == type:
|
|
1783
|
+
self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)
|
|
1784
|
+
return
|
|
1785
|
+
|
|
1786
|
+
def set_balance_cache(self, client: Client, type, isPortfolioMargin=False):
|
|
1787
|
+
if type in client.subscriptions:
|
|
1788
|
+
return
|
|
1789
|
+
options = self.safe_value(self.options, 'watchBalance')
|
|
1790
|
+
fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
|
|
1791
|
+
if fetchBalanceSnapshot:
|
|
1792
|
+
messageHash = type + ':fetchBalanceSnapshot'
|
|
1793
|
+
if not (messageHash in client.futures):
|
|
1794
|
+
client.future(messageHash)
|
|
1795
|
+
self.spawn(self.load_balance_snapshot, client, messageHash, type, isPortfolioMargin)
|
|
1796
|
+
else:
|
|
1797
|
+
self.balance[type] = {}
|
|
1798
|
+
|
|
1799
|
+
async def load_balance_snapshot(self, client, messageHash, type, isPortfolioMargin):
|
|
1800
|
+
params: dict = {
|
|
1801
|
+
'type': type,
|
|
1802
|
+
}
|
|
1803
|
+
if isPortfolioMargin:
|
|
1804
|
+
params['portfolioMargin'] = True
|
|
1805
|
+
response = await self.fetch_balance(params)
|
|
1806
|
+
self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
|
|
1807
|
+
# don't remove the future from the .futures cache
|
|
1808
|
+
future = client.futures[messageHash]
|
|
1809
|
+
future.resolve()
|
|
1810
|
+
client.resolve(self.balance[type], type + ':balance')
|
|
1811
|
+
|
|
1812
|
+
async def fetch_balance_ws(self, params={}) -> Balances:
|
|
1813
|
+
"""
|
|
1814
|
+
fetch balance and get the amount of funds available for trading or funds locked in orders
|
|
1815
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#account-information-user_data
|
|
1816
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#account-information-user_data
|
|
1817
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#futures-account-balance-user_data
|
|
1818
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1819
|
+
:param str|None [params.type]: 'future', 'delivery', 'savings', 'funding', or 'spot'
|
|
1820
|
+
:param str|None [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
|
|
1821
|
+
:param str[]|None [params.symbols]: unified market symbols, only used in isolated margin mode
|
|
1822
|
+
:param str|None [params.method]: method to use. Can be account.balance or account.status
|
|
1823
|
+
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
1824
|
+
"""
|
|
1825
|
+
await self.load_markets()
|
|
1826
|
+
type = self.get_market_type('fetchBalanceWs', None, params)
|
|
1827
|
+
if type != 'spot' and type != 'future':
|
|
1828
|
+
raise BadRequest(self.id + ' fetchBalanceWs only supports spot or swap markets')
|
|
1829
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
1830
|
+
requestId = self.request_id(url)
|
|
1831
|
+
messageHash = str(requestId)
|
|
1832
|
+
returnRateLimits = False
|
|
1833
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchBalanceWs', 'returnRateLimits', False)
|
|
1834
|
+
payload: dict = {
|
|
1835
|
+
'returnRateLimits': returnRateLimits,
|
|
1836
|
+
}
|
|
1837
|
+
method = None
|
|
1838
|
+
method, params = self.handle_option_and_params(params, 'fetchBalanceWs', 'method', 'account.status')
|
|
1839
|
+
message: dict = {
|
|
1840
|
+
'id': messageHash,
|
|
1841
|
+
'method': method,
|
|
1842
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
1843
|
+
}
|
|
1844
|
+
subscription: dict = {
|
|
1845
|
+
'method': self.handle_account_status_ws if (method == 'account.status') else self.handle_balance_ws,
|
|
1846
|
+
}
|
|
1847
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
1848
|
+
|
|
1849
|
+
def handle_balance_ws(self, client: Client, message):
|
|
1850
|
+
#
|
|
1851
|
+
#
|
|
1852
|
+
messageHash = self.safe_string(message, 'id')
|
|
1853
|
+
result = self.safe_dict(message, 'result', {})
|
|
1854
|
+
rawBalance = self.safe_list(result, 0, [])
|
|
1855
|
+
parsedBalances = self.parseBalanceCustom(rawBalance)
|
|
1856
|
+
client.resolve(parsedBalances, messageHash)
|
|
1857
|
+
|
|
1858
|
+
def handle_account_status_ws(self, client: Client, message):
|
|
1859
|
+
#
|
|
1860
|
+
# spot
|
|
1861
|
+
# {
|
|
1862
|
+
# "id": "605a6d20-6588-4cb9-afa0-b0ab087507ba",
|
|
1863
|
+
# "status": 200,
|
|
1864
|
+
# "result": {
|
|
1865
|
+
# "makerCommission": 15,
|
|
1866
|
+
# "takerCommission": 15,
|
|
1867
|
+
# "buyerCommission": 0,
|
|
1868
|
+
# "sellerCommission": 0,
|
|
1869
|
+
# "canTrade": True,
|
|
1870
|
+
# "canWithdraw": True,
|
|
1871
|
+
# "canDeposit": True,
|
|
1872
|
+
# "commissionRates": {
|
|
1873
|
+
# "maker": "0.00150000",
|
|
1874
|
+
# "taker": "0.00150000",
|
|
1875
|
+
# "buyer": "0.00000000",
|
|
1876
|
+
# "seller": "0.00000000"
|
|
1877
|
+
# },
|
|
1878
|
+
# "brokered": False,
|
|
1879
|
+
# "requireSelfTradePrevention": False,
|
|
1880
|
+
# "updateTime": 1660801833000,
|
|
1881
|
+
# "accountType": "SPOT",
|
|
1882
|
+
# "balances": [{
|
|
1883
|
+
# "asset": "BNB",
|
|
1884
|
+
# "free": "0.00000000",
|
|
1885
|
+
# "locked": "0.00000000"
|
|
1886
|
+
# },
|
|
1887
|
+
# {
|
|
1888
|
+
# "asset": "BTC",
|
|
1889
|
+
# "free": "1.3447112",
|
|
1890
|
+
# "locked": "0.08600000"
|
|
1891
|
+
# },
|
|
1892
|
+
# {
|
|
1893
|
+
# "asset": "USDT",
|
|
1894
|
+
# "free": "1021.21000000",
|
|
1895
|
+
# "locked": "0.00000000"
|
|
1896
|
+
# }
|
|
1897
|
+
# ],
|
|
1898
|
+
# "permissions": [
|
|
1899
|
+
# "SPOT"
|
|
1900
|
+
# ]
|
|
1901
|
+
# }
|
|
1902
|
+
# }
|
|
1903
|
+
# swap
|
|
1904
|
+
#
|
|
1905
|
+
messageHash = self.safe_string(message, 'id')
|
|
1906
|
+
result = self.safe_dict(message, 'result', {})
|
|
1907
|
+
parsedBalances = self.parseBalanceCustom(result)
|
|
1908
|
+
client.resolve(parsedBalances, messageHash)
|
|
1909
|
+
|
|
1910
|
+
async def fetch_position_ws(self, symbol: str, params={}) -> List[Position]:
|
|
1911
|
+
"""
|
|
1912
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#position-information-user_data
|
|
1913
|
+
fetch data on an open position
|
|
1914
|
+
:param str symbol: unified market symbol of the market the position is held in
|
|
1915
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1916
|
+
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
1917
|
+
"""
|
|
1918
|
+
return await self.fetch_positions_ws([symbol], params)
|
|
1919
|
+
|
|
1920
|
+
async def fetch_positions_ws(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
1921
|
+
"""
|
|
1922
|
+
fetch all open positions
|
|
1923
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#position-information-user_data
|
|
1924
|
+
:param str[] [symbols]: list of unified market symbols
|
|
1925
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1926
|
+
:param boolean [params.returnRateLimits]: set to True to return rate limit informations, defaults to False.
|
|
1927
|
+
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
1928
|
+
"""
|
|
1929
|
+
await self.load_markets()
|
|
1930
|
+
symbols = self.market_symbols(symbols, 'swap', True, True, True)
|
|
1931
|
+
url = self.urls['api']['ws']['ws-api']['future']
|
|
1932
|
+
requestId = self.request_id(url)
|
|
1933
|
+
messageHash = str(requestId)
|
|
1934
|
+
payload: dict = {}
|
|
1935
|
+
if symbols is not None:
|
|
1936
|
+
symbolsLength = len(symbols)
|
|
1937
|
+
if symbolsLength == 1:
|
|
1938
|
+
payload['symbol'] = self.market_id(symbols[0])
|
|
1939
|
+
returnRateLimits = False
|
|
1940
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchPositionsWs', 'returnRateLimits', False)
|
|
1941
|
+
payload['returnRateLimits'] = returnRateLimits
|
|
1942
|
+
message: dict = {
|
|
1943
|
+
'id': messageHash,
|
|
1944
|
+
'method': 'account.position',
|
|
1945
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
1946
|
+
}
|
|
1947
|
+
subscription: dict = {
|
|
1948
|
+
'method': self.handle_positions_ws,
|
|
1949
|
+
}
|
|
1950
|
+
result = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
1951
|
+
return self.filter_by_array_positions(result, 'symbol', symbols, False)
|
|
1952
|
+
|
|
1953
|
+
def handle_positions_ws(self, client: Client, message):
|
|
1954
|
+
#
|
|
1955
|
+
# {
|
|
1956
|
+
# id: '1',
|
|
1957
|
+
# status: 200,
|
|
1958
|
+
# result: [
|
|
1959
|
+
# {
|
|
1960
|
+
# symbol: 'BTCUSDT',
|
|
1961
|
+
# positionAmt: '-0.014',
|
|
1962
|
+
# entryPrice: '42901.1',
|
|
1963
|
+
# breakEvenPrice: '30138.83333142',
|
|
1964
|
+
# markPrice: '71055.98470333',
|
|
1965
|
+
# unRealizedProfit: '-394.16838584',
|
|
1966
|
+
# liquidationPrice: '137032.02272908',
|
|
1967
|
+
# leverage: '123',
|
|
1968
|
+
# maxNotionalValue: '50000',
|
|
1969
|
+
# marginType: 'cross',
|
|
1970
|
+
# isolatedMargin: '0.00000000',
|
|
1971
|
+
# isAutoAddMargin: 'false',
|
|
1972
|
+
# positionSide: 'BOTH',
|
|
1973
|
+
# notional: '-994.78378584',
|
|
1974
|
+
# isolatedWallet: '0',
|
|
1975
|
+
# updateTime: 1708906343111,
|
|
1976
|
+
# isolated: False,
|
|
1977
|
+
# adlQuantile: 2
|
|
1978
|
+
# },
|
|
1979
|
+
# ...
|
|
1980
|
+
# ]
|
|
1981
|
+
# }
|
|
1982
|
+
#
|
|
1983
|
+
#
|
|
1984
|
+
messageHash = self.safe_string(message, 'id')
|
|
1985
|
+
result = self.safe_list(message, 'result', [])
|
|
1986
|
+
positions = []
|
|
1987
|
+
for i in range(0, len(result)):
|
|
1988
|
+
parsed = self.parse_position_risk(result[i])
|
|
1989
|
+
entryPrice = self.safe_string(parsed, 'entryPrice')
|
|
1990
|
+
if (entryPrice != '0') and (entryPrice != '0.0') and (entryPrice != '0.00000000'):
|
|
1991
|
+
positions.append(parsed)
|
|
1992
|
+
client.resolve(positions, messageHash)
|
|
1993
|
+
|
|
1994
|
+
async def watch_balance(self, params={}) -> Balances:
|
|
1995
|
+
"""
|
|
1996
|
+
watch balance and get the amount of funds available for trading or funds locked in orders
|
|
1997
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
1998
|
+
:param boolean [params.portfolioMargin]: set to True if you would like to watch the balance of a portfolio margin account
|
|
1999
|
+
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
2000
|
+
"""
|
|
2001
|
+
await self.load_markets()
|
|
2002
|
+
await self.authenticate(params)
|
|
2003
|
+
defaultType = self.safe_string(self.options, 'defaultType', 'spot')
|
|
2004
|
+
type = self.safe_string(params, 'type', defaultType)
|
|
2005
|
+
subType = None
|
|
2006
|
+
subType, params = self.handle_sub_type_and_params('watchBalance', None, params)
|
|
2007
|
+
isPortfolioMargin = None
|
|
2008
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchBalance', 'papi', 'portfolioMargin', False)
|
|
2009
|
+
urlType = type
|
|
2010
|
+
if isPortfolioMargin:
|
|
2011
|
+
urlType = 'papi'
|
|
2012
|
+
if self.isLinear(type, subType):
|
|
2013
|
+
type = 'future'
|
|
2014
|
+
elif self.isInverse(type, subType):
|
|
2015
|
+
type = 'delivery'
|
|
2016
|
+
url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey']
|
|
2017
|
+
client = self.client(url)
|
|
2018
|
+
self.set_balance_cache(client, type, isPortfolioMargin)
|
|
2019
|
+
self.set_positions_cache(client, type, None, isPortfolioMargin)
|
|
2020
|
+
options = self.safe_dict(self.options, 'watchBalance')
|
|
2021
|
+
fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
|
|
2022
|
+
awaitBalanceSnapshot = self.safe_bool(options, 'awaitBalanceSnapshot', True)
|
|
2023
|
+
if fetchBalanceSnapshot and awaitBalanceSnapshot:
|
|
2024
|
+
await client.future(type + ':fetchBalanceSnapshot')
|
|
2025
|
+
messageHash = type + ':balance'
|
|
2026
|
+
message = None
|
|
2027
|
+
return await self.watch(url, messageHash, message, type)
|
|
2028
|
+
|
|
2029
|
+
def handle_balance(self, client: Client, message):
|
|
2030
|
+
#
|
|
2031
|
+
# sent upon a balance update not related to orders
|
|
2032
|
+
#
|
|
2033
|
+
# {
|
|
2034
|
+
# "e": "balanceUpdate",
|
|
2035
|
+
# "E": 1629352505586,
|
|
2036
|
+
# "a": "IOTX",
|
|
2037
|
+
# "d": "0.43750000",
|
|
2038
|
+
# "T": 1629352505585
|
|
2039
|
+
# }
|
|
2040
|
+
#
|
|
2041
|
+
# sent upon creating or filling an order
|
|
2042
|
+
#
|
|
2043
|
+
# {
|
|
2044
|
+
# "e": "outboundAccountPosition", # Event type
|
|
2045
|
+
# "E": 1564034571105, # Event Time
|
|
2046
|
+
# "u": 1564034571073, # Time of last account update
|
|
2047
|
+
# "B": [ # Balances Array
|
|
2048
|
+
# {
|
|
2049
|
+
# "a": "ETH", # Asset
|
|
2050
|
+
# "f": "10000.000000", # Free
|
|
2051
|
+
# "l": "0.000000" # Locked
|
|
2052
|
+
# }
|
|
2053
|
+
# ]
|
|
2054
|
+
# }
|
|
2055
|
+
#
|
|
2056
|
+
# future/delivery
|
|
2057
|
+
#
|
|
2058
|
+
# {
|
|
2059
|
+
# "e": "ACCOUNT_UPDATE", # Event Type
|
|
2060
|
+
# "E": 1564745798939, # Event Time
|
|
2061
|
+
# "T": 1564745798938 , # Transaction
|
|
2062
|
+
# "i": "SfsR", # Account Alias
|
|
2063
|
+
# "a": { # Update Data
|
|
2064
|
+
# "m":"ORDER", # Event reason type
|
|
2065
|
+
# "B":[ # Balances
|
|
2066
|
+
# {
|
|
2067
|
+
# "a":"BTC", # Asset
|
|
2068
|
+
# "wb":"122624.12345678", # Wallet Balance
|
|
2069
|
+
# "cw":"100.12345678" # Cross Wallet Balance
|
|
2070
|
+
# },
|
|
2071
|
+
# ],
|
|
2072
|
+
# "P":[
|
|
2073
|
+
# {
|
|
2074
|
+
# "s":"BTCUSD_200925", # Symbol
|
|
2075
|
+
# "pa":"0", # Position Amount
|
|
2076
|
+
# "ep":"0.0", # Entry Price
|
|
2077
|
+
# "cr":"200", #(Pre-fee) Accumulated Realized
|
|
2078
|
+
# "up":"0", # Unrealized PnL
|
|
2079
|
+
# "mt":"isolated", # Margin Type
|
|
2080
|
+
# "iw":"0.00000000", # Isolated Wallet(if isolated position)
|
|
2081
|
+
# "ps":"BOTH" # Position Side
|
|
2082
|
+
# },
|
|
2083
|
+
# ]
|
|
2084
|
+
# }
|
|
2085
|
+
# }
|
|
2086
|
+
#
|
|
2087
|
+
wallet = self.safe_string(self.options, 'wallet', 'wb') # cw for cross wallet
|
|
2088
|
+
# each account is connected to a different endpoint
|
|
2089
|
+
# and has exactly one subscriptionhash which is the account type
|
|
2090
|
+
subscriptions = list(client.subscriptions.keys())
|
|
2091
|
+
accountType = subscriptions[0]
|
|
2092
|
+
messageHash = accountType + ':balance'
|
|
2093
|
+
if self.balance[accountType] is None:
|
|
2094
|
+
self.balance[accountType] = {}
|
|
2095
|
+
self.balance[accountType]['info'] = message
|
|
2096
|
+
event = self.safe_string(message, 'e')
|
|
2097
|
+
if event == 'balanceUpdate':
|
|
2098
|
+
currencyId = self.safe_string(message, 'a')
|
|
2099
|
+
code = self.safe_currency_code(currencyId)
|
|
2100
|
+
account = self.account()
|
|
2101
|
+
delta = self.safe_string(message, 'd')
|
|
2102
|
+
if code in self.balance[accountType]:
|
|
2103
|
+
previousValue = self.balance[accountType][code]['free']
|
|
2104
|
+
if not isinstance(previousValue, str):
|
|
2105
|
+
previousValue = self.number_to_string(previousValue)
|
|
2106
|
+
account['free'] = Precise.string_add(previousValue, delta)
|
|
2107
|
+
else:
|
|
2108
|
+
account['free'] = delta
|
|
2109
|
+
self.balance[accountType][code] = account
|
|
2110
|
+
else:
|
|
2111
|
+
message = self.safe_dict(message, 'a', message)
|
|
2112
|
+
B = self.safe_list(message, 'B')
|
|
2113
|
+
for i in range(0, len(B)):
|
|
2114
|
+
entry = B[i]
|
|
2115
|
+
currencyId = self.safe_string(entry, 'a')
|
|
2116
|
+
code = self.safe_currency_code(currencyId)
|
|
2117
|
+
account = self.account()
|
|
2118
|
+
account['free'] = self.safe_string(entry, 'f')
|
|
2119
|
+
account['used'] = self.safe_string(entry, 'l')
|
|
2120
|
+
account['total'] = self.safe_string(entry, wallet)
|
|
2121
|
+
self.balance[accountType][code] = account
|
|
2122
|
+
timestamp = self.safe_integer(message, 'E')
|
|
2123
|
+
self.balance[accountType]['timestamp'] = timestamp
|
|
2124
|
+
self.balance[accountType]['datetime'] = self.iso8601(timestamp)
|
|
2125
|
+
self.balance[accountType] = self.safe_balance(self.balance[accountType])
|
|
2126
|
+
client.resolve(self.balance[accountType], messageHash)
|
|
2127
|
+
|
|
2128
|
+
def get_market_type(self, method, market, params={}):
|
|
2129
|
+
type = None
|
|
2130
|
+
type, params = self.handle_market_type_and_params(method, market, params)
|
|
2131
|
+
subType = None
|
|
2132
|
+
subType, params = self.handle_sub_type_and_params(method, market, params)
|
|
2133
|
+
if self.isLinear(type, subType):
|
|
2134
|
+
type = 'future'
|
|
2135
|
+
elif self.isInverse(type, subType):
|
|
2136
|
+
type = 'delivery'
|
|
2137
|
+
return type
|
|
2138
|
+
|
|
2139
|
+
async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
|
2140
|
+
"""
|
|
2141
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#place-new-order-trade
|
|
2142
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#new-order-trade-2
|
|
2143
|
+
create a trade order
|
|
2144
|
+
:param str symbol: unified symbol of the market to create an order in
|
|
2145
|
+
:param str type: 'market' or 'limit'
|
|
2146
|
+
:param str side: 'buy' or 'sell'
|
|
2147
|
+
:param float amount: how much of currency you want to trade in units of base currency
|
|
2148
|
+
:param float|None [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
|
|
2149
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2150
|
+
:param boolean params['test']: test order, default False
|
|
2151
|
+
:param boolean params['returnRateLimits']: set to True to return rate limit information, default False
|
|
2152
|
+
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2153
|
+
"""
|
|
2154
|
+
await self.load_markets()
|
|
2155
|
+
market = self.market(symbol)
|
|
2156
|
+
marketType = self.get_market_type('createOrderWs', market, params)
|
|
2157
|
+
if marketType != 'spot' and marketType != 'future':
|
|
2158
|
+
raise BadRequest(self.id + ' createOrderWs only supports spot or swap markets')
|
|
2159
|
+
url = self.urls['api']['ws']['ws-api'][marketType]
|
|
2160
|
+
requestId = self.request_id(url)
|
|
2161
|
+
messageHash = str(requestId)
|
|
2162
|
+
sor = self.safe_bool_2(params, 'sor', 'SOR', False)
|
|
2163
|
+
params = self.omit(params, 'sor', 'SOR')
|
|
2164
|
+
payload = self.create_order_request(symbol, type, side, amount, price, params)
|
|
2165
|
+
returnRateLimits = False
|
|
2166
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'createOrderWs', 'returnRateLimits', False)
|
|
2167
|
+
payload['returnRateLimits'] = returnRateLimits
|
|
2168
|
+
test = self.safe_bool(params, 'test', False)
|
|
2169
|
+
params = self.omit(params, 'test')
|
|
2170
|
+
message: dict = {
|
|
2171
|
+
'id': messageHash,
|
|
2172
|
+
'method': 'order.place',
|
|
2173
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2174
|
+
}
|
|
2175
|
+
if test:
|
|
2176
|
+
if sor:
|
|
2177
|
+
message['method'] = 'sor.order.test'
|
|
2178
|
+
else:
|
|
2179
|
+
message['method'] = 'order.test'
|
|
2180
|
+
subscription: dict = {
|
|
2181
|
+
'method': self.handle_order_ws,
|
|
2182
|
+
}
|
|
2183
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2184
|
+
|
|
2185
|
+
def handle_order_ws(self, client: Client, message):
|
|
2186
|
+
#
|
|
2187
|
+
# {
|
|
2188
|
+
# "id": 1,
|
|
2189
|
+
# "status": 200,
|
|
2190
|
+
# "result": {
|
|
2191
|
+
# "symbol": "BTCUSDT",
|
|
2192
|
+
# "orderId": 7663053,
|
|
2193
|
+
# "orderListId": -1,
|
|
2194
|
+
# "clientOrderId": "x-R4BD3S82d8959d0f5114499487a614",
|
|
2195
|
+
# "transactTime": 1687642291434,
|
|
2196
|
+
# "price": "25000.00000000",
|
|
2197
|
+
# "origQty": "0.00100000",
|
|
2198
|
+
# "executedQty": "0.00000000",
|
|
2199
|
+
# "cummulativeQuoteQty": "0.00000000",
|
|
2200
|
+
# "status": "NEW",
|
|
2201
|
+
# "timeInForce": "GTC",
|
|
2202
|
+
# "type": "LIMIT",
|
|
2203
|
+
# "side": "BUY",
|
|
2204
|
+
# "workingTime": 1687642291434,
|
|
2205
|
+
# "fills": [],
|
|
2206
|
+
# "selfTradePreventionMode": "NONE"
|
|
2207
|
+
# },
|
|
2208
|
+
# "rateLimits": [
|
|
2209
|
+
# {
|
|
2210
|
+
# "rateLimitType": "ORDERS",
|
|
2211
|
+
# "interval": "SECOND",
|
|
2212
|
+
# "intervalNum": 10,
|
|
2213
|
+
# "limit": 50,
|
|
2214
|
+
# "count": 1
|
|
2215
|
+
# },
|
|
2216
|
+
# {
|
|
2217
|
+
# "rateLimitType": "ORDERS",
|
|
2218
|
+
# "interval": "DAY",
|
|
2219
|
+
# "intervalNum": 1,
|
|
2220
|
+
# "limit": 160000,
|
|
2221
|
+
# "count": 1
|
|
2222
|
+
# },
|
|
2223
|
+
# {
|
|
2224
|
+
# "rateLimitType": "REQUEST_WEIGHT",
|
|
2225
|
+
# "interval": "MINUTE",
|
|
2226
|
+
# "intervalNum": 1,
|
|
2227
|
+
# "limit": 1200,
|
|
2228
|
+
# "count": 12
|
|
2229
|
+
# }
|
|
2230
|
+
# ]
|
|
2231
|
+
# }
|
|
2232
|
+
#
|
|
2233
|
+
messageHash = self.safe_string(message, 'id')
|
|
2234
|
+
result = self.safe_dict(message, 'result', {})
|
|
2235
|
+
order = self.parse_order(result)
|
|
2236
|
+
client.resolve(order, messageHash)
|
|
2237
|
+
|
|
2238
|
+
def handle_orders_ws(self, client: Client, message):
|
|
2239
|
+
#
|
|
2240
|
+
# {
|
|
2241
|
+
# "id": 1,
|
|
2242
|
+
# "status": 200,
|
|
2243
|
+
# "result": [{
|
|
2244
|
+
# "symbol": "BTCUSDT",
|
|
2245
|
+
# "orderId": 7665584,
|
|
2246
|
+
# "orderListId": -1,
|
|
2247
|
+
# "clientOrderId": "x-R4BD3S82b54769abdd3e4b57874c52",
|
|
2248
|
+
# "price": "26000.00000000",
|
|
2249
|
+
# "origQty": "0.00100000",
|
|
2250
|
+
# "executedQty": "0.00000000",
|
|
2251
|
+
# "cummulativeQuoteQty": "0.00000000",
|
|
2252
|
+
# "status": "NEW",
|
|
2253
|
+
# "timeInForce": "GTC",
|
|
2254
|
+
# "type": "LIMIT",
|
|
2255
|
+
# "side": "BUY",
|
|
2256
|
+
# "stopPrice": "0.00000000",
|
|
2257
|
+
# "icebergQty": "0.00000000",
|
|
2258
|
+
# "time": 1687642884646,
|
|
2259
|
+
# "updateTime": 1687642884646,
|
|
2260
|
+
# "isWorking": True,
|
|
2261
|
+
# "workingTime": 1687642884646,
|
|
2262
|
+
# "origQuoteOrderQty": "0.00000000",
|
|
2263
|
+
# "selfTradePreventionMode": "NONE"
|
|
2264
|
+
# },
|
|
2265
|
+
# ...
|
|
2266
|
+
# ],
|
|
2267
|
+
# "rateLimits": [{
|
|
2268
|
+
# "rateLimitType": "REQUEST_WEIGHT",
|
|
2269
|
+
# "interval": "MINUTE",
|
|
2270
|
+
# "intervalNum": 1,
|
|
2271
|
+
# "limit": 1200,
|
|
2272
|
+
# "count": 14
|
|
2273
|
+
# }]
|
|
2274
|
+
# }
|
|
2275
|
+
#
|
|
2276
|
+
messageHash = self.safe_string(message, 'id')
|
|
2277
|
+
result = self.safe_list(message, 'result', [])
|
|
2278
|
+
orders = self.parse_orders(result)
|
|
2279
|
+
client.resolve(orders, messageHash)
|
|
2280
|
+
|
|
2281
|
+
async def edit_order_ws(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
|
|
2282
|
+
"""
|
|
2283
|
+
edit a trade order
|
|
2284
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#cancel-and-replace-order-trade
|
|
2285
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#modify-order-trade-2
|
|
2286
|
+
:param str id: order id
|
|
2287
|
+
:param str symbol: unified symbol of the market to create an order in
|
|
2288
|
+
:param str type: 'market' or 'limit'
|
|
2289
|
+
:param str side: 'buy' or 'sell'
|
|
2290
|
+
:param float amount: how much of the currency you want to trade in units of the base currency
|
|
2291
|
+
:param float|None [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
|
|
2292
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2293
|
+
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2294
|
+
"""
|
|
2295
|
+
await self.load_markets()
|
|
2296
|
+
market = self.market(symbol)
|
|
2297
|
+
marketType = self.get_market_type('editOrderWs', market, params)
|
|
2298
|
+
if marketType != 'spot' and marketType != 'future':
|
|
2299
|
+
raise BadRequest(self.id + ' editOrderWs only supports spot or swap markets')
|
|
2300
|
+
url = self.urls['api']['ws']['ws-api'][marketType]
|
|
2301
|
+
requestId = self.request_id(url)
|
|
2302
|
+
messageHash = str(requestId)
|
|
2303
|
+
payload = None
|
|
2304
|
+
if marketType == 'spot':
|
|
2305
|
+
payload = self.editSpotOrderRequest(id, symbol, type, side, amount, price, params)
|
|
2306
|
+
elif marketType == 'future':
|
|
2307
|
+
payload = self.editContractOrderRequest(id, symbol, type, side, amount, price, params)
|
|
2308
|
+
returnRateLimits = False
|
|
2309
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'editOrderWs', 'returnRateLimits', False)
|
|
2310
|
+
payload['returnRateLimits'] = returnRateLimits
|
|
2311
|
+
message: dict = {
|
|
2312
|
+
'id': messageHash,
|
|
2313
|
+
'method': 'order.modify' if (marketType == 'future') else 'order.cancelReplace',
|
|
2314
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2315
|
+
}
|
|
2316
|
+
subscription: dict = {
|
|
2317
|
+
'method': self.handle_edit_order_ws,
|
|
2318
|
+
}
|
|
2319
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2320
|
+
|
|
2321
|
+
def handle_edit_order_ws(self, client: Client, message):
|
|
2322
|
+
#
|
|
2323
|
+
# spot
|
|
2324
|
+
# {
|
|
2325
|
+
# "id": 1,
|
|
2326
|
+
# "status": 200,
|
|
2327
|
+
# "result": {
|
|
2328
|
+
# "cancelResult": "SUCCESS",
|
|
2329
|
+
# "newOrderResult": "SUCCESS",
|
|
2330
|
+
# "cancelResponse": {
|
|
2331
|
+
# "symbol": "BTCUSDT",
|
|
2332
|
+
# "origClientOrderId": "x-R4BD3S82813c5d7ffa594104917de2",
|
|
2333
|
+
# "orderId": 7665177,
|
|
2334
|
+
# "orderListId": -1,
|
|
2335
|
+
# "clientOrderId": "mbrnbQsQhtCXCLY45d5q7S",
|
|
2336
|
+
# "price": "26000.00000000",
|
|
2337
|
+
# "origQty": "0.00100000",
|
|
2338
|
+
# "executedQty": "0.00000000",
|
|
2339
|
+
# "cummulativeQuoteQty": "0.00000000",
|
|
2340
|
+
# "status": "CANCELED",
|
|
2341
|
+
# "timeInForce": "GTC",
|
|
2342
|
+
# "type": "LIMIT",
|
|
2343
|
+
# "side": "BUY",
|
|
2344
|
+
# "selfTradePreventionMode": "NONE"
|
|
2345
|
+
# },
|
|
2346
|
+
# "newOrderResponse": {
|
|
2347
|
+
# "symbol": "BTCUSDT",
|
|
2348
|
+
# "orderId": 7665584,
|
|
2349
|
+
# "orderListId": -1,
|
|
2350
|
+
# "clientOrderId": "x-R4BD3S82b54769abdd3e4b57874c52",
|
|
2351
|
+
# "transactTime": 1687642884646,
|
|
2352
|
+
# "price": "26000.00000000",
|
|
2353
|
+
# "origQty": "0.00100000",
|
|
2354
|
+
# "executedQty": "0.00000000",
|
|
2355
|
+
# "cummulativeQuoteQty": "0.00000000",
|
|
2356
|
+
# "status": "NEW",
|
|
2357
|
+
# "timeInForce": "GTC",
|
|
2358
|
+
# "type": "LIMIT",
|
|
2359
|
+
# "side": "BUY",
|
|
2360
|
+
# "workingTime": 1687642884646,
|
|
2361
|
+
# "fills": [],
|
|
2362
|
+
# "selfTradePreventionMode": "NONE"
|
|
2363
|
+
# }
|
|
2364
|
+
# },
|
|
2365
|
+
# "rateLimits": [{
|
|
2366
|
+
# "rateLimitType": "ORDERS",
|
|
2367
|
+
# "interval": "SECOND",
|
|
2368
|
+
# "intervalNum": 10,
|
|
2369
|
+
# "limit": 50,
|
|
2370
|
+
# "count": 1
|
|
2371
|
+
# },
|
|
2372
|
+
# {
|
|
2373
|
+
# "rateLimitType": "ORDERS",
|
|
2374
|
+
# "interval": "DAY",
|
|
2375
|
+
# "intervalNum": 1,
|
|
2376
|
+
# "limit": 160000,
|
|
2377
|
+
# "count": 3
|
|
2378
|
+
# },
|
|
2379
|
+
# {
|
|
2380
|
+
# "rateLimitType": "REQUEST_WEIGHT",
|
|
2381
|
+
# "interval": "MINUTE",
|
|
2382
|
+
# "intervalNum": 1,
|
|
2383
|
+
# "limit": 1200,
|
|
2384
|
+
# "count": 12
|
|
2385
|
+
# }
|
|
2386
|
+
# ]
|
|
2387
|
+
# }
|
|
2388
|
+
# swap
|
|
2389
|
+
# {
|
|
2390
|
+
# "id":"1",
|
|
2391
|
+
# "status":200,
|
|
2392
|
+
# "result":{
|
|
2393
|
+
# "orderId":667061487,
|
|
2394
|
+
# "symbol":"LTCUSDT",
|
|
2395
|
+
# "status":"NEW",
|
|
2396
|
+
# "clientOrderId":"x-xcKtGhcu91a74c818749ee42c0f70",
|
|
2397
|
+
# "price":"82.00",
|
|
2398
|
+
# "avgPrice":"0.00",
|
|
2399
|
+
# "origQty":"1.000",
|
|
2400
|
+
# "executedQty":"0.000",
|
|
2401
|
+
# "cumQty":"0.000",
|
|
2402
|
+
# "cumQuote":"0.00000",
|
|
2403
|
+
# "timeInForce":"GTC",
|
|
2404
|
+
# "type":"LIMIT",
|
|
2405
|
+
# "reduceOnly":false,
|
|
2406
|
+
# "closePosition":false,
|
|
2407
|
+
# "side":"BUY",
|
|
2408
|
+
# "positionSide":"BOTH",
|
|
2409
|
+
# "stopPrice":"0.00",
|
|
2410
|
+
# "workingType":"CONTRACT_PRICE",
|
|
2411
|
+
# "priceProtect":false,
|
|
2412
|
+
# "origType":"LIMIT",
|
|
2413
|
+
# "priceMatch":"NONE",
|
|
2414
|
+
# "selfTradePreventionMode":"NONE",
|
|
2415
|
+
# "goodTillDate":0,
|
|
2416
|
+
# "updateTime":1712918927511
|
|
2417
|
+
# }
|
|
2418
|
+
# }
|
|
2419
|
+
#
|
|
2420
|
+
messageHash = self.safe_string(message, 'id')
|
|
2421
|
+
result = self.safe_dict(message, 'result', {})
|
|
2422
|
+
newSpotOrder = self.safe_dict(result, 'newOrderResponse')
|
|
2423
|
+
order = None
|
|
2424
|
+
if newSpotOrder is not None:
|
|
2425
|
+
order = self.parse_order(newSpotOrder)
|
|
2426
|
+
else:
|
|
2427
|
+
order = self.parse_order(result)
|
|
2428
|
+
client.resolve(order, messageHash)
|
|
2429
|
+
|
|
2430
|
+
async def cancel_order_ws(self, id: str, symbol: Str = None, params={}) -> Order:
|
|
2431
|
+
"""
|
|
2432
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#cancel-order-trade
|
|
2433
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#cancel-order-trade-2
|
|
2434
|
+
cancel multiple orders
|
|
2435
|
+
:param str id: order id
|
|
2436
|
+
:param str symbol: unified market symbol, default is None
|
|
2437
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2438
|
+
:param str|None [params.cancelRestrictions]: Supported values: ONLY_NEW - Cancel will succeed if the order status is NEW. ONLY_PARTIALLY_FILLED - Cancel will succeed if order status is PARTIALLY_FILLED.
|
|
2439
|
+
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2440
|
+
"""
|
|
2441
|
+
await self.load_markets()
|
|
2442
|
+
if symbol is None:
|
|
2443
|
+
raise BadRequest(self.id + ' cancelOrderWs requires a symbol')
|
|
2444
|
+
market = self.market(symbol)
|
|
2445
|
+
type = self.get_market_type('cancelOrderWs', market, params)
|
|
2446
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
2447
|
+
requestId = self.request_id(url)
|
|
2448
|
+
messageHash = str(requestId)
|
|
2449
|
+
returnRateLimits = False
|
|
2450
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'cancelOrderWs', 'returnRateLimits', False)
|
|
2451
|
+
payload: dict = {
|
|
2452
|
+
'symbol': self.market_id(symbol),
|
|
2453
|
+
'returnRateLimits': returnRateLimits,
|
|
2454
|
+
}
|
|
2455
|
+
clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId')
|
|
2456
|
+
if clientOrderId is not None:
|
|
2457
|
+
payload['origClientOrderId'] = clientOrderId
|
|
2458
|
+
else:
|
|
2459
|
+
payload['orderId'] = self.parse_to_int(id)
|
|
2460
|
+
params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
|
|
2461
|
+
message: dict = {
|
|
2462
|
+
'id': messageHash,
|
|
2463
|
+
'method': 'order.cancel',
|
|
2464
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2465
|
+
}
|
|
2466
|
+
subscription: dict = {
|
|
2467
|
+
'method': self.handle_order_ws,
|
|
2468
|
+
}
|
|
2469
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2470
|
+
|
|
2471
|
+
async def cancel_all_orders_ws(self, symbol: Str = None, params={}):
|
|
2472
|
+
"""
|
|
2473
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#current-open-orders-user_data
|
|
2474
|
+
cancel all open orders in a market
|
|
2475
|
+
:param str symbol: unified market symbol of the market to cancel orders in
|
|
2476
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2477
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2478
|
+
"""
|
|
2479
|
+
await self.load_markets()
|
|
2480
|
+
market = self.market(symbol)
|
|
2481
|
+
type = self.get_market_type('cancelAllOrdersWs', market, params)
|
|
2482
|
+
if type != 'spot' and type != 'future':
|
|
2483
|
+
raise BadRequest(self.id + ' cancelAllOrdersWs only supports spot or swap markets')
|
|
2484
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
2485
|
+
requestId = self.request_id(url)
|
|
2486
|
+
messageHash = str(requestId)
|
|
2487
|
+
returnRateLimits = False
|
|
2488
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'cancelAllOrdersWs', 'returnRateLimits', False)
|
|
2489
|
+
payload: dict = {
|
|
2490
|
+
'symbol': self.market_id(symbol),
|
|
2491
|
+
'returnRateLimits': returnRateLimits,
|
|
2492
|
+
}
|
|
2493
|
+
message: dict = {
|
|
2494
|
+
'id': messageHash,
|
|
2495
|
+
'method': 'order.cancel',
|
|
2496
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2497
|
+
}
|
|
2498
|
+
subscription: dict = {
|
|
2499
|
+
'method': self.handle_orders_ws,
|
|
2500
|
+
}
|
|
2501
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2502
|
+
|
|
2503
|
+
async def fetch_order_ws(self, id: str, symbol: Str = None, params={}) -> Order:
|
|
2504
|
+
"""
|
|
2505
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#query-order-user_data
|
|
2506
|
+
:see: https://binance-docs.github.io/apidocs/futures/en/#query-order-user_data-2
|
|
2507
|
+
fetches information on an order made by the user
|
|
2508
|
+
:param str symbol: unified symbol of the market the order was made in
|
|
2509
|
+
:param dict params: extra parameters specific to the exchange API endpoint
|
|
2510
|
+
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2511
|
+
"""
|
|
2512
|
+
await self.load_markets()
|
|
2513
|
+
if symbol is None:
|
|
2514
|
+
raise BadRequest(self.id + ' cancelOrderWs requires a symbol')
|
|
2515
|
+
market = self.market(symbol)
|
|
2516
|
+
type = self.get_market_type('fetchOrderWs', market, params)
|
|
2517
|
+
if type != 'spot' and type != 'future':
|
|
2518
|
+
raise BadRequest(self.id + ' fetchOrderWs only supports spot or swap markets')
|
|
2519
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
2520
|
+
requestId = self.request_id(url)
|
|
2521
|
+
messageHash = str(requestId)
|
|
2522
|
+
returnRateLimits = False
|
|
2523
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False)
|
|
2524
|
+
payload: dict = {
|
|
2525
|
+
'symbol': self.market_id(symbol),
|
|
2526
|
+
'returnRateLimits': returnRateLimits,
|
|
2527
|
+
}
|
|
2528
|
+
clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId')
|
|
2529
|
+
if clientOrderId is not None:
|
|
2530
|
+
payload['origClientOrderId'] = clientOrderId
|
|
2531
|
+
else:
|
|
2532
|
+
payload['orderId'] = self.parse_to_int(id)
|
|
2533
|
+
message: dict = {
|
|
2534
|
+
'id': messageHash,
|
|
2535
|
+
'method': 'order.status',
|
|
2536
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2537
|
+
}
|
|
2538
|
+
subscription: dict = {
|
|
2539
|
+
'method': self.handle_order_ws,
|
|
2540
|
+
}
|
|
2541
|
+
return await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2542
|
+
|
|
2543
|
+
async def fetch_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
2544
|
+
"""
|
|
2545
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#account-order-history-user_data
|
|
2546
|
+
fetches information on multiple orders made by the user
|
|
2547
|
+
:param str symbol: unified market symbol of the market orders were made in
|
|
2548
|
+
:param int|None [since]: the earliest time in ms to fetch orders for
|
|
2549
|
+
:param int|None [limit]: the maximum number of order structures to retrieve
|
|
2550
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2551
|
+
:param int [params.orderId]: order id to begin at
|
|
2552
|
+
:param int [params.startTime]: earliest time in ms to retrieve orders for
|
|
2553
|
+
:param int [params.endTime]: latest time in ms to retrieve orders for
|
|
2554
|
+
:param int [params.limit]: the maximum number of order structures to retrieve
|
|
2555
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2556
|
+
"""
|
|
2557
|
+
await self.load_markets()
|
|
2558
|
+
if symbol is None:
|
|
2559
|
+
raise BadRequest(self.id + ' fetchOrdersWs requires a symbol')
|
|
2560
|
+
market = self.market(symbol)
|
|
2561
|
+
type = self.get_market_type('fetchOrdersWs', market, params)
|
|
2562
|
+
if type != 'spot':
|
|
2563
|
+
raise BadRequest(self.id + ' fetchOrdersWs only supports spot markets')
|
|
2564
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
2565
|
+
requestId = self.request_id(url)
|
|
2566
|
+
messageHash = str(requestId)
|
|
2567
|
+
returnRateLimits = False
|
|
2568
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False)
|
|
2569
|
+
payload: dict = {
|
|
2570
|
+
'symbol': self.market_id(symbol),
|
|
2571
|
+
'returnRateLimits': returnRateLimits,
|
|
2572
|
+
}
|
|
2573
|
+
message: dict = {
|
|
2574
|
+
'id': messageHash,
|
|
2575
|
+
'method': 'allOrders',
|
|
2576
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2577
|
+
}
|
|
2578
|
+
subscription: dict = {
|
|
2579
|
+
'method': self.handle_orders_ws,
|
|
2580
|
+
}
|
|
2581
|
+
orders = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2582
|
+
return self.filter_by_symbol_since_limit(orders, symbol, since, limit)
|
|
2583
|
+
|
|
2584
|
+
async def fetch_closed_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
2585
|
+
"""
|
|
2586
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#account-order-history-user_data
|
|
2587
|
+
fetch closed orders
|
|
2588
|
+
:param str symbol: unified market symbol
|
|
2589
|
+
:param int [since]: the earliest time in ms to fetch open orders for
|
|
2590
|
+
:param int [limit]: the maximum number of open orders structures to retrieve
|
|
2591
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2592
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2593
|
+
"""
|
|
2594
|
+
orders = await self.fetch_orders_ws(symbol, since, limit, params)
|
|
2595
|
+
closedOrders = []
|
|
2596
|
+
for i in range(0, len(orders)):
|
|
2597
|
+
order = orders[i]
|
|
2598
|
+
if order['status'] == 'closed':
|
|
2599
|
+
closedOrders.append(order)
|
|
2600
|
+
return closedOrders
|
|
2601
|
+
|
|
2602
|
+
async def fetch_open_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
2603
|
+
"""
|
|
2604
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#current-open-orders-user_data
|
|
2605
|
+
fetch all unfilled currently open orders
|
|
2606
|
+
:param str symbol: unified market symbol
|
|
2607
|
+
:param int|None [since]: the earliest time in ms to fetch open orders for
|
|
2608
|
+
:param int|None [limit]: the maximum number of open orders structures to retrieve
|
|
2609
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2610
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2611
|
+
"""
|
|
2612
|
+
await self.load_markets()
|
|
2613
|
+
market = self.market(symbol)
|
|
2614
|
+
type = self.get_market_type('fetchOpenOrdersWs', market, params)
|
|
2615
|
+
if type != 'spot' and type != 'future':
|
|
2616
|
+
raise BadRequest(self.id + ' fetchOpenOrdersWs only supports spot or swap markets')
|
|
2617
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
2618
|
+
requestId = self.request_id(url)
|
|
2619
|
+
messageHash = str(requestId)
|
|
2620
|
+
returnRateLimits = False
|
|
2621
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False)
|
|
2622
|
+
payload: dict = {
|
|
2623
|
+
'returnRateLimits': returnRateLimits,
|
|
2624
|
+
}
|
|
2625
|
+
if symbol is not None:
|
|
2626
|
+
payload['symbol'] = self.market_id(symbol)
|
|
2627
|
+
message: dict = {
|
|
2628
|
+
'id': messageHash,
|
|
2629
|
+
'method': 'openOrders.status',
|
|
2630
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
2631
|
+
}
|
|
2632
|
+
subscription: dict = {
|
|
2633
|
+
'method': self.handle_orders_ws,
|
|
2634
|
+
}
|
|
2635
|
+
orders = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
2636
|
+
return self.filter_by_symbol_since_limit(orders, symbol, since, limit)
|
|
2637
|
+
|
|
2638
|
+
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
2639
|
+
"""
|
|
2640
|
+
watches information on multiple orders made by the user
|
|
2641
|
+
:see: https://binance-docs.github.io/apidocs/spot/en/#payload-order-update
|
|
2642
|
+
:see: https://binance-docs.github.io/apidocs/pm/en/#event-futures-order-update
|
|
2643
|
+
:see: https://binance-docs.github.io/apidocs/pm/en/#event-margin-order-update
|
|
2644
|
+
:param str symbol: unified market symbol of the market the orders were made in
|
|
2645
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
|
2646
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
|
2647
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
2648
|
+
:param str|None [params.marginMode]: 'cross' or 'isolated', for spot margin
|
|
2649
|
+
:param boolean [params.portfolioMargin]: set to True if you would like to watch portfolio margin account orders
|
|
2650
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
2651
|
+
"""
|
|
2652
|
+
await self.load_markets()
|
|
2653
|
+
messageHash = 'orders'
|
|
2654
|
+
market = None
|
|
2655
|
+
if symbol is not None:
|
|
2656
|
+
market = self.market(symbol)
|
|
2657
|
+
symbol = market['symbol']
|
|
2658
|
+
messageHash += ':' + symbol
|
|
2659
|
+
type = None
|
|
2660
|
+
type, params = self.handle_market_type_and_params('watchOrders', market, params)
|
|
2661
|
+
subType = None
|
|
2662
|
+
subType, params = self.handle_sub_type_and_params('watchOrders', market, params)
|
|
2663
|
+
if self.isLinear(type, subType):
|
|
2664
|
+
type = 'future'
|
|
2665
|
+
elif self.isInverse(type, subType):
|
|
2666
|
+
type = 'delivery'
|
|
2667
|
+
params = self.extend(params, {'type': type, 'symbol': symbol}) # needed inside authenticate for isolated margin
|
|
2668
|
+
await self.authenticate(params)
|
|
2669
|
+
marginMode = None
|
|
2670
|
+
marginMode, params = self.handle_margin_mode_and_params('watchOrders', params)
|
|
2671
|
+
urlType = type
|
|
2672
|
+
if (type == 'margin') or ((type == 'spot') and (marginMode is not None)):
|
|
2673
|
+
urlType = 'spot' # spot-margin shares the same stream spot
|
|
2674
|
+
isPortfolioMargin = None
|
|
2675
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchOrders', 'papi', 'portfolioMargin', False)
|
|
2676
|
+
if isPortfolioMargin:
|
|
2677
|
+
urlType = 'papi'
|
|
2678
|
+
url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey']
|
|
2679
|
+
client = self.client(url)
|
|
2680
|
+
self.set_balance_cache(client, type, isPortfolioMargin)
|
|
2681
|
+
self.set_positions_cache(client, type, None, isPortfolioMargin)
|
|
2682
|
+
message = None
|
|
2683
|
+
orders = await self.watch(url, messageHash, message, type)
|
|
2684
|
+
if self.newUpdates:
|
|
2685
|
+
limit = orders.getLimit(symbol, limit)
|
|
2686
|
+
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
|
2687
|
+
|
|
2688
|
+
def parse_ws_order(self, order, market=None):
|
|
2689
|
+
#
|
|
2690
|
+
# spot
|
|
2691
|
+
#
|
|
2692
|
+
# {
|
|
2693
|
+
# "e": "executionReport", # Event type
|
|
2694
|
+
# "E": 1499405658658, # Event time
|
|
2695
|
+
# "s": "ETHBTC", # Symbol
|
|
2696
|
+
# "c": "mUvoqJxFIILMdfAW5iGSOW", # Client order ID
|
|
2697
|
+
# "S": "BUY", # Side
|
|
2698
|
+
# "o": "LIMIT", # Order type
|
|
2699
|
+
# "f": "GTC", # Time in force
|
|
2700
|
+
# "q": "1.00000000", # Order quantity
|
|
2701
|
+
# "p": "0.10264410", # Order price
|
|
2702
|
+
# "P": "0.00000000", # Stop price
|
|
2703
|
+
# "F": "0.00000000", # Iceberg quantity
|
|
2704
|
+
# "g": -1, # OrderListId
|
|
2705
|
+
# "C": null, # Original client order ID; This is the ID of the order being canceled
|
|
2706
|
+
# "x": "NEW", # Current execution type
|
|
2707
|
+
# "X": "NEW", # Current order status
|
|
2708
|
+
# "r": "NONE", # Order reject reason; will be an error code.
|
|
2709
|
+
# "i": 4293153, # Order ID
|
|
2710
|
+
# "l": "0.00000000", # Last executed quantity
|
|
2711
|
+
# "z": "0.00000000", # Cumulative filled quantity
|
|
2712
|
+
# "L": "0.00000000", # Last executed price
|
|
2713
|
+
# "n": "0", # Commission amount
|
|
2714
|
+
# "N": null, # Commission asset
|
|
2715
|
+
# "T": 1499405658657, # Transaction time
|
|
2716
|
+
# "t": -1, # Trade ID
|
|
2717
|
+
# "I": 8641984, # Ignore
|
|
2718
|
+
# "w": True, # Is the order on the book?
|
|
2719
|
+
# "m": False, # Is self trade the maker side?
|
|
2720
|
+
# "M": False, # Ignore
|
|
2721
|
+
# "O": 1499405658657, # Order creation time
|
|
2722
|
+
# "Z": "0.00000000", # Cumulative quote asset transacted quantity
|
|
2723
|
+
# "Y": "0.00000000" # Last quote asset transacted quantity(i.e. lastPrice * lastQty),
|
|
2724
|
+
# "Q": "0.00000000" # Quote Order Qty
|
|
2725
|
+
# }
|
|
2726
|
+
#
|
|
2727
|
+
# future
|
|
2728
|
+
#
|
|
2729
|
+
# {
|
|
2730
|
+
# "s":"BTCUSDT", # Symbol
|
|
2731
|
+
# "c":"TEST", # Client Order Id
|
|
2732
|
+
# # special client order id:
|
|
2733
|
+
# # starts with "autoclose-": liquidation order
|
|
2734
|
+
# # "adl_autoclose": ADL auto close order
|
|
2735
|
+
# "S":"SELL", # Side
|
|
2736
|
+
# "o":"TRAILING_STOP_MARKET", # Order Type
|
|
2737
|
+
# "f":"GTC", # Time in Force
|
|
2738
|
+
# "q":"0.001", # Original Quantity
|
|
2739
|
+
# "p":"0", # Original Price
|
|
2740
|
+
# "ap":"0", # Average Price
|
|
2741
|
+
# "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order
|
|
2742
|
+
# "x":"NEW", # Execution Type
|
|
2743
|
+
# "X":"NEW", # Order Status
|
|
2744
|
+
# "i":8886774, # Order Id
|
|
2745
|
+
# "l":"0", # Order Last Filled Quantity
|
|
2746
|
+
# "z":"0", # Order Filled Accumulated Quantity
|
|
2747
|
+
# "L":"0", # Last Filled Price
|
|
2748
|
+
# "N":"USDT", # Commission Asset, will not push if no commission
|
|
2749
|
+
# "n":"0", # Commission, will not push if no commission
|
|
2750
|
+
# "T":1568879465651, # Order Trade Time
|
|
2751
|
+
# "t":0, # Trade Id
|
|
2752
|
+
# "b":"0", # Bids Notional
|
|
2753
|
+
# "a":"9.91", # Ask Notional
|
|
2754
|
+
# "m":false, # Is self trade the maker side?
|
|
2755
|
+
# "R":false, # Is self reduce only
|
|
2756
|
+
# "wt":"CONTRACT_PRICE", # Stop Price Working Type
|
|
2757
|
+
# "ot":"TRAILING_STOP_MARKET", # Original Order Type
|
|
2758
|
+
# "ps":"LONG", # Position Side
|
|
2759
|
+
# "cp":false, # If Close-All, pushed with conditional order
|
|
2760
|
+
# "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order
|
|
2761
|
+
# "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
|
2762
|
+
# "rp":"0" # Realized Profit of the trade
|
|
2763
|
+
# }
|
|
2764
|
+
#
|
|
2765
|
+
executionType = self.safe_string(order, 'x')
|
|
2766
|
+
orderId = self.safe_string(order, 'i')
|
|
2767
|
+
marketId = self.safe_string(order, 's')
|
|
2768
|
+
marketType = 'contract' if ('ps' in order) else 'spot'
|
|
2769
|
+
symbol = self.safe_symbol(marketId, None, None, marketType)
|
|
2770
|
+
timestamp = self.safe_integer(order, 'O')
|
|
2771
|
+
T = self.safe_integer(order, 'T')
|
|
2772
|
+
lastTradeTimestamp = None
|
|
2773
|
+
if executionType == 'NEW' or executionType == 'AMENDMENT' or executionType == 'CANCELED':
|
|
2774
|
+
if timestamp is None:
|
|
2775
|
+
timestamp = T
|
|
2776
|
+
elif executionType == 'TRADE':
|
|
2777
|
+
lastTradeTimestamp = T
|
|
2778
|
+
lastUpdateTimestamp = T
|
|
2779
|
+
fee = None
|
|
2780
|
+
feeCost = self.safe_string(order, 'n')
|
|
2781
|
+
if (feeCost is not None) and (Precise.string_gt(feeCost, '0')):
|
|
2782
|
+
feeCurrencyId = self.safe_string(order, 'N')
|
|
2783
|
+
feeCurrency = self.safe_currency_code(feeCurrencyId)
|
|
2784
|
+
fee = {
|
|
2785
|
+
'cost': feeCost,
|
|
2786
|
+
'currency': feeCurrency,
|
|
2787
|
+
}
|
|
2788
|
+
price = self.safe_string(order, 'p')
|
|
2789
|
+
amount = self.safe_string(order, 'q')
|
|
2790
|
+
side = self.safe_string_lower(order, 'S')
|
|
2791
|
+
type = self.safe_string_lower(order, 'o')
|
|
2792
|
+
filled = self.safe_string(order, 'z')
|
|
2793
|
+
cost = self.safe_string(order, 'Z')
|
|
2794
|
+
average = self.safe_string(order, 'ap')
|
|
2795
|
+
rawStatus = self.safe_string(order, 'X')
|
|
2796
|
+
status = self.parse_order_status(rawStatus)
|
|
2797
|
+
trades = None
|
|
2798
|
+
clientOrderId = self.safe_string(order, 'C')
|
|
2799
|
+
if (clientOrderId is None) or (len(clientOrderId) == 0):
|
|
2800
|
+
clientOrderId = self.safe_string(order, 'c')
|
|
2801
|
+
stopPrice = self.safe_string_2(order, 'P', 'sp')
|
|
2802
|
+
timeInForce = self.safe_string(order, 'f')
|
|
2803
|
+
if timeInForce == 'GTX':
|
|
2804
|
+
# GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
|
|
2805
|
+
timeInForce = 'PO'
|
|
2806
|
+
return self.safe_order({
|
|
2807
|
+
'info': order,
|
|
2808
|
+
'symbol': symbol,
|
|
2809
|
+
'id': orderId,
|
|
2810
|
+
'clientOrderId': clientOrderId,
|
|
2811
|
+
'timestamp': timestamp,
|
|
2812
|
+
'datetime': self.iso8601(timestamp),
|
|
2813
|
+
'lastTradeTimestamp': lastTradeTimestamp,
|
|
2814
|
+
'lastUpdateTimestamp': lastUpdateTimestamp,
|
|
2815
|
+
'type': type,
|
|
2816
|
+
'timeInForce': timeInForce,
|
|
2817
|
+
'postOnly': None,
|
|
2818
|
+
'reduceOnly': self.safe_bool(order, 'R'),
|
|
2819
|
+
'side': side,
|
|
2820
|
+
'price': price,
|
|
2821
|
+
'stopPrice': stopPrice,
|
|
2822
|
+
'triggerPrice': stopPrice,
|
|
2823
|
+
'amount': amount,
|
|
2824
|
+
'cost': cost,
|
|
2825
|
+
'average': average,
|
|
2826
|
+
'filled': filled,
|
|
2827
|
+
'remaining': None,
|
|
2828
|
+
'status': status,
|
|
2829
|
+
'fee': fee,
|
|
2830
|
+
'trades': trades,
|
|
2831
|
+
})
|
|
2832
|
+
|
|
2833
|
+
def handle_order_update(self, client: Client, message):
|
|
2834
|
+
#
|
|
2835
|
+
# spot
|
|
2836
|
+
#
|
|
2837
|
+
# {
|
|
2838
|
+
# "e": "executionReport", # Event type
|
|
2839
|
+
# "E": 1499405658658, # Event time
|
|
2840
|
+
# "s": "ETHBTC", # Symbol
|
|
2841
|
+
# "c": "mUvoqJxFIILMdfAW5iGSOW", # Client order ID
|
|
2842
|
+
# "S": "BUY", # Side
|
|
2843
|
+
# "o": "LIMIT", # Order type
|
|
2844
|
+
# "f": "GTC", # Time in force
|
|
2845
|
+
# "q": "1.00000000", # Order quantity
|
|
2846
|
+
# "p": "0.10264410", # Order price
|
|
2847
|
+
# "P": "0.00000000", # Stop price
|
|
2848
|
+
# "F": "0.00000000", # Iceberg quantity
|
|
2849
|
+
# "g": -1, # OrderListId
|
|
2850
|
+
# "C": null, # Original client order ID; This is the ID of the order being canceled
|
|
2851
|
+
# "x": "NEW", # Current execution type
|
|
2852
|
+
# "X": "NEW", # Current order status
|
|
2853
|
+
# "r": "NONE", # Order reject reason; will be an error code.
|
|
2854
|
+
# "i": 4293153, # Order ID
|
|
2855
|
+
# "l": "0.00000000", # Last executed quantity
|
|
2856
|
+
# "z": "0.00000000", # Cumulative filled quantity
|
|
2857
|
+
# "L": "0.00000000", # Last executed price
|
|
2858
|
+
# "n": "0", # Commission amount
|
|
2859
|
+
# "N": null, # Commission asset
|
|
2860
|
+
# "T": 1499405658657, # Transaction time
|
|
2861
|
+
# "t": -1, # Trade ID
|
|
2862
|
+
# "I": 8641984, # Ignore
|
|
2863
|
+
# "w": True, # Is the order on the book?
|
|
2864
|
+
# "m": False, # Is self trade the maker side?
|
|
2865
|
+
# "M": False, # Ignore
|
|
2866
|
+
# "O": 1499405658657, # Order creation time
|
|
2867
|
+
# "Z": "0.00000000", # Cumulative quote asset transacted quantity
|
|
2868
|
+
# "Y": "0.00000000" # Last quote asset transacted quantity(i.e. lastPrice * lastQty),
|
|
2869
|
+
# "Q": "0.00000000" # Quote Order Qty
|
|
2870
|
+
# }
|
|
2871
|
+
#
|
|
2872
|
+
# future
|
|
2873
|
+
#
|
|
2874
|
+
# {
|
|
2875
|
+
# "e":"ORDER_TRADE_UPDATE", # Event Type
|
|
2876
|
+
# "E":1568879465651, # Event Time
|
|
2877
|
+
# "T":1568879465650, # Trasaction Time
|
|
2878
|
+
# "o": {
|
|
2879
|
+
# "s":"BTCUSDT", # Symbol
|
|
2880
|
+
# "c":"TEST", # Client Order Id
|
|
2881
|
+
# # special client order id:
|
|
2882
|
+
# # starts with "autoclose-": liquidation order
|
|
2883
|
+
# # "adl_autoclose": ADL auto close order
|
|
2884
|
+
# "S":"SELL", # Side
|
|
2885
|
+
# "o":"TRAILING_STOP_MARKET", # Order Type
|
|
2886
|
+
# "f":"GTC", # Time in Force
|
|
2887
|
+
# "q":"0.001", # Original Quantity
|
|
2888
|
+
# "p":"0", # Original Price
|
|
2889
|
+
# "ap":"0", # Average Price
|
|
2890
|
+
# "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order
|
|
2891
|
+
# "x":"NEW", # Execution Type
|
|
2892
|
+
# "X":"NEW", # Order Status
|
|
2893
|
+
# "i":8886774, # Order Id
|
|
2894
|
+
# "l":"0", # Order Last Filled Quantity
|
|
2895
|
+
# "z":"0", # Order Filled Accumulated Quantity
|
|
2896
|
+
# "L":"0", # Last Filled Price
|
|
2897
|
+
# "N":"USDT", # Commission Asset, will not push if no commission
|
|
2898
|
+
# "n":"0", # Commission, will not push if no commission
|
|
2899
|
+
# "T":1568879465651, # Order Trade Time
|
|
2900
|
+
# "t":0, # Trade Id
|
|
2901
|
+
# "b":"0", # Bids Notional
|
|
2902
|
+
# "a":"9.91", # Ask Notional
|
|
2903
|
+
# "m":false, # Is self trade the maker side?
|
|
2904
|
+
# "R":false, # Is self reduce only
|
|
2905
|
+
# "wt":"CONTRACT_PRICE", # Stop Price Working Type
|
|
2906
|
+
# "ot":"TRAILING_STOP_MARKET", # Original Order Type
|
|
2907
|
+
# "ps":"LONG", # Position Side
|
|
2908
|
+
# "cp":false, # If Close-All, pushed with conditional order
|
|
2909
|
+
# "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order
|
|
2910
|
+
# "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order
|
|
2911
|
+
# "rp":"0" # Realized Profit of the trade
|
|
2912
|
+
# }
|
|
2913
|
+
# }
|
|
2914
|
+
#
|
|
2915
|
+
e = self.safe_string(message, 'e')
|
|
2916
|
+
if e == 'ORDER_TRADE_UPDATE':
|
|
2917
|
+
message = self.safe_dict(message, 'o', message)
|
|
2918
|
+
self.handle_my_trade(client, message)
|
|
2919
|
+
self.handle_order(client, message)
|
|
2920
|
+
self.handle_my_liquidation(client, message)
|
|
2921
|
+
|
|
2922
|
+
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
2923
|
+
"""
|
|
2924
|
+
watch all open positions
|
|
2925
|
+
:param str[]|None symbols: list of unified market symbols
|
|
2926
|
+
:param dict params: extra parameters specific to the exchange API endpoint
|
|
2927
|
+
:param boolean [params.portfolioMargin]: set to True if you would like to watch positions in a portfolio margin account
|
|
2928
|
+
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
|
2929
|
+
"""
|
|
2930
|
+
await self.load_markets()
|
|
2931
|
+
market = None
|
|
2932
|
+
messageHash = ''
|
|
2933
|
+
symbols = self.market_symbols(symbols)
|
|
2934
|
+
if not self.is_empty(symbols):
|
|
2935
|
+
market = self.get_market_from_symbols(symbols)
|
|
2936
|
+
messageHash = '::' + ','.join(symbols)
|
|
2937
|
+
marketTypeObject: dict = {}
|
|
2938
|
+
if market is not None:
|
|
2939
|
+
marketTypeObject['type'] = market['type']
|
|
2940
|
+
marketTypeObject['subType'] = market['subType']
|
|
2941
|
+
await self.authenticate(self.extend(marketTypeObject, params))
|
|
2942
|
+
type = None
|
|
2943
|
+
type, params = self.handle_market_type_and_params('watchPositions', market, params)
|
|
2944
|
+
if type == 'spot' or type == 'margin':
|
|
2945
|
+
type = 'future'
|
|
2946
|
+
subType = None
|
|
2947
|
+
subType, params = self.handle_sub_type_and_params('watchPositions', market, params)
|
|
2948
|
+
if self.isLinear(type, subType):
|
|
2949
|
+
type = 'future'
|
|
2950
|
+
elif self.isInverse(type, subType):
|
|
2951
|
+
type = 'delivery'
|
|
2952
|
+
messageHash = type + ':positions' + messageHash
|
|
2953
|
+
isPortfolioMargin = None
|
|
2954
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchPositions', 'papi', 'portfolioMargin', False)
|
|
2955
|
+
urlType = type
|
|
2956
|
+
if isPortfolioMargin:
|
|
2957
|
+
urlType = 'papi'
|
|
2958
|
+
url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey']
|
|
2959
|
+
client = self.client(url)
|
|
2960
|
+
self.set_balance_cache(client, type, isPortfolioMargin)
|
|
2961
|
+
self.set_positions_cache(client, type, symbols, isPortfolioMargin)
|
|
2962
|
+
fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
|
|
2963
|
+
awaitPositionsSnapshot = self.safe_bool('watchPositions', 'awaitPositionsSnapshot', True)
|
|
2964
|
+
cache = self.safe_value(self.positions, type)
|
|
2965
|
+
if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None:
|
|
2966
|
+
snapshot = await client.future(type + ':fetchPositionsSnapshot')
|
|
2967
|
+
return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
|
|
2968
|
+
newPositions = await self.watch(url, messageHash, None, type)
|
|
2969
|
+
if self.newUpdates:
|
|
2970
|
+
return newPositions
|
|
2971
|
+
return self.filter_by_symbols_since_limit(cache, symbols, since, limit, True)
|
|
2972
|
+
|
|
2973
|
+
def set_positions_cache(self, client: Client, type, symbols: Strings = None, isPortfolioMargin=False):
|
|
2974
|
+
if type == 'spot':
|
|
2975
|
+
return
|
|
2976
|
+
if self.positions is None:
|
|
2977
|
+
self.positions = {}
|
|
2978
|
+
if type in self.positions:
|
|
2979
|
+
return
|
|
2980
|
+
fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
|
|
2981
|
+
if fetchPositionsSnapshot:
|
|
2982
|
+
messageHash = type + ':fetchPositionsSnapshot'
|
|
2983
|
+
if not (messageHash in client.futures):
|
|
2984
|
+
client.future(messageHash)
|
|
2985
|
+
self.spawn(self.load_positions_snapshot, client, messageHash, type, isPortfolioMargin)
|
|
2986
|
+
else:
|
|
2987
|
+
self.positions[type] = ArrayCacheBySymbolBySide()
|
|
2988
|
+
|
|
2989
|
+
async def load_positions_snapshot(self, client, messageHash, type, isPortfolioMargin):
|
|
2990
|
+
params: dict = {
|
|
2991
|
+
'type': type,
|
|
2992
|
+
}
|
|
2993
|
+
if isPortfolioMargin:
|
|
2994
|
+
params['portfolioMargin'] = True
|
|
2995
|
+
positions = await self.fetch_positions(None, params)
|
|
2996
|
+
self.positions[type] = ArrayCacheBySymbolBySide()
|
|
2997
|
+
cache = self.positions[type]
|
|
2998
|
+
for i in range(0, len(positions)):
|
|
2999
|
+
position = positions[i]
|
|
3000
|
+
contracts = self.safe_number(position, 'contracts', 0)
|
|
3001
|
+
if contracts > 0:
|
|
3002
|
+
cache.append(position)
|
|
3003
|
+
# don't remove the future from the .futures cache
|
|
3004
|
+
future = client.futures[messageHash]
|
|
3005
|
+
future.resolve(cache)
|
|
3006
|
+
client.resolve(cache, type + ':position')
|
|
3007
|
+
|
|
3008
|
+
def handle_positions(self, client, message):
|
|
3009
|
+
#
|
|
3010
|
+
# {
|
|
3011
|
+
# e: 'ACCOUNT_UPDATE',
|
|
3012
|
+
# T: 1667881353112,
|
|
3013
|
+
# E: 1667881353115,
|
|
3014
|
+
# a: {
|
|
3015
|
+
# B: [{
|
|
3016
|
+
# a: 'USDT',
|
|
3017
|
+
# wb: '1127.95750089',
|
|
3018
|
+
# cw: '1040.82091149',
|
|
3019
|
+
# bc: '0'
|
|
3020
|
+
# }],
|
|
3021
|
+
# P: [{
|
|
3022
|
+
# s: 'BTCUSDT',
|
|
3023
|
+
# pa: '-0.089',
|
|
3024
|
+
# ep: '19700.03933',
|
|
3025
|
+
# cr: '-1260.24809979',
|
|
3026
|
+
# up: '1.53058860',
|
|
3027
|
+
# mt: 'isolated',
|
|
3028
|
+
# iw: '87.13658940',
|
|
3029
|
+
# ps: 'BOTH',
|
|
3030
|
+
# ma: 'USDT'
|
|
3031
|
+
# }],
|
|
3032
|
+
# m: 'ORDER'
|
|
3033
|
+
# }
|
|
3034
|
+
# }
|
|
3035
|
+
#
|
|
3036
|
+
# each account is connected to a different endpoint
|
|
3037
|
+
# and has exactly one subscriptionhash which is the account type
|
|
3038
|
+
subscriptions = list(client.subscriptions.keys())
|
|
3039
|
+
accountType = subscriptions[0]
|
|
3040
|
+
if self.positions is None:
|
|
3041
|
+
self.positions = {}
|
|
3042
|
+
if not (accountType in self.positions):
|
|
3043
|
+
self.positions[accountType] = ArrayCacheBySymbolBySide()
|
|
3044
|
+
cache = self.positions[accountType]
|
|
3045
|
+
data = self.safe_dict(message, 'a', {})
|
|
3046
|
+
rawPositions = self.safe_list(data, 'P', [])
|
|
3047
|
+
newPositions = []
|
|
3048
|
+
for i in range(0, len(rawPositions)):
|
|
3049
|
+
rawPosition = rawPositions[i]
|
|
3050
|
+
position = self.parse_ws_position(rawPosition)
|
|
3051
|
+
timestamp = self.safe_integer(message, 'E')
|
|
3052
|
+
position['timestamp'] = timestamp
|
|
3053
|
+
position['datetime'] = self.iso8601(timestamp)
|
|
3054
|
+
newPositions.append(position)
|
|
3055
|
+
cache.append(position)
|
|
3056
|
+
messageHashes = self.find_message_hashes(client, accountType + ':positions::')
|
|
3057
|
+
for i in range(0, len(messageHashes)):
|
|
3058
|
+
messageHash = messageHashes[i]
|
|
3059
|
+
parts = messageHash.split('::')
|
|
3060
|
+
symbolsString = parts[1]
|
|
3061
|
+
symbols = symbolsString.split(',')
|
|
3062
|
+
positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
|
|
3063
|
+
if not self.is_empty(positions):
|
|
3064
|
+
client.resolve(positions, messageHash)
|
|
3065
|
+
client.resolve(newPositions, accountType + ':positions')
|
|
3066
|
+
|
|
3067
|
+
def parse_ws_position(self, position, market=None):
|
|
3068
|
+
#
|
|
3069
|
+
# {
|
|
3070
|
+
# "s": "BTCUSDT", # Symbol
|
|
3071
|
+
# "pa": "0", # Position Amount
|
|
3072
|
+
# "ep": "0.00000", # Entry Price
|
|
3073
|
+
# "cr": "200", #(Pre-fee) Accumulated Realized
|
|
3074
|
+
# "up": "0", # Unrealized PnL
|
|
3075
|
+
# "mt": "isolated", # Margin Type
|
|
3076
|
+
# "iw": "0.00000000", # Isolated Wallet(if isolated position)
|
|
3077
|
+
# "ps": "BOTH" # Position Side
|
|
3078
|
+
# }
|
|
3079
|
+
#
|
|
3080
|
+
marketId = self.safe_string(position, 's')
|
|
3081
|
+
contracts = self.safe_string(position, 'pa')
|
|
3082
|
+
contractsAbs = Precise.string_abs(self.safe_string(position, 'pa'))
|
|
3083
|
+
positionSide = self.safe_string_lower(position, 'ps')
|
|
3084
|
+
hedged = True
|
|
3085
|
+
if positionSide == 'both':
|
|
3086
|
+
hedged = False
|
|
3087
|
+
if not Precise.string_eq(contracts, '0'):
|
|
3088
|
+
if Precise.string_lt(contracts, '0'):
|
|
3089
|
+
positionSide = 'short'
|
|
3090
|
+
else:
|
|
3091
|
+
positionSide = 'long'
|
|
3092
|
+
return self.safe_position({
|
|
3093
|
+
'info': position,
|
|
3094
|
+
'id': None,
|
|
3095
|
+
'symbol': self.safe_symbol(marketId, None, None, 'contract'),
|
|
3096
|
+
'notional': None,
|
|
3097
|
+
'marginMode': self.safe_string(position, 'mt'),
|
|
3098
|
+
'liquidationPrice': None,
|
|
3099
|
+
'entryPrice': self.safe_number(position, 'ep'),
|
|
3100
|
+
'unrealizedPnl': self.safe_number(position, 'up'),
|
|
3101
|
+
'percentage': None,
|
|
3102
|
+
'contracts': self.parse_number(contractsAbs),
|
|
3103
|
+
'contractSize': None,
|
|
3104
|
+
'markPrice': None,
|
|
3105
|
+
'side': positionSide,
|
|
3106
|
+
'hedged': hedged,
|
|
3107
|
+
'timestamp': None,
|
|
3108
|
+
'datetime': None,
|
|
3109
|
+
'maintenanceMargin': None,
|
|
3110
|
+
'maintenanceMarginPercentage': None,
|
|
3111
|
+
'collateral': None,
|
|
3112
|
+
'initialMargin': None,
|
|
3113
|
+
'initialMarginPercentage': None,
|
|
3114
|
+
'leverage': None,
|
|
3115
|
+
'marginRatio': None,
|
|
3116
|
+
})
|
|
3117
|
+
|
|
3118
|
+
async def fetch_my_trades_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
3119
|
+
"""
|
|
3120
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#account-trade-history-user_data
|
|
3121
|
+
fetch all trades made by the user
|
|
3122
|
+
:param str symbol: unified market symbol
|
|
3123
|
+
:param int|None [since]: the earliest time in ms to fetch trades for
|
|
3124
|
+
:param int|None [limit]: the maximum number of trades structures to retrieve
|
|
3125
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
3126
|
+
:param int [params.endTime]: the latest time in ms to fetch trades for
|
|
3127
|
+
:param int [params.fromId]: first trade Id to fetch
|
|
3128
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
3129
|
+
"""
|
|
3130
|
+
await self.load_markets()
|
|
3131
|
+
if symbol is None:
|
|
3132
|
+
raise BadRequest(self.id + ' fetchMyTradesWs requires a symbol')
|
|
3133
|
+
market = self.market(symbol)
|
|
3134
|
+
type = self.get_market_type('fetchMyTradesWs', market, params)
|
|
3135
|
+
if type != 'spot' and type != 'future':
|
|
3136
|
+
raise BadRequest(self.id + ' fetchMyTradesWs does not support ' + type + ' markets')
|
|
3137
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
3138
|
+
requestId = self.request_id(url)
|
|
3139
|
+
messageHash = str(requestId)
|
|
3140
|
+
returnRateLimits = False
|
|
3141
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchMyTradesWs', 'returnRateLimits', False)
|
|
3142
|
+
payload: dict = {
|
|
3143
|
+
'symbol': self.market_id(symbol),
|
|
3144
|
+
'returnRateLimits': returnRateLimits,
|
|
3145
|
+
}
|
|
3146
|
+
if since is not None:
|
|
3147
|
+
payload['startTime'] = since
|
|
3148
|
+
if limit is not None:
|
|
3149
|
+
payload['limit'] = limit
|
|
3150
|
+
fromId = self.safe_integer(params, 'fromId')
|
|
3151
|
+
if fromId is not None and since is not None:
|
|
3152
|
+
raise BadRequest(self.id + 'fetchMyTradesWs does not support fetching by both fromId and since parameters at the same time')
|
|
3153
|
+
message: dict = {
|
|
3154
|
+
'id': messageHash,
|
|
3155
|
+
'method': 'myTrades',
|
|
3156
|
+
'params': self.sign_params(self.extend(payload, params)),
|
|
3157
|
+
}
|
|
3158
|
+
subscription: dict = {
|
|
3159
|
+
'method': self.handle_trades_ws,
|
|
3160
|
+
}
|
|
3161
|
+
trades = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
3162
|
+
return self.filter_by_symbol_since_limit(trades, symbol, since, limit)
|
|
3163
|
+
|
|
3164
|
+
async def fetch_trades_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
3165
|
+
"""
|
|
3166
|
+
:see: https://binance-docs.github.io/apidocs/websocket_api/en/#recent-trades
|
|
3167
|
+
fetch all trades made by the user
|
|
3168
|
+
:param str symbol: unified market symbol
|
|
3169
|
+
:param int [since]: the earliest time in ms to fetch trades for
|
|
3170
|
+
:param int [limit]: the maximum number of trades structures to retrieve, default=500, max=1000
|
|
3171
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
3172
|
+
*
|
|
3173
|
+
* EXCHANGE SPECIFIC PARAMETERS
|
|
3174
|
+
:param int [params.fromId]: trade ID to begin at
|
|
3175
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
3176
|
+
"""
|
|
3177
|
+
await self.load_markets()
|
|
3178
|
+
if symbol is None:
|
|
3179
|
+
raise BadRequest(self.id + ' fetchTradesWs() requires a symbol argument')
|
|
3180
|
+
market = self.market(symbol)
|
|
3181
|
+
type = self.get_market_type('fetchTradesWs', market, params)
|
|
3182
|
+
if type != 'spot' and type != 'future':
|
|
3183
|
+
raise BadRequest(self.id + ' fetchTradesWs does not support ' + type + ' markets')
|
|
3184
|
+
url = self.urls['api']['ws']['ws-api'][type]
|
|
3185
|
+
requestId = self.request_id(url)
|
|
3186
|
+
messageHash = str(requestId)
|
|
3187
|
+
returnRateLimits = False
|
|
3188
|
+
returnRateLimits, params = self.handle_option_and_params(params, 'fetchTradesWs', 'returnRateLimits', False)
|
|
3189
|
+
payload: dict = {
|
|
3190
|
+
'symbol': self.market_id(symbol),
|
|
3191
|
+
'returnRateLimits': returnRateLimits,
|
|
3192
|
+
}
|
|
3193
|
+
if limit is not None:
|
|
3194
|
+
payload['limit'] = limit
|
|
3195
|
+
message: dict = {
|
|
3196
|
+
'id': messageHash,
|
|
3197
|
+
'method': 'trades.historical',
|
|
3198
|
+
'params': self.extend(payload, params),
|
|
3199
|
+
}
|
|
3200
|
+
subscription: dict = {
|
|
3201
|
+
'method': self.handle_trades_ws,
|
|
3202
|
+
}
|
|
3203
|
+
trades = await self.watch(url, messageHash, message, messageHash, subscription)
|
|
3204
|
+
return self.filter_by_since_limit(trades, since, limit)
|
|
3205
|
+
|
|
3206
|
+
def handle_trades_ws(self, client: Client, message):
|
|
3207
|
+
#
|
|
3208
|
+
# fetchMyTradesWs
|
|
3209
|
+
#
|
|
3210
|
+
# {
|
|
3211
|
+
# "id": "f4ce6a53-a29d-4f70-823b-4ab59391d6e8",
|
|
3212
|
+
# "status": 200,
|
|
3213
|
+
# "result": [
|
|
3214
|
+
# {
|
|
3215
|
+
# "symbol": "BTCUSDT",
|
|
3216
|
+
# "id": 1650422481,
|
|
3217
|
+
# "orderId": 12569099453,
|
|
3218
|
+
# "orderListId": -1,
|
|
3219
|
+
# "price": "23416.10000000",
|
|
3220
|
+
# "qty": "0.00635000",
|
|
3221
|
+
# "quoteQty": "148.69223500",
|
|
3222
|
+
# "commission": "0.00000000",
|
|
3223
|
+
# "commissionAsset": "BNB",
|
|
3224
|
+
# "time": 1660801715793,
|
|
3225
|
+
# "isBuyer": False,
|
|
3226
|
+
# "isMaker": True,
|
|
3227
|
+
# "isBestMatch": True
|
|
3228
|
+
# },
|
|
3229
|
+
# ...
|
|
3230
|
+
# ],
|
|
3231
|
+
# }
|
|
3232
|
+
#
|
|
3233
|
+
# fetchTradesWs
|
|
3234
|
+
#
|
|
3235
|
+
# {
|
|
3236
|
+
# "id": "f4ce6a53-a29d-4f70-823b-4ab59391d6e8",
|
|
3237
|
+
# "status": 200,
|
|
3238
|
+
# "result": [
|
|
3239
|
+
# {
|
|
3240
|
+
# "id": 0,
|
|
3241
|
+
# "price": "0.00005000",
|
|
3242
|
+
# "qty": "40.00000000",
|
|
3243
|
+
# "quoteQty": "0.00200000",
|
|
3244
|
+
# "time": 1500004800376,
|
|
3245
|
+
# "isBuyerMaker": True,
|
|
3246
|
+
# "isBestMatch": True
|
|
3247
|
+
# }
|
|
3248
|
+
# ...
|
|
3249
|
+
# ],
|
|
3250
|
+
# }
|
|
3251
|
+
#
|
|
3252
|
+
messageHash = self.safe_string(message, 'id')
|
|
3253
|
+
result = self.safe_list(message, 'result', [])
|
|
3254
|
+
trades = self.parse_trades(result)
|
|
3255
|
+
client.resolve(trades, messageHash)
|
|
3256
|
+
|
|
3257
|
+
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
3258
|
+
"""
|
|
3259
|
+
watches information on multiple trades made by the user
|
|
3260
|
+
:param str symbol: unified market symbol of the market orders were made in
|
|
3261
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
|
3262
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
|
3263
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
3264
|
+
:param boolean [params.portfolioMargin]: set to True if you would like to watch trades in a portfolio margin account
|
|
3265
|
+
:returns dict[]: a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure
|
|
3266
|
+
"""
|
|
3267
|
+
await self.load_markets()
|
|
3268
|
+
type = None
|
|
3269
|
+
market = None
|
|
3270
|
+
if symbol is not None:
|
|
3271
|
+
market = self.market(symbol)
|
|
3272
|
+
symbol = market['symbol']
|
|
3273
|
+
type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
|
|
3274
|
+
subType = None
|
|
3275
|
+
subType, params = self.handle_sub_type_and_params('watchMyTrades', market, params)
|
|
3276
|
+
if self.isLinear(type, subType):
|
|
3277
|
+
type = 'future'
|
|
3278
|
+
elif self.isInverse(type, subType):
|
|
3279
|
+
type = 'delivery'
|
|
3280
|
+
messageHash = 'myTrades'
|
|
3281
|
+
if symbol is not None:
|
|
3282
|
+
symbol = self.symbol(symbol)
|
|
3283
|
+
messageHash += ':' + symbol
|
|
3284
|
+
params = self.extend(params, {'type': market['type'], 'symbol': symbol})
|
|
3285
|
+
await self.authenticate(params)
|
|
3286
|
+
urlType = type # we don't change type because the listening key is different
|
|
3287
|
+
if type == 'margin':
|
|
3288
|
+
urlType = 'spot' # spot-margin shares the same stream spot
|
|
3289
|
+
isPortfolioMargin = None
|
|
3290
|
+
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchMyTrades', 'papi', 'portfolioMargin', False)
|
|
3291
|
+
if isPortfolioMargin:
|
|
3292
|
+
urlType = 'papi'
|
|
3293
|
+
url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey']
|
|
3294
|
+
client = self.client(url)
|
|
3295
|
+
self.set_balance_cache(client, type, isPortfolioMargin)
|
|
3296
|
+
self.set_positions_cache(client, type, None, isPortfolioMargin)
|
|
3297
|
+
message = None
|
|
3298
|
+
trades = await self.watch(url, messageHash, message, type)
|
|
3299
|
+
if self.newUpdates:
|
|
3300
|
+
limit = trades.getLimit(symbol, limit)
|
|
3301
|
+
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
|
3302
|
+
|
|
3303
|
+
def handle_my_trade(self, client: Client, message):
|
|
3304
|
+
messageHash = 'myTrades'
|
|
3305
|
+
executionType = self.safe_string(message, 'x')
|
|
3306
|
+
if executionType == 'TRADE':
|
|
3307
|
+
trade = self.parse_ws_trade(message)
|
|
3308
|
+
orderId = self.safe_string(trade, 'order')
|
|
3309
|
+
tradeFee = self.safe_dict(trade, 'fee', {})
|
|
3310
|
+
tradeFee = self.extend({}, tradeFee)
|
|
3311
|
+
symbol = self.safe_string(trade, 'symbol')
|
|
3312
|
+
if orderId is not None and tradeFee is not None and symbol is not None:
|
|
3313
|
+
cachedOrders = self.orders
|
|
3314
|
+
if cachedOrders is not None:
|
|
3315
|
+
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
|
|
3316
|
+
order = self.safe_value(orders, orderId)
|
|
3317
|
+
if order is not None:
|
|
3318
|
+
# accumulate order fees
|
|
3319
|
+
fees = self.safe_value(order, 'fees')
|
|
3320
|
+
fee = self.safe_value(order, 'fee')
|
|
3321
|
+
if not self.is_empty(fees):
|
|
3322
|
+
insertNewFeeCurrency = True
|
|
3323
|
+
for i in range(0, len(fees)):
|
|
3324
|
+
orderFee = fees[i]
|
|
3325
|
+
if orderFee['currency'] == tradeFee['currency']:
|
|
3326
|
+
feeCost = self.sum(tradeFee['cost'], orderFee['cost'])
|
|
3327
|
+
order['fees'][i]['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost))
|
|
3328
|
+
insertNewFeeCurrency = False
|
|
3329
|
+
break
|
|
3330
|
+
if insertNewFeeCurrency:
|
|
3331
|
+
order['fees'].append(tradeFee)
|
|
3332
|
+
elif fee is not None:
|
|
3333
|
+
if fee['currency'] == tradeFee['currency']:
|
|
3334
|
+
feeCost = self.sum(fee['cost'], tradeFee['cost'])
|
|
3335
|
+
order['fee']['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost))
|
|
3336
|
+
elif fee['currency'] is None:
|
|
3337
|
+
order['fee'] = tradeFee
|
|
3338
|
+
else:
|
|
3339
|
+
order['fees'] = [fee, tradeFee]
|
|
3340
|
+
order['fee'] = None
|
|
3341
|
+
else:
|
|
3342
|
+
order['fee'] = tradeFee
|
|
3343
|
+
# save self trade in the order
|
|
3344
|
+
orderTrades = self.safe_list(order, 'trades', [])
|
|
3345
|
+
orderTrades.append(trade)
|
|
3346
|
+
order['trades'] = orderTrades
|
|
3347
|
+
# don't append twice cause it breaks newUpdates mode
|
|
3348
|
+
# self order already exists in the cache
|
|
3349
|
+
if self.myTrades is None:
|
|
3350
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
3351
|
+
self.myTrades = ArrayCacheBySymbolById(limit)
|
|
3352
|
+
myTrades = self.myTrades
|
|
3353
|
+
myTrades.append(trade)
|
|
3354
|
+
client.resolve(self.myTrades, messageHash)
|
|
3355
|
+
messageHashSymbol = messageHash + ':' + symbol
|
|
3356
|
+
client.resolve(self.myTrades, messageHashSymbol)
|
|
3357
|
+
|
|
3358
|
+
def handle_order(self, client: Client, message):
|
|
3359
|
+
parsed = self.parse_ws_order(message)
|
|
3360
|
+
symbol = self.safe_string(parsed, 'symbol')
|
|
3361
|
+
orderId = self.safe_string(parsed, 'id')
|
|
3362
|
+
if symbol is not None:
|
|
3363
|
+
if self.orders is None:
|
|
3364
|
+
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
|
3365
|
+
self.orders = ArrayCacheBySymbolById(limit)
|
|
3366
|
+
cachedOrders = self.orders
|
|
3367
|
+
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
|
|
3368
|
+
order = self.safe_value(orders, orderId)
|
|
3369
|
+
if order is not None:
|
|
3370
|
+
fee = self.safe_value(order, 'fee')
|
|
3371
|
+
if fee is not None:
|
|
3372
|
+
parsed['fee'] = fee
|
|
3373
|
+
fees = self.safe_value(order, 'fees')
|
|
3374
|
+
if fees is not None:
|
|
3375
|
+
parsed['fees'] = fees
|
|
3376
|
+
parsed['trades'] = self.safe_value(order, 'trades')
|
|
3377
|
+
timestamp = self.safe_integer(parsed, 'timestamp')
|
|
3378
|
+
if timestamp is None:
|
|
3379
|
+
parsed['timestamp'] = self.safe_integer(order, 'timestamp')
|
|
3380
|
+
parsed['datetime'] = self.safe_string(order, 'datetime')
|
|
3381
|
+
cachedOrders.append(parsed)
|
|
3382
|
+
messageHash = 'orders'
|
|
3383
|
+
symbolSpecificMessageHash = 'orders:' + symbol
|
|
3384
|
+
client.resolve(cachedOrders, messageHash)
|
|
3385
|
+
client.resolve(cachedOrders, symbolSpecificMessageHash)
|
|
3386
|
+
|
|
3387
|
+
def handle_acount_update(self, client, message):
|
|
3388
|
+
self.handle_balance(client, message)
|
|
3389
|
+
self.handle_positions(client, message)
|
|
3390
|
+
|
|
3391
|
+
def handle_ws_error(self, client: Client, message):
|
|
3392
|
+
#
|
|
3393
|
+
# {
|
|
3394
|
+
# "error": {
|
|
3395
|
+
# "code": 2,
|
|
3396
|
+
# "msg": "Invalid request: invalid stream"
|
|
3397
|
+
# },
|
|
3398
|
+
# "id": 1
|
|
3399
|
+
# }
|
|
3400
|
+
#
|
|
3401
|
+
id = self.safe_string(message, 'id')
|
|
3402
|
+
rejected = False
|
|
3403
|
+
error = self.safe_dict(message, 'error', {})
|
|
3404
|
+
code = self.safe_integer(error, 'code')
|
|
3405
|
+
msg = self.safe_string(error, 'msg')
|
|
3406
|
+
try:
|
|
3407
|
+
self.handle_errors(code, msg, client.url, None, None, self.json(error), error, None, None)
|
|
3408
|
+
except Exception as e:
|
|
3409
|
+
rejected = True
|
|
3410
|
+
# private endpoint uses id
|
|
3411
|
+
client.reject(e, id)
|
|
3412
|
+
# public endpoint stores messageHash in subscriptios
|
|
3413
|
+
subscriptionKeys = list(client.subscriptions.keys())
|
|
3414
|
+
for i in range(0, len(subscriptionKeys)):
|
|
3415
|
+
subscriptionHash = subscriptionKeys[i]
|
|
3416
|
+
subscriptionId = self.safe_string(client.subscriptions[subscriptionHash], 'id')
|
|
3417
|
+
if id == subscriptionId:
|
|
3418
|
+
client.reject(e, subscriptionHash)
|
|
3419
|
+
if not rejected:
|
|
3420
|
+
client.reject(message, id)
|
|
3421
|
+
# reset connection if 5xx error
|
|
3422
|
+
if self.safe_string(code, 0) == '5':
|
|
3423
|
+
client.reset(message)
|
|
3424
|
+
|
|
3425
|
+
def handle_message(self, client: Client, message):
|
|
3426
|
+
# handle WebSocketAPI
|
|
3427
|
+
status = self.safe_string(message, 'status')
|
|
3428
|
+
error = self.safe_value(message, 'error')
|
|
3429
|
+
if (error is not None) or (status is not None and status != '200'):
|
|
3430
|
+
self.handle_ws_error(client, message)
|
|
3431
|
+
return
|
|
3432
|
+
id = self.safe_string(message, 'id')
|
|
3433
|
+
subscriptions = self.safe_value(client.subscriptions, id)
|
|
3434
|
+
method = self.safe_value(subscriptions, 'method')
|
|
3435
|
+
if method is not None:
|
|
3436
|
+
method(client, message)
|
|
3437
|
+
return
|
|
3438
|
+
# handle other APIs
|
|
3439
|
+
methods: dict = {
|
|
3440
|
+
'depthUpdate': self.handle_order_book,
|
|
3441
|
+
'trade': self.handle_trade,
|
|
3442
|
+
'aggTrade': self.handle_trade,
|
|
3443
|
+
'kline': self.handle_ohlcv,
|
|
3444
|
+
'markPrice_kline': self.handle_ohlcv,
|
|
3445
|
+
'indexPrice_kline': self.handle_ohlcv,
|
|
3446
|
+
'1hTicker@arr': self.handle_tickers,
|
|
3447
|
+
'4hTicker@arr': self.handle_tickers,
|
|
3448
|
+
'1dTicker@arr': self.handle_tickers,
|
|
3449
|
+
'24hrTicker@arr': self.handle_tickers,
|
|
3450
|
+
'24hrMiniTicker@arr': self.handle_tickers,
|
|
3451
|
+
'1hTicker': self.handle_tickers,
|
|
3452
|
+
'4hTicker': self.handle_tickers,
|
|
3453
|
+
'1dTicker': self.handle_tickers,
|
|
3454
|
+
'24hrTicker': self.handle_tickers,
|
|
3455
|
+
'24hrMiniTicker': self.handle_tickers,
|
|
3456
|
+
'bookTicker': self.handle_bids_asks, # there is no "bookTicker@arr" endpoint
|
|
3457
|
+
'outboundAccountPosition': self.handle_balance,
|
|
3458
|
+
'balanceUpdate': self.handle_balance,
|
|
3459
|
+
'ACCOUNT_UPDATE': self.handle_acount_update,
|
|
3460
|
+
'executionReport': self.handle_order_update,
|
|
3461
|
+
'ORDER_TRADE_UPDATE': self.handle_order_update,
|
|
3462
|
+
'forceOrder': self.handle_liquidation,
|
|
3463
|
+
}
|
|
3464
|
+
event = self.safe_string(message, 'e')
|
|
3465
|
+
if isinstance(message, list):
|
|
3466
|
+
data = message[0]
|
|
3467
|
+
event = self.safe_string(data, 'e') + '@arr'
|
|
3468
|
+
method = self.safe_value(methods, event)
|
|
3469
|
+
if method is None:
|
|
3470
|
+
requestId = self.safe_string(message, 'id')
|
|
3471
|
+
if requestId is not None:
|
|
3472
|
+
self.handle_subscription_status(client, message)
|
|
3473
|
+
return
|
|
3474
|
+
# special case for the real-time bookTicker, since it comes without an event identifier
|
|
3475
|
+
#
|
|
3476
|
+
# {
|
|
3477
|
+
# "u": 7488717758,
|
|
3478
|
+
# "s": "BTCUSDT",
|
|
3479
|
+
# "b": "28621.74000000",
|
|
3480
|
+
# "B": "1.43278800",
|
|
3481
|
+
# "a": "28621.75000000",
|
|
3482
|
+
# "A": "2.52500800"
|
|
3483
|
+
# }
|
|
3484
|
+
#
|
|
3485
|
+
if event is None and ('a' in message) and ('b' in message):
|
|
3486
|
+
self.handle_bids_asks(client, message)
|
|
3487
|
+
else:
|
|
3488
|
+
method(client, message)
|