diff --git a/.coverage b/.coverage new file mode 100644 index 000000000..f4ee48d12 Binary files /dev/null and b/.coverage differ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..f8b8eff0b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @carlosmiei diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..a3b222b21 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Code snippet to reproduce the behavior: + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Environment (please complete the following information):** + - Python version: [e.g. 3.5] + - Virtual Env: [e.g. virtualenv, conda] + - OS: [e.g. Mac, Ubuntu] + - python-binance version + +**Logs or Additional context** +Add any other context about the problem here. diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 000000000..38588b08e --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,76 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + workflow_dispatch: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install Ruff + run: pip install ruff + - name: Lint code with Ruff + run: ruff check --output-format=github --target-version=py39 . + continue-on-error: true # TODO: delete once ruff errors are fixed + - name: Check code formatting with Ruff + run: ruff format --check . + continue-on-error: true # TODO: delete once ruff errors are fixed + + build: + needs: lint + runs-on: ubuntu-latest + env: + PROXY: "http://51.83.140.52:16301" + strategy: + matrix: + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Checking env + run: | + echo "PROXY: $PROXY" + env + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pyright tox + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi + - name: Type check with pyright + run: pyright + - name: Test with tox + run: tox -e py + - name: Coveralls Parallel + uses: coverallsapp/github-action@v2 + with: + flag-name: run-${{ join(matrix.*, '-') }} + parallel: true + finish: + needs: build + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true diff --git a/.gitignore b/.gitignore index de43a747d..d531a3c50 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ binance/__pycache__/ build/ dist/ python_binance.egg-info/ +*__pycache__ +*.egg-info/ +.idea/ +venv*/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..ba957bf85 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b7bfb982e..3d1ca122c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ +dist: xenial + language: python python: - - "2.7" - - "3.4" - - "3.5" - "3.6" + - "3.7" + - "3.8" + - "3.9" install: - pip install -r test-requirements.txt diff --git a/Endpoints.md b/Endpoints.md new file mode 100644 index 000000000..2b54816ad --- /dev/null +++ b/Endpoints.md @@ -0,0 +1,882 @@ +> :warning: **Disclaimer**: + + > * Before using the endpoints, please check the [API documentation](https://binance-docs.github.io/apidocs/#change-log) to be informed about the latest changes or possible bugs/problems. + + > * Not all parameters are mandatory. Some parameters are only mandatory in specific conditions/types. Check the official documentation the type of each parameter and to know if a parameter is mandatory or optional. + + > * This documentation only includes methods in client.py file. Websocket methods haven't (yet) been covered. + +### [Spot/Margin/Saving/Mining Endpoints](https://binance-docs.github.io/apidocs/spot/en/) +- *Wallet Endpoints* + - **GET /sapi/v1/system/status** (Fetch system status.) + ```python + client.get_system_status() + ``` + - **GET /sapi/v1/capital/config/getall (HMAC SHA256)** (Get information of coins (available for deposit and withdraw) for user.) + ```python + client.get_all_coins_info() + ``` + - **GET /sapi/v1/accountSnapshot (HMAC SHA256)** (Daily Account Snapshot (USER_DATA).) + ```python + client.get_account_snapshot(type='SPOT') + ``` + - **POST /sapi/v1/account/disableFastWithdrawSwitch (HMAC SHA256)** (Disable Fast Withdraw Switch (USER_DATA).) + ```python + client.disable_fast_withdraw_switch(type='SPOT') + ``` + - **POST /sapi/v1/account/enableFastWithdrawSwitch (HMAC SHA256)** (Enable Fast Withdraw Switch (USER_DATA).) + ```python + client.enable_fast_withdraw_switch(type='SPOT') + ``` + - **POST /sapi/v1/capital/withdraw/apply (HMAC SHA256)** (Withdraw: Submit a withdraw request.) + ```python + client.withdraw(coin, + withdrawOrderId, + network, + address, + addressTag, + amount, + transactionFeeFlag, + name, + recvWindow) + ``` + - **GET /sapi/v1/capital/deposit/hisrec (HMAC SHA256)** (Fetch Deposit History(supporting network) (USER_DATA).) + ```python + client.get_deposit_history(coin, status, startTime, endTime, recvWindow) + ``` + - **GET /sapi/v1/capital/withdraw/history (HMAC SHA256)** (Fetch Withdraw History (supporting network) (USER_DATA).) + ```python + client.get_withdraw_history(coin, status, startTime, endTime, recvWindow) + ``` + - **GET /sapi/v1/capital/deposit/address (HMAC SHA256)** (Fetch deposit address with network.) + ```python + client.get_deposit_address(coin, status, recvWindow) + ``` + - **GET /sapi/v1/account/status** (Fetch account status detail.) + ```python + client.get_account_status(recvWindow) + ``` + - **GET /sapi/account/apiTradingStatus** (Fetch account api trading status detail.) + ```python + client.get_account_api_trading_status(recvWindow) + ``` + - **GET /sapi/v1/asset/dribblet (HMAC SHA256)** (DustLog: Fetch small amounts of assets exchanged BNB records.) + ```python + client.get_dust_log(recvWindow) + ``` + - **Post /sapi/v1/asset/dust (HMAC SHA256)** (Dust Transfer: Convert dust assets to BNB.) + ```python + client.transfer_dust(asset, recvWindow) + ``` + - **Get /sapi/v1/asset/assetDividend (HMAC SHA256)** (Query asset dividend record.) + ```python + client.get_asset_dividend_history(asset, startTime, endTime, limit, recvWindow) + ``` + - **GET /sapi/v1/asset/assetDetail (HMAC SHA256)** (Fetch details of assets supported on Binance.) + ```python + client.get_asset_details(recvWindow) + ``` + - **GET /sapi/v1/asset/tradeFee (HMAC SHA256)** (Fetch trade fee, values in percentage.) + ```python + client.get_trade_fee(symbol, recvWindow) + ``` +- *Market Data Endpoints* + - **GET /api/v3/ping** (Test connectivity to the Rest API.) + ```python + client.ping() + ``` + - **GET /api/v3/time** (Test connectivity to the Rest API and get the current server time.) + ```python + client.get_server_time() + ``` + - **GET /api/v3/exchangeInfo** (Current exchange trading rules and symbol information.) + ```python + client.get_exchange_info() + ``` + - **GET /api/v3/depth** (Get the Order Book for the market.) + ```python + client.get_order_book(symbol, limit) + ``` + - **GET /api/v3/trades** (Get recent trades (up to last 500)) + ```python + client.get_recent_trades(symbol, limit) + ``` + - **GET /api/v3/historicalTrades** (Get older market trades.) + ```python + client.get_historical_trades(symbol, limit, fromId) + ``` + - **GET /api/v3/aggTrades** (Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same price will have the quantity aggregated.) + ```python + client.get_aggregate_trades(symbol, fromId, startTime, endTime, limit) + + # Wrapper function: Iterate over aggregate trade data from (start_time or last_id) the end of the history so far: + client.aggregate_trade_iter(symbol, start_str, last_id) + ``` + - **GET /api/v3/klines** (Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.) + ```python + client.get_klines(symbol, interval, startTime, endTime, limit) + + # Wrapper function: Iterate over klines data from client.get_klines() + client.get_historical_klines(symbol, interval, start_str, end_str, limit) + ``` + - **GET /api/v3/avgPrice** (Current average price for a symbol.) + ```python + client.get_avg_price(symbol) + ``` + - **GET /api/v3/ticker/24hr** (24 hour rolling window price change statistics. **Careful** when accessing this with no symbol.) + ```python + client.get_ticker(symbol) + ``` + - **GET /api/v3/ticker/price** (Latest price for a symbol or symbols.) + ```python + client.get_symbol_ticker(symbol) + ``` + - **GET /api/v3/ticker/bookTicker** (Best price/qty on the order book for a symbol or symbols.) + ```python + client.get_orderbook_ticker(symbol) + ``` +- *Spot Account/Trade Endpoints* + - **POST /api/v3/order/test (HMAC SHA256)** (Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine.) + ```python + client.create_test_order(symbol, + side, + type, + timeInForce, + quantity, + quoteOrderQty, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + recvWindow) + ``` + - **POST /api/v3/order (HMAC SHA256)** (Send in a new order.) + ```python + client.create_order(symbol, + side, + type, + timeInForce, + quantity, + quoteOrderQty, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + recvWindow) + + ## Wrapper functions: + # Send in a new limit order. + # Default parameters: timeInForce=Client.TIME_IN_FORCE_GTC, type=Client.ORDER_TYPE_LIMIT + client.order_limit(symbol, + side, + quantity, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + recvWindow) + + # Send in a new limit buy order. + # Default parameters: timeInForce=Client.TIME_IN_FORCE_GTC, type=Client.ORDER_TYPE_LIMIT, side=Client.SIDE_BUY + client.order_limit_buy(symbol, + quantity, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + recvWindow) + + # Send in a new limit sell order. + # Default parameters: timeInForce=Client.TIME_IN_FORCE_GTC, type=Client.ORDER_TYPE_LIMIT, side= Client.SIDE_SELL + client.order_limit_sell(symbol, + quantity, + price, + newClientOrderId, + stopPrice, + icebergQty, + newOrderRespType, + recvWindow) + + # Send in a new market order. + # Default parameters: type=Client.ORDER_TYPE_MARKET + client.order_market(symbol, + side, + quantity, + quoteOrderQty, + newClientOrderId, + newOrderRespType, + recvWindow) + + # Send in a new market buy order. + # Default parameters: type=Client.ORDER_TYPE_MARKET, side=Client.SIDE_BUY + client.order_market_buy(symbol, + quantity, + quoteOrderQty, + newClientOrderId, + newOrderRespType, + recvWindow) + + # Send in a new market sell order. + # Default parameters: type=Client.ORDER_TYPE_MARKET, side=Client.SIDE_SELL + client.order_market_sell(symbol, + quantity, + quoteOrderQty, + newClientOrderId, + newOrderRespType, + recvWindow) + ``` + - **DELETE /api/v3/order (HMAC SHA256)** (Cancel an active order.) + ```python + client.cancel_order(symbol, orderId, origClientOrderId, newClientOrderId, recvWindow) + ``` + - **DELETE api/v3/openOrders** (Cancels all active orders on a symbol. This includes OCO orders.) + + > :warning: Not yet implemented + - **GET /api/v3/order (HMAC SHA256)** (Check an order's status.) + ```python + client.get_order(symbol, orderId, origClientOrderId, recvWindow) + ``` + - **GET /api/v3/openOrders (HMAC SHA256)** (Get all open orders on a symbol. **Careful** when accessing this with no symbol.) + ```python + client.get_open_orders(symbol, recvWindow) + ``` + - **GET /api/v3/allOrders (HMAC SHA256)** (Get all account orders; active, canceled, or filled.) + ```python + client.get_all_orders(symbol, orderId, startTime, endTime, limit, recvWindow) + ``` + - **POST /api/v3/order/oco (HMAC SHA256)** (Send in a new OCO order) + ```python + client.create_oco_order(symbol, + listClientOrderId, + side, + quantity, + limitClientOrderId, + price, + limitIcebergQty, + stopClientOrderId, + stopPrice, + stopLimitPrice, + stopIcebergQty, + stopLimitTimeInForce, + newOrderRespType, + recvWindow) + + ## Wrapper Functions: + + # Send in a new OCO buy order. Default parameter: type=Client.SIDE_BUY + client.order_oco_buy(symbol, + listClientOrderId, + quantity, + limitClientOrderId, + price, + limitIcebergQty, + stopClientOrderId, + stopPrice, + stopLimitPrice, + stopIcebergQty, + stopLimitTimeInForce, + newOrderRespType, + recvWindow) + + # Send in a new OCO sell order. Default parameter: type=Client.SIDE_SELL + client.order_oco_sell(symbol, + listClientOrderId, + quantity, + limitClientOrderId, + price, + limitIcebergQty, + stopClientOrderId, + stopPrice, + stopLimitPrice, + stopIcebergQty, + stopLimitTimeInForce, + newOrderRespType, + recvWindow) + ``` + - **DELETE /api/v3/orderList (HMAC SHA256)** (Cancel OCO: Cancel an entire Order List) + + > :warning: Not yet implemented + - **GET /api/v3/orderList (HMAC SHA256)** (Query OCO: Retrieves a specific OCO based on provided optional parameters) + + > :warning: Not yet implemented + - **GET /api/v3/allOrderList (HMAC SHA256)** (Retrieves all OCO based on provided optional parameters) + + > :warning: Not yet implemented + - **GET /api/v3/openOrderList (HMAC SHA256)** (Query Open OCO (USER_DATA)) + + > :warning: Not yet implemented + - **GET /api/v3/account (HMAC SHA256)** (Get current account information.) + ```python + client.get_account(recvWindow) + ``` + - **GET /api/v3/myTrades (HMAC SHA256)** (Get trades for a specific account and symbol.) + ```python + client.get_my_trades(symbol, startTime, endTime, fromId, limit, recvWindow) + ``` +- *Margin Account/Trade* + - **POST /sapi/v1/margin/transfer (HMAC SHA256)** (Execute transfer between margin account and spot account(MARGIN).) + ```python + client.transfer_margin_to_spot(asset, amount, recvWindow) + client.transfer_spot_to_margin(asset, amount, recvWindow) + ``` + - **POST /sapi/v1/margin/loan (HMAC SHA256)** (Apply for a loan(MARGIN).) + ```python + client.create_margin_loan(asset, isIsolated, symbol, amount, recvWindow) + ``` + - **POST /sapi/v1/margin/repay (HMAC SHA256)** (Repay loan for margin account (MARGIN).) + ```python + client.repay_margin_loan(asset, isIsolated, symbol, amount, recvWindow) + ``` + - **GET /sapi/v1/margin/asset** (Query Margin Asset (MARKET_DATA).) + ```python + client.get_margin_asset(asset) + ``` + - **GET /sapi/v1/margin/pair** (Query Cross Margin Pair (MARKET_DATA).) + ```python + client.get_margin_symbol(symbol) + ``` + - **GET /sapi/v1/margin/allAssets** (Get All Cross Margin Assets (MARKET_DATA).) + ```python + client.get_margin_all_assets() + ``` + - **GET /sapi/v1/margin/allPairs** (Get All Cross Margin Pairs (MARKET_DATA).) + ```python + client.get_margin_all_pairs() + ``` + - **GET /sapi/v1/margin/priceIndex** (Query Margin PriceIndex (MARKET_DATA).) + ```python + client.get_margin_price_index(symbol) + ``` + - **POST /sapi/v1/margin/order (HMAC SHA256)** (Post a new order for margin account.) + ```python + client.create_margin_order(symbol, + isIsolated, + side, + type, + quantity, + price, + stopPrice, + newClientOrderId, + icebergQty, + newOrderRespType, + sideEffectType, + timeInForce, + recvWindow) + ``` + - **DELETE /sapi/v1/margin/order (HMAC SHA256)** (Cancel an active order for margin account.) + ```python + client.cancel_margin_order(symbol, + isIsolated, + orderId, + origClientOrderId, + newClientOrderId, + recvWindow) + ``` + - **GET /sapi/v1/margin/transfer (HMAC SHA256)** (Get Cross Margin Transfer History (USER_DATA).) + ```python + client.transfer_margin_to_spot(asset, amount, recvWindow) + client.transfer_spot_to_margin(asset, amount, recvWindow) + ``` + - **GET /sapi/v1/margin/loan (HMAC SHA256)** (Query Loan Record (USER_DATA).) + ```python + client.get_margin_loan_details(asset, isolatedSymbol, txId, startTime, endTime, current, size, recvWindow) + ``` + - **GET /sapi/v1/margin/repay (HMAC SHA256)** (Query repay record (USER_DATA).) + ```python + client.get_margin_repay_details(asset, isolatedSymbol, txId, startTime, endTime, current, size, recvWindow) + ``` + - **GET /sapi/v1/margin/interestHistory (HMAC SHA256)** (Get Interest History (USER_DATA).) + ```python + client.get_margin_interest_history(asset, isolatedSymbol, startTime, endTime, current, size, archived, recvWindow) + ``` + - **GET /sapi/v1/margin/forceLiquidationRec (HMAC SHA256)** (Get Force Liquidation Record (USER_DATA).) + ```python + client.get_margin_force_liquidation_rec(isolatedSymbol, startTime, endTime, current, size, recvWindow) + ``` + - **GET /sapi/v1/margin/account (HMAC SHA256)** (Query Cross Margin Account Details (USER_DATA).) + ```python + client.get_margin_account(recvWindow) + ``` + - **GET /sapi/v1/margin/order (HMAC SHA256)** (Query Margin Account's Order (USER_DATA).) + ```python + client.get_margin_order(symbol, isIsolated, orderId, origClientOrderId, recvWindow) + ``` + - **GET /sapi/v1/margin/openOrders (HMAC SHA256)** (Query Margin Account's Open Order (USER_DATA).) + ```python + client.get_open_margin_orders(symbol, isIsolated, recvWindow) + ``` + - **GET /sapi/v1/margin/allOrders (HMAC SHA256)** (Query Margin Account's All Order (USER_DATA).) + ```python + client.get_all_margin_orders(symbol, isIsolated, orderId, startTime, endTime, limit, recvWindow) + ``` + - **GET /sapi/v1/margin/myTrades (HMAC SHA256)** (Query Margin Account's Trade List (USER_DATA).) + ```python + client.get_margin_trades(symbol, isIsolated, startTime, endTime, fromId, limit, recvWindow) + ``` + - **GET /sapi/v1/margin/maxBorrowable (HMAC SHA256)** (Query Max Borrow amount for an asset (USER_DATA).) + ```python + client.get_max_margin_loan(asset, isolatedSymbol, recvWindow) + ``` + - **GET /sapi/v1/margin/maxTransferable (HMAC SHA256)** (Query Max Transfer-Out Amount (USER_DATA).) + ```python + client.get_max_margin_transfer(asset, isolatedSymbol, recvWindow) + ``` + - **POST /sapi/v1/margin/isolated/create (HMAC SHA256)** (Create Isolated Margin Account (MARGIN).) + ```python + client.create_isolated_margin_account(base, quote, recvWindow) + ``` + - **POST /sapi/v1/margin/isolated/transfer (HMAC SHA256)** (Isolated Margin Account Transfer (MARGIN).) + ```python + client.transfer_spot_to_isolated_margin(asset, symbol, amount, recvWindow) + client.transfer_isolated_margin_to_spot(asset, symbol, amount, recvWindow) + ``` + - **GET /sapi/v1/margin/isolated/transfer (HMAC SHA256)** (Get Isolated Margin Transfer History (USER_DATA).) + + > :warning: Not yet implemented + - **GET /sapi/v1/margin/isolated/account (HMAC SHA256)** (Query Isolated Margin Account Info (USER_DATA).) + ```python + client.get_isolated_margin_account(symbols, recvWindow) + ``` + - **GET /sapi/v1/margin/isolated/pair (HMAC SHA256)** (Query Isolated Margin Symbol (USER_DATA).) + ```python + client.get_isolated_margin_symbol(symbol, recvWindow) + ``` + - **GET /sapi/v1/margin/isolated/allPairs (HMAC SHA256)** (Get All Isolated Margin Symbol (USER_DATA).) + ```python + client.get_all_isolated_margin_symbols(recvWindow) + ``` + - **POST /sapi/v1/margin/manual-liquidation (HMAC SHA256)** (Margin manual liquidation (MARGIN).) + ```python + client.margin_manual_liquidation(type) + ``` +- *User Data Streams* + - **POST /api/v3/userDataStream** (Create a ListenKey (Spot) (USER_STREAM): Start a new user data stream.) + ```python + client.stream_get_listen_key() + ``` + - **PUT /api/v3/userDataStream** (Ping/Keep-alive a ListenKey (Spot) (USER_STREAM).) + ```python + client.stream_keepalive(listenKey) + ``` + - **DELETE /api/v3/userDataStream** (Close a ListenKey (Spot) (USER_STREAM).) + ```python + client.stream_close(listenKey) + ``` + - **POST /sapi/v1/userDataStream** (Create a ListenKey (Margin).) + ```python + client.margin_stream_get_listen_key() + ``` + - **PUT /sapi/v1/userDataStream** (Ping/Keep-alive a ListenKey (Margin).) + ```python + client.margin_stream_keepalive(listenKey) + ``` + - **DELETE /sapi/v1/userDataStream** (Close a ListenKey (Margin).) + ```python + client.margin_stream_close(listenKey) + ``` + - **POST /sapi/v1/userDataStream/isolated** (Create a ListenKey (Isolated).) + ```python + client.isolated_margin_stream_get_listen_key(symbol) + ``` + - **PUT /sapi/v1/userDataStream/isolated** (Ping/Keep-alive a ListenKey (Isolated).) + ```python + client.isolated_margin_stream_keepalive(symbol, listenKey) + ``` + - **DELETE /sapi/v1/userDataStream/isolated** (Close a ListenKey (Isolated).) + ```python + client.isolated_margin_stream_close(symbol, listenKey) + ``` +- *Savings Endpoints* + - **GET /sapi/v1/lending/daily/product/list (HMAC SHA256)** (Get Flexible Product List (USER_DATA).) + ```python + client.get_lending_product_list(status, featured, recvWindow) + ``` + - **GET /sapi/v1/lending/daily/userLeftQuota (HMAC SHA256)** (Get Left Daily Purchase Quota of Flexible Product (USER_DATA).) + ```python + client.get_lending_daily_quota_left(productId, recvWindow) + ``` + - **POST /sapi/v1/lending/daily/purchase (HMAC SHA256)** (Purchase Flexible Product (USER_DATA).) + ```python + client.purchase_lending_product(productId, amount, recvWindow) + ``` + - **GET /sapi/v1/lending/daily/userRedemptionQuota (HMAC SHA256)** (Get Left Daily Redemption Quota of Flexible Product (USER_DATA).) + ```python + client.get_lending_daily_redemption_quota(productId, type, recvWindow) + ``` + - **POST /sapi/v1/lending/daily/redeem (HMAC SHA256)** (Redeem Flexible Product (USER_DATA).) + ```python + client.redeem_lending_product(productId, amount, type, recvWindow) + ``` + - **GET /sapi/v1/lending/daily/token/position (HMAC SHA256)** (Get Flexible Product Position (USER_DATA).) + ```python + client.get_lending_position(asset, recvWindow) + ``` + - **GET /sapi/v1/lending/project/list (HMAC SHA256)** (Get Fixed and Activity Project List (USER_DATA).) + ```python + client.get_fixed_activity_project_list(asset, type, status, isSortAsc, sortBy, current, size, recvWindow) + ``` + - **POST /sapi/v1/lending/customizedFixed/purchase (HMAC SHA256)** (Purchase Fixed/Activity Project (USER_DATA).) + + > :warning: Not yet implemented + - **GET /sapi/v1/lending/project/position/list (HMAC SHA256)** (Get Fixed/Activity Project Position (USER_DATA).) + + > :warning: Not yet implemented + - **GET /sapi/v1/lending/union/account (HMAC SHA256)** (Lending Account (USER_DATA).) + ```python + client.get_lending_account(recvWindow) + ``` + - **GET /sapi/v1/lending/union/purchaseRecord (HMAC SHA256)** (Get Purchase Record (USER_DATA).) + ```python + client.get_lending_purchase_history(lendingType, asset, startTime, endTime, current, size, recvWindow) + ``` + - **GET /sapi/v1/lending/union/redemptionRecord (HMAC SHA256)** (Get Redemption Record (USER_DATA).) + ```python + client.get_lending_redemption_history(lendingType, asset, startTime, endTime, current, size, recvWindow) + ``` + - **GET /sapi/v1/lending/union/interestHistory (HMAC SHA256)** (Get Interest History (USER_DATA).) + ```python + client.get_lending_interest_history(lendingType, asset, startTime, endTime, current, size, recvWindow) + ``` + - **POST /sapi/v1/lending/positionChanged (HMAC SHA256)** (Change Fixed/Activity Position to Daily Position (USER_DATA).) + ```python + client.change_fixed_activity_to_daily_position(projectId, lot, positionId, recvWindow) + ``` +- *Mining Endpoints* + > :warning: Not yet implemented +- *Sub-Account Endpoints* + - **GET /sapi/v1/sub-account/list (HMAC SHA256)** (Query Sub-account List (For Master Account).) + ```python + client.get_sub_account_list(email, isFreeze, page, limit, recvWindow) + ``` + - **GET /sapi/v1/sub-account/sub/transfer/history (HMAC SHA256)** (Query Sub-account Spot Asset Transfer History (For Master Account).) + ```python + client.get_sub_account_transfer_history(fromEmail, toEmail, startTime, endTime, page, limit, recvWindow) + ``` + - **GET /sapi/v1/sub-account/assets (HMAC SHA256)** (Query Sub-account Assets (For Master Account).) + ```python + client.get_sub_account_assets(email, recvWindow) + ``` + > :warning: The rest of methods for Sub-Account Endpoints are not yet implemented +- *BLVT Endpoints* + > :warning: Not yet implemented +- *BSwap Endpoints* + > :warning: Not yet implemented +### [USDT-M Futures](https://binance-docs.github.io/apidocs/futures/en/) +- *Market Data Endpoints* + - **GET /fapi/v1/ping** (Test connectivity to the Rest API.) + ```python + client.futures_ping() + ``` + - **GET /fapi/v1/time** (Test connectivity to the Rest API and get the current server time.) + ```python + client.futures_time() + ``` + - **GET /fapi/v1/exchangeInfo** (Current exchange trading rules and symbol information.) + ```python + client.futures_exchange_info() + ``` + - **GET /fapi/v1/depth** (Get the Order Book for the market.) + ```python + client.futures_order_book(symbol, limit) + ``` + - **GET /fapi/v1/trades** (Get recent trades.) + ```python + client.futures_recent_trades(symbol, limit) + ``` + - **GET /fapi/v1/historicalTrades** (Get older market historical trades (MARKET_DATA).) + ```python + client.futures_historical_trades(symbol, limit, fromId) + ``` + - **GET /fapi/v1/aggTrades** (Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same price will have the quantity aggregated.) + ```python + client.futures_aggregate_trades(symbol, fromId, startTime, endTime, limit) + ``` + - **GET /fapi/v1/klines** (Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.) + ```python + client.futures_klines(symbol, interval, startTime, endTime, limit) + ``` + - **GET /fapi/v1/premiumIndex** (Get Mark Price and Funding Rate.) + ```python + client.futures_mark_price(symbol) + ``` + - **GET /fapi/v1/fundingRate** (Get Funding Rate History.) + ```python + client.futures_funding_rate(symbol, startTime, endTime, limit) + ``` + - **GET /fapi/v1/ticker/24hr** (24 hour rolling window price change statistics. **Careful** when accessing this with no symbol.) + ```python + client.futures_ticker(symbol) + ``` + - **GET /fapi/v1/ticker/price** (Latest price for a symbol or symbols.) + ```python + client.futures_symbol_ticker(symbol) + ``` + - **GET /fapi/v1/ticker/bookTicker** (Best price/qty on the order book for a symbol or symbols.) + ```python + client.futures_orderbook_ticker(symbol) + ``` + - **GET /fapi/v1/allForceOrders** (Get all Liquidation Orders.) + > :warning: Probably broken, python code below is implemented on v1/ticker/allForceOrders endpoint. + ```python + client.futures_liquidation_orders(symbol, startTime, endTime, limit) + ``` + - **GET /fapi/v1/openInterest** (Get present open interest of a specific symbol.) + ```python + client.futures_open_interest(symbol) + ``` + - **GET /futures/data/openInterestHist** (Open Interest Statistics.) + + > :warning: Not yet implemented + - **GET /futures/data/topLongShortAccountRatio** (Top Trader Long/Short Ratio (Accounts) (MARKET_DATA).) + + > :warning: Not yet implemented + - **GET /futures/data/topLongShortPositionRatio** (Top Trader Long/Short Ratio (Positions).) + + > :warning: Not yet implemented + - **GET /futures/data/globalLongShortAccountRatio** (Long/Short Ratio.) + + > :warning: Not yet implemented + - **GET /futures/data/takerlongshortRatio** (Taker Buy/Sell Volume.) + + > :warning: Not yet implemented + - **GET /fapi/v1/lvtKlines** (Historical BLVT NAV Kline/Candlestick.) + + > :warning: Not yet implemented + - **GET /fapi/v1/indexInfo** (Composite Index Symbol Information.) + + > :warning: Not yet implemented +- *Account/trades Endpoints* + - **POST /sapi/v1/futures/transfer (HMAC SHA256)** (New Future Account Transfer (FUTURES): Execute transfer between spot account and futures account.) + ```python + client.futures_account_transfer(asset, amount, type, recvWindow) + ``` + - **GET /sapi/v1/futures/transfer (HMAC SHA256)** (Get Future Account Transaction History List (USER_DATA).) + ```python + client.transfer_history(asset, startTime, endTime, current, size, recvWindow) + ``` + - **POST /fapi/v1/positionSide/dual (HMAC SHA256)** (Change user's position mode (Hedge Mode or One-way Mode ) on _**EVERY symbol**_.) + ```python + client.futures_change_position_mode(dualSidePosition, recvWindow) + ``` + - **GET /fapi/v1/positionSide/dual (HMAC SHA256)** (Get user's position mode (Hedge Mode or One-way Mode ) on _**EVERY symbol**_.) + ```python + client.futures_get_position_mode(recvWindow) + ``` + - **POST /fapi/v1/order (HMAC SHA256)** (Send in a new order (TRADE).) + ```python + client.futures_create_order(symbol, + side, + positionSide, + type, + timeInForce, + quantity, + reduceOnly, + price, + newClientOrderId, + stopPrice, + closePosition, + activationPrice, + callbackRate, + workingType, + priceProtect, + newOrderRespType, + recvWindow) + ``` + - **POST /fapi/v1/batchOrders (HMAC SHA256)** (Place Multiple Orders (TRADE).) + + > :warning: Not yet implemented + - **GET /fapi/v1/order (HMAC SHA256)** (Query Order (USER_DATA): Check an order's status.) + ```python + client.futures_get_order(symbol, orderId, origClientOrderId, recvWindow) + ``` + - **DELETE /fapi/v1/order (HMAC SHA256)** (Cancel an active order (TRADE).) + ```python + client.futures_cancel_order(symbol, orderId, origClientOrderId, recvWindow) + ``` + - **DELETE /fapi/v1/allOpenOrders (HMAC SHA256)** (Cancel All Open Orders (TRADE).) + ```python + client.futures_cancel_all_open_orders(symbol, recvWindow) + ``` + - **DELETE /fapi/v1/batchOrders (HMAC SHA256)** (Cancel Multiple Orders (TRADE).) + ```python + client.futures_cancel_orders(symbol, orderIdList, origClientOrderIdList, recvWindow) + ``` + - **POST /fapi/v1/countdownCancelAll (HMAC SHA256)** (Cancel all open orders of the specified symbol at the end of the specified countdown (TRADE).) + + > :warning: Not yet implemented + - **GET /fapi/v1/openOrder (HMAC SHA256)** (Query Current Open Order (USER_DATA).) + + > :warning: Not yet implemented + - **GET /fapi/v1/openOrders (HMAC SHA256)** (Get all open orders on a symbol. **Careful** when accessing this with no symbol (USER_DATA).) + ```python + client.futures_get_open_orders(symbol, recvWindow) + ``` + - **GET /fapi/v1/allOrders (HMAC SHA256)** (Get all account orders; active, canceled, or filled (USER_DATA).) + ```python + client.futures_get_all_orders(symbol, orderId, startTime, endTime, limit, recvWindow) + ``` + - **GET /fapi/v2/balance (HMAC SHA256)** (Futures Account Balance V2 (USER_DATA).) + > :warning: Probably broken, python code below is implemented on v1 endpoint. + ```python + client.futures_account_balance(recvWindow) + ``` + - **GET /fapi/v2/account (HMAC SHA256)** (Account Information V2: Get current account information (USER_DATA).) + > :warning: Probably broken, python code below is implemented on v1 endpoint. + ```python + client.futures_account(recvWindow) + ``` + - **POST /fapi/v1/leverage (HMAC SHA256)** (Change user's initial leverage of specific symbol market (TRADE).) + ```python + client.futures_change_leverage(symbol, leverage, recvWindow) + ``` + - **POST /fapi/v1/marginType (HMAC SHA256)** (Change the margin type for a symbol (TRADE).) + ```python + client.futures_change_margin_type(symbol, marginType, recvWindow) + ``` + - **POST /fapi/v1/positionMargin (HMAC SHA256)** (Modify Isolated Position Margin (TRADE).) + ```python + client.futures_change_position_margin(symbol, positionSide, amount, type, recvWindow) + ``` + - **GET /fapi/v1/positionMargin/history (HMAC SHA256)** (Get Position Margin Change History (TRADE).) + ```python + client.futures_position_margin_history(symbol, type, startTime, endTime, limit, recvWindow) + ``` + - **GET /fapi/v2/positionRisk (HMAC SHA256)** (Position Information V2: Get current position information (USER_DATA).) + > :warning: Probably broken, python code below is implemented on v1 endpoint. + ```python + client.futures_position_information(symbol, recvWindow) + ``` + - **GET /fapi/v1/userTrades (HMAC SHA256)** (Account Trade List: Get trades for a specific account and symbol (USER_DATA).) + ```python + client.futures_account_trades(symbol, startTime, endTime, fromId, limit, recvWindow) + ``` + - **GET /fapi/v1/income (HMAC SHA256)** (Get Income History (USER_DATA).) + ```python + client.futures_income_history(symbol, incomeType, startTime, endTime, limit, recvWindow) + ``` + - **GET /fapi/v1/leverageBracket** (Notional and Leverage Brackets (USER_DATA).) + > :warning: Probably broken, python code below is implemented on ticker/leverageBracket endpoint. + ```python + client.futures_leverage_bracket(symbol, recvWindow) + ``` + - **GET /fapi/v1/adlQuantile** (Position ADL Quantile Estimation (USER_DATA).) + + > :warning: Not yet implemented + - **GET /fapi/v1/forceOrders** (User's Force Orders (USER_DATA).) + + > :warning: Not yet implemented + - **GET /fapi/v1/apiTradingStatus** (User API Trading Quantitative Rules Indicators (USER_DATA).) + + > :warning: Not yet implemented +- *User Data Streams* + > :warning: Not yet implemented +### [Vanilla Options](https://binance-docs.github.io/apidocs/voptions/en/) +- *Quoting interface* + - **GET /vapi/v1/ping** (Test connectivity) + ```python + client.options_ping() + ``` + - **GET /vapi/v1/time** (Get server time) + ```python + client.options_time() + ``` + - **GET /vapi/v1/optionInfo** (Get current trading pair info) + ```python + client.options_info() + ``` + - **GET /vapi/v1/exchangeInfo** (Get current limit info and trading pair info) + ```python + client.options_exchange_info() + ``` + - **GET /vapi/v1/index** (Get the spot index price) + ```python + client.options_index_price(underlying) + ``` + - **GET /vapi/v1/ticker** (Get the latest price) + ```python + client.options_price(symbol) + ``` + - **GET /vapi/v1/mark** (Get the latest mark price) + ```python + client.options_mark_price(symbol) + ``` + - **GET /vapi/v1/depth** (Depth information) + ```python + client.options_order_book(symbol, limit) + ``` + - **GET /vapi/v1/klines** (Candle data) + ```python + client.options_klines(symbol, interval, startTime, endTime, limit) + ``` + - **GET /vapi/v1/trades** (Recently completed Option trades) + ```python + client.options_recent_trades(symbol, limit) + ``` + - **GET /vapi/v1/historicalTrades** (Query trade history) + ```python + client.options_historical_trades(symbol, fromId, limit) + ``` +- *Account and trading interface* + - **GET /vapi/v1/account (HMAC SHA256)** (Account asset info (USER_DATA)) + ```python + client.options_account_info(recvWindow) + ``` + - **POST /vapi/v1/transfer (HMAC SHA256)** (Funds transfer (USER_DATA)) + ```python + client.options_funds_transfer(currency, type, amount, recvWindow) + ``` + - **GET /vapi/v1/position (HMAC SHA256)** (Option holdings info (USER_DATA)) + ```python + client.options_positions(symbol, recvWindow) + ``` + - **POST /vapi/v1/bill (HMAC SHA256)** (Account funding flow (USER_DATA)) + ```python + client.options_bill(currency, recordId, startTime, endTime, limit, recvWindow) + ``` + - **POST /vapi/v1/order (HMAC SHA256)** (Option order (TRADE)) + ```python + client.options_place_order(symbol, side, type, quantity, price, timeInForce, reduceOnly, postOnly, \ + newOrderRespType, clientOrderId, recvWindow, recvWindow) + ``` + - **POST /vapi/v1/batchOrders (HMAC SHA256)** (Place Multiple Option orders (TRADE)) + ```python + client.options_place_batch_order(orders, recvWindow) + ``` + - **DELETE /vapi/v1/order (HMAC SHA256)** (Cancel Option order (TRADE)) + ```python + client.options_cancel_order(symbol, orderId, clientOrderId, recvWindow) + ``` + - **DELETE /vapi/v1/batchOrders (HMAC SHA256)** (Cancel Multiple Option orders (TRADE)) + ```python + client.options_cancel_batch_order(symbol, orderIds, clientOrderIds, recvWindow) + ``` + - **DELETE /vapi/v1/allOpenOrders (HMAC SHA256)** (Cancel all Option orders (TRADE)) + ```python + client.options_cancel_all_orders(symbol, recvWindow) + ``` + - **GET /vapi/v1/order (HMAC SHA256)** (Query Option order (TRADE)) + ```python + client.options_query_order(symbol, orderId, clientOrderId, recvWindow) + ``` + - **GET /vapi/v1/openOrders (HMAC SHA256)** (Query current pending Option orders (TRADE)) + ```python + client.options_query_pending_orders(symbol, orderId, startTime, endTime, limit, recvWindow) + ``` + - **GET /vapi/v1/historyOrders (HMAC SHA256)** (Query Option order history (TRADE)) + ```python + client.options_query_order_history(symbol, orderId, startTime, endTime, limit, recvWindow) + ``` + - **GET /vapi/v1/userTrades (HMAC SHA256)** (Option Trade List (USER_DATA)) + ```python + client.options_user_trades(symbol, fromId, startTime, endTime, limit, recvWindow) + ``` +### [COIN-M Futures](https://binance-docs.github.io/apidocs/delivery/en/) +> :warning: Not yet implemented +### [USDT-M Futures testnet](https://binance-docs.github.io/apidocs/testnet/en/) +> :warning: Not yet implemented +### [COIN-M Futures testnet](https://binance-docs.github.io/apidocs/delivery_testnet/en/) +> :warning: Not yet implemented diff --git a/PYPIREADME.rst b/PYPIREADME.rst index f0a4131f6..4156a3169 100644 --- a/PYPIREADME.rst +++ b/PYPIREADME.rst @@ -1,7 +1,3 @@ -================================ -Welcome to python-binance v0.7.0 -================================ - .. image:: https://img.shields.io/pypi/v/python-binance.svg :target: https://pypi.python.org/pypi/python-binance @@ -20,9 +16,12 @@ Welcome to python-binance v0.7.0 .. image:: https://img.shields.io/pypi/pyversions/python-binance.svg :target: https://pypi.python.org/pypi/python-binance -This is an unofficial Python wrapper for the `Binance exchange REST API v1 `_. I am in no way affiliated with Binance, use at your own risk. +This is an unofficial Python wrapper for the `Binance exchange REST API v3 `_. I am in no way affiliated with Binance, use at your own risk. + +If you came here looking for the `Binance exchange `_ to purchase cryptocurrencies, then `go here `_. +If you want to automate interactions with Binance stick around. -If you came here looking for the `Binance exchange `_ to purchase cryptocurrencies, then `go here `_. If you want to automate interactions with Binance stick around. +If you're interested in Binance's new DEX Binance Chain see my `python-binance-chain library `_ Source code https://github.com/sammchardy/python-binance @@ -33,13 +32,21 @@ Documentation Binance API Telegram https://t.me/binance_api_english +Blog with examples including async + https://sammchardy.github.io + +- `Async basics for Binance `_ +- `Understanding Binance Order Filters `_ + Make sure you update often and check the `Changelog `_ for new features and bug fixes. Features -------- - Implementation of all General, Market Data and Account endpoints. -- Simple handling of authentication +- Asyncio implementation +- Testnet support for Spot, Futures and Vanilla Options +- Simple handling of authentication include RSA keys - No need to generate timestamps yourself, the wrapper does it for you - Response exception handling - Websocket handling with reconnection and multiplexed connections @@ -47,13 +54,35 @@ Features - Historical Kline/Candle fetching function - Withdraw functionality - Deposit addresses +- Margin Trading +- Futures Trading +- Vanilla Options +- Support other domains (.us, .jp, etc) + +Upgrading to v1.0.0+ +-------------------- + +The breaking changes include the migration from wapi to sapi endpoints which related to the +wallet endpoints detailed in the `Binance Docs `_ + +The other breaking change is for websocket streams and the Depth Cache Manager which have been +converted to use Asynchronous Context Managers. See examples in the Async section below or view the +`websockets `_ and +`depth cache `_ docs. Quick Start ----------- -`Register an account with Binance `_. +`Register an account with Binance `_. + +`Generate an API Key `_ and assign relevant permissions. + +If you are using an exchange from the US, Japan or other TLD then make sure pass `tld='us'` when creating the +client. + +To use the `Spot `_ or `Vanilla Options `_ Testnet, +pass `testnet=True` when creating the client. -`Generate an API Key `_ and assign relevant permissions. .. code:: bash @@ -62,7 +91,7 @@ Quick Start .. code:: python - from binance.client import Client + from binance import Client, ThreadedWebsocketManager, ThreadedDepthCacheManager client = Client(api_key, api_secret) # get market depth @@ -80,7 +109,7 @@ Quick Start # withdraw 100 ETH # check docs for assumptions around withdrawals - from binance.exceptions import BinanceAPIException, BinanceWithdrawException + from binance.exceptions import BinanceAPIException try: result = client.withdraw( asset='ETH', @@ -88,8 +117,6 @@ Quick Start amount=100) except BinanceAPIException as e: print(e) - except BinanceWithdrawException as e: - print(e) else: print("Success") @@ -97,21 +124,10 @@ Quick Start withdraws = client.get_withdraw_history() # fetch list of ETH withdrawals - eth_withdraws = client.get_withdraw_history(asset='ETH') + eth_withdraws = client.get_withdraw_history(coin='ETH') # get a deposit address for BTC - address = client.get_deposit_address(asset='BTC') - - # start aggregated trade websocket for BNBBTC - def process_message(msg): - print("message type: {}".format(msg['e'])) - print(msg) - # do something - - from binance.websockets import BinanceSocketManager - bm = BinanceSocketManager(client) - bm.start_aggtrade_socket('BNBBTC', process_message) - bm.start() + address = client.get_deposit_address(coin='BTC') # get historical kline data from any date range @@ -122,11 +138,117 @@ Quick Start klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") # fetch weekly klines since it listed - klines = client.get_historical_klines("NEOBTC", KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + + # socket manager using threads + twm = ThreadedWebsocketManager() + twm.start() + + # depth cache manager using threads + dcm = ThreadedDepthCacheManager() + dcm.start() + + def handle_socket_message(msg): + print(f"message type: {msg['e']}") + print(msg) + + def handle_dcm_message(depth_cache): + print(f"symbol {depth_cache.symbol}") + print("top 5 bids") + print(depth_cache.get_bids()[:5]) + print("top 5 asks") + print(depth_cache.get_asks()[:5]) + print("last update time {}".format(depth_cache.update_time)) + + twm.start_kline_socket(callback=handle_socket_message, symbol='BNBBTC') + + dcm.start_depth_cache(callback=handle_dcm_message, symbol='ETHBTC') + + # replace with a current options symbol + options_symbol = 'BTC-210430-36000-C' + dcm.start_options_depth_cache(callback=handle_dcm_message, symbol=options_symbol) For more `check out the documentation `_. +Async Example +------------- + +Read `Async basics for Binance `_ +for more information. + +.. code:: python + + import asyncio + import json + + from binance import AsyncClient, DepthCacheManager, BinanceSocketManager + + async def main(): + + # initialise the client + client = await AsyncClient.create() + + # run some simple requests + print(json.dumps(await client.get_exchange_info(), indent=2)) + + print(json.dumps(await client.get_symbol_ticker(symbol="BTCUSDT"), indent=2)) + + # initialise websocket factory manager + bsm = BinanceSocketManager(client) + + # create listener using async with + # this will exit and close the connection after 5 messages + async with bsm.trade_socket('ETHBTC') as ts: + for _ in range(5): + res = await ts.recv() + print(f'recv {res}') + + # get historical kline data from any date range + + # fetch 1 minute klines for the last day up until now + klines = client.get_historical_klines("BNBBTC", AsyncClient.KLINE_INTERVAL_1MINUTE, "1 day ago UTC") + + # use generator to fetch 1 minute klines for the last day up until now + async for kline in await client.get_historical_klines_generator("BNBBTC", AsyncClient.KLINE_INTERVAL_1MINUTE, "1 day ago UTC"): + print(kline) + + # fetch 30 minute klines for the last month of 2017 + klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") + + # fetch weekly klines since it listed + klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + + # setup an async context the Depth Cache and exit after 5 messages + async with DepthCacheManager(client, symbol='ETHBTC') as dcm_socket: + for _ in range(5): + depth_cache = await dcm_socket.recv() + print(f"symbol {depth_cache.symbol} updated:{depth_cache.update_time}") + print("Top 5 asks:") + print(depth_cache.get_asks()[:5]) + print("Top 5 bids:") + print(depth_cache.get_bids()[:5]) + + # Vanilla options Depth Cache works the same, update the symbol to a current one + options_symbol = 'BTC-210430-36000-C' + async with OptionsDepthCacheManager(client, symbol=options_symbol) as dcm_socket: + for _ in range(5): + depth_cache = await dcm_socket.recv() + count += 1 + print(f"symbol {depth_cache.symbol} updated:{depth_cache.update_time}") + print("Top 5 asks:") + print(depth_cache.get_asks()[:5]) + print("Top 5 bids:") + print(depth_cache.get_bids()[:5]) + + await client.close_connection() + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + + Donate ------ @@ -140,11 +262,10 @@ If this library helped you out feel free to donate. Other Exchanges --------------- -If you use `Quoinex `_ -or `Qryptos `_ check out my `python-quoine `_ library. +If you use `Binance Chain `_ check out my `python-binance-chain `_ library. -If you use `Kucoin `_ check out my `python-kucoin `_ library. +If you use `Kucoin `_ check out my `python-kucoin `_ library. If you use `IDEX `_ check out my `python-idex `_ library. -.. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance?pixel&useReferer +.. image:: https://ga-beacon.appspot.com/UA-111417213-1/github/python-binance?pixel&useReferer diff --git a/README.rst b/README.rst old mode 100644 new mode 100755 index bd27ca8aa..7ba790bb9 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -================================ -Welcome to python-binance v0.7.1 -================================ +================================= +Welcome to python-binance v1.0.21 +================================= .. image:: https://img.shields.io/pypi/v/python-binance.svg :target: https://pypi.python.org/pypi/python-binance @@ -8,9 +8,6 @@ Welcome to python-binance v0.7.1 .. image:: https://img.shields.io/pypi/l/python-binance.svg :target: https://pypi.python.org/pypi/python-binance -.. image:: https://img.shields.io/travis/sammchardy/python-binance.svg - :target: https://travis-ci.org/sammchardy/python-binance - .. image:: https://img.shields.io/coveralls/sammchardy/python-binance.svg :target: https://coveralls.io/github/sammchardy/python-binance @@ -20,9 +17,21 @@ Welcome to python-binance v0.7.1 .. image:: https://img.shields.io/pypi/pyversions/python-binance.svg :target: https://pypi.python.org/pypi/python-binance -This is an unofficial Python wrapper for the `Binance exchange REST API v1/3 `_. I am in no way affiliated with Binance, use at your own risk. +.. image:: https://img.shields.io/badge/Telegram-Join%20Us-blue?logo=Telegram + :target: https://t.me/python_binance + + +This is an unofficial Python wrapper for the `Binance exchange REST API v3 `_. + +If you came here looking for the `Binance exchange `_ to purchase cryptocurrencies, then `go here `_. +If you want to automate interactions with Binance stick around. -If you came here looking for the `Binance exchange `_ to purchase cryptocurrencies, then `go here `_. If you want to automate interactions with Binance stick around. +.. |ico1| image:: https://avatars.githubusercontent.com/u/31901609?s=48&v=4 + :target: https://github.com/ccxt/ccxt + :height: 3ex + :align: middle + +**This project is powered by** |ico1| Source code https://github.com/sammchardy/python-binance @@ -30,19 +39,29 @@ Source code Documentation https://python-binance.readthedocs.io/en/latest/ -Binance API Telegram - https://t.me/binance_api_english +Community Telegram Chat + https://t.me/python_binance + +Announcements Channel + https://t.me/python_binance_announcements + +Examples including async + https://github.com/sammchardy/python-binance/tree/master/examples -Blog with examples - https://sammchardy.github.io +- `Async basics for Binance `_ +- `Understanding Binance Order Filters `_ Make sure you update often and check the `Changelog `_ for new features and bug fixes. +Your contributions, suggestions, and fixes are always welcome! Don't hesitate to open a GitHub issue or reach out to us on our Telegram chat + Features -------- - Implementation of all General, Market Data and Account endpoints. -- Simple handling of authentication +- Asyncio implementation +- Testnet support for Spot, Futures and Vanilla Options +- Simple handling of authentication include RSA and EDDSA keys - No need to generate timestamps yourself, the wrapper does it for you - Response exception handling - Websocket handling with reconnection and multiplexed connections @@ -50,13 +69,37 @@ Features - Historical Kline/Candle fetching function - Withdraw functionality - Deposit addresses +- Margin Trading +- Futures Trading +- Porfolio Margin Trading +- Vanilla Options +- Proxy support +- Support other domains (.us, .jp, etc) + +Upgrading to v1.0.0+ +-------------------- + +The breaking changes include the migration from wapi to sapi endpoints which related to the +wallet endpoints detailed in the `Binance Docs `_ + +The other breaking change is for websocket streams and the Depth Cache Manager which have been +converted to use Asynchronous Context Managers. See examples in the Async section below or view the +`websockets `_ and +`depth cache `_ docs. Quick Start ----------- -`Register an account with Binance `_. +`Register an account with Binance `_. + +`Generate an API Key `_ and assign relevant permissions. + +If you are using an exchange from the US, Japan or other TLD then make sure pass `tld='us'` when creating the +client. + +To use the `Spot `_, `Vanilla Options `_ , or `Futures `_ Testnet +pass `testnet=True` when creating the client. -`Generate an API Key `_ and assign relevant permissions. .. code:: bash @@ -65,7 +108,7 @@ Quick Start .. code:: python - from binance.client import Client + from binance import Client, ThreadedWebsocketManager, ThreadedDepthCacheManager client = Client(api_key, api_secret) # get market depth @@ -83,7 +126,7 @@ Quick Start # withdraw 100 ETH # check docs for assumptions around withdrawals - from binance.exceptions import BinanceAPIException, BinanceWithdrawException + from binance.exceptions import BinanceAPIException try: result = client.withdraw( asset='ETH', @@ -91,8 +134,6 @@ Quick Start amount=100) except BinanceAPIException as e: print(e) - except BinanceWithdrawException as e: - print(e) else: print("Success") @@ -100,21 +141,10 @@ Quick Start withdraws = client.get_withdraw_history() # fetch list of ETH withdrawals - eth_withdraws = client.get_withdraw_history(asset='ETH') + eth_withdraws = client.get_withdraw_history(coin='ETH') # get a deposit address for BTC - address = client.get_deposit_address(asset='BTC') - - # start aggregated trade websocket for BNBBTC - def process_message(msg): - print("message type: {}".format(msg['e'])) - print(msg) - # do something - - from binance.websockets import BinanceSocketManager - bm = BinanceSocketManager(client) - bm.start_aggtrade_socket('BNBBTC', process_message) - bm.start() + address = client.get_deposit_address(coin='BTC') # get historical kline data from any date range @@ -127,30 +157,133 @@ Quick Start # fetch weekly klines since it listed klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + # socket manager using threads + twm = ThreadedWebsocketManager() + twm.start() + + # depth cache manager using threads + dcm = ThreadedDepthCacheManager() + dcm.start() + + def handle_socket_message(msg): + print(f"message type: {msg['e']}") + print(msg) + + def handle_dcm_message(depth_cache): + print(f"symbol {depth_cache.symbol}") + print("top 5 bids") + print(depth_cache.get_bids()[:5]) + print("top 5 asks") + print(depth_cache.get_asks()[:5]) + print("last update time {}".format(depth_cache.update_time)) + + twm.start_kline_socket(callback=handle_socket_message, symbol='BNBBTC') + + dcm.start_depth_cache(callback=handle_dcm_message, symbol='ETHBTC') + + # replace with a current options symbol + options_symbol = 'BTC-241227-41000-C' + dcm.start_options_depth_cache(callback=handle_dcm_message, symbol=options_symbol) + + # join the threaded managers to the main thread + twm.join() + dcm.join() + For more `check out the documentation `_. -Donate ------- +Async Example +------------- -If this library helped you out feel free to donate. +Read `Async basics for Binance `_ +for more information. -- ETH: 0xD7a7fDdCfA687073d7cC93E9E51829a727f9fE70 -- LTC: LPC5vw9ajR1YndE1hYVeo3kJ9LdHjcRCUZ -- NEO: AVJB4ZgN7VgSUtArCt94y7ZYT6d5NDfpBo -- BTC: 1Dknp6L6oRZrHDECRedihPzx2sSfmvEBys +.. code:: python -Other Exchanges ---------------- + import asyncio + import json + + from binance import AsyncClient, DepthCacheManager, BinanceSocketManager + + async def main(): + + # initialise the client + client = await AsyncClient.create() + + # run some simple requests + print(json.dumps(await client.get_exchange_info(), indent=2)) + + print(json.dumps(await client.get_symbol_ticker(symbol="BTCUSDT"), indent=2)) + + # initialise websocket factory manager + bsm = BinanceSocketManager(client) + + # create listener using async with + # this will exit and close the connection after 5 messages + async with bsm.trade_socket('ETHBTC') as ts: + for _ in range(5): + res = await ts.recv() + print(f'recv {res}') + + # get historical kline data from any date range + + # fetch 1 minute klines for the last day up until now + klines = client.get_historical_klines("BNBBTC", AsyncClient.KLINE_INTERVAL_1MINUTE, "1 day ago UTC") -If you use `Quoinex `_ -or `Qryptos `_ check out my `python-quoine `_ library. + # use generator to fetch 1 minute klines for the last day up until now + async for kline in await client.get_historical_klines_generator("BNBBTC", AsyncClient.KLINE_INTERVAL_1MINUTE, "1 day ago UTC"): + print(kline) -If you use `Kucoin `_ check out my `python-kucoin `_ library. + # fetch 30 minute klines for the last month of 2017 + klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") -If you use `Allcoin `_ check out my `python-allucoin `_ library. + # fetch weekly klines since it listed + klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") -If you use `IDEX `_ check out my `python-idex `_ library. + # setup an async context the Depth Cache and exit after 5 messages + async with DepthCacheManager(client, symbol='ETHBTC') as dcm_socket: + for _ in range(5): + depth_cache = await dcm_socket.recv() + print(f"symbol {depth_cache.symbol} updated:{depth_cache.update_time}") + print("Top 5 asks:") + print(depth_cache.get_asks()[:5]) + print("Top 5 bids:") + print(depth_cache.get_bids()[:5]) -If you use `BigONE `_ check out my `python-bigone `_ library. + # Vanilla options Depth Cache works the same, update the symbol to a current one + options_symbol = 'BTC-241227-41000-C' + async with OptionsDepthCacheManager(client, symbol=options_symbol) as dcm_socket: + for _ in range(5): + depth_cache = await dcm_socket.recv() + count += 1 + print(f"symbol {depth_cache.symbol} updated:{depth_cache.update_time}") + print("Top 5 asks:") + print(depth_cache.get_asks()[:5]) + print("Top 5 bids:") + print(depth_cache.get_bids()[:5]) -.. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance?pixel&useReferer + await client.close_connection() + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + + +The library is under `MIT license`, that means it's absolutely free for any developer to build commercial and opensource software on top of it, but use it at your own risk with no warranties, as is. + + +Star history +------------ + +.. image:: https://api.star-history.com/svg?repos=sammchardy/python-binance&type=Date + :target: https://api.star-history.com/svg?repos=sammchardy/python-binance&type=Date + +Contact Us +---------- + +For business inquiries: `info@ccxt.trade` + +Other Exchanges +--------------- +- Check out `CCXT `_ for more than 100 crypto exchanges with a unified trading API. +- If you use `Kucoin `_ check out my `python-kucoin `_ library. diff --git a/binance/__init__.py b/binance/__init__.py old mode 100644 new mode 100755 index fa285a810..f60cb39d2 --- a/binance/__init__.py +++ b/binance/__init__.py @@ -3,3 +3,9 @@ .. moduleauthor:: Sam McHardy """ + +__version__ = "1.0.21" + +from binance.client import Client, AsyncClient # noqa +from binance.depthcache import DepthCacheManager, OptionsDepthCacheManager, ThreadedDepthCacheManager # noqa +from binance.streams import BinanceSocketManager, ThreadedWebsocketManager # noqa diff --git a/binance/client.py b/binance/client.py old mode 100644 new mode 100755 index 35984c8d7..bdabef336 --- a/binance/client.py +++ b/binance/client.py @@ -1,22 +1,63 @@ -# coding=utf-8 +from base64 import b64encode +from pathlib import Path +import random +from typing import Dict, Optional, List, Tuple, Union, Any +import aiohttp +import asyncio import hashlib import hmac import requests import time +from Crypto.PublicKey import RSA, ECC +from Crypto.Hash import SHA256 +from Crypto.Signature import pkcs1_15, eddsa from operator import itemgetter -from .helpers import date_to_milliseconds, interval_to_milliseconds -from .exceptions import BinanceAPIException, BinanceRequestException, BinanceWithdrawException - - -class Client(object): - - API_URL = 'https://api.binance.com/api' - WITHDRAW_API_URL = 'https://api.binance.com/wapi' - WEBSITE_URL = 'https://www.binance.com' +from urllib.parse import urlencode + +from .helpers import interval_to_milliseconds, convert_ts_str, get_loop +from .exceptions import BinanceAPIException, BinanceRequestException, NotImplementedException +from .enums import HistoricalKlinesType + + +class BaseClient: + + API_URL = 'https://api{}.binance.{}/api' + API_TESTNET_URL = 'https://testnet.binance.vision/api' + MARGIN_API_URL = 'https://api{}.binance.{}/sapi' + WEBSITE_URL = 'https://www.binance.{}' + FUTURES_URL = 'https://fapi.binance.{}/fapi' + FUTURES_TESTNET_URL = 'https://testnet.binancefuture.com/fapi' + FUTURES_DATA_URL = 'https://fapi.binance.{}/futures/data' + FUTURES_DATA_TESTNET_URL = 'https://testnet.binancefuture.com/futures/data' + FUTURES_COIN_URL = "https://dapi.binance.{}/dapi" + FUTURES_COIN_TESTNET_URL = 'https://testnet.binancefuture.com/dapi' + FUTURES_COIN_DATA_URL = "https://dapi.binance.{}/futures/data" + FUTURES_COIN_DATA_TESTNET_URL = 'https://testnet.binancefuture.com/futures/data' + OPTIONS_URL = 'https://eapi.binance.{}/eapi' + OPTIONS_TESTNET_URL = 'https://testnet.binanceops.{}/eapi' + PAPI_URL = 'https://papi.binance.{}/papi' PUBLIC_API_VERSION = 'v1' PRIVATE_API_VERSION = 'v3' - WITHDRAW_API_VERSION = 'v3' + MARGIN_API_VERSION = 'v1' + MARGIN_API_VERSION2 = 'v2' + MARGIN_API_VERSION3 = 'v3' + MARGIN_API_VERSION4 = 'v4' + FUTURES_API_VERSION = 'v1' + FUTURES_API_VERSION2 = 'v2' + FUTURES_API_VERSION3 = 'v3' + OPTIONS_API_VERSION = 'v1' + PORTFOLIO_API_VERSION = 'v1' + PORTFOLIO_API_VERSION2 = 'v2' + + + BASE_ENDPOINT_DEFAULT = '' + BASE_ENDPOINT_1 = '1' + BASE_ENDPOINT_2 = '2' + BASE_ENDPOINT_3 = '3' + BASE_ENDPOINT_4 = '4' + + REQUEST_TIMEOUT: float = 10 SYMBOL_TYPE_SPOT = 'SPOT' @@ -28,6 +69,7 @@ class Client(object): ORDER_STATUS_REJECTED = 'REJECTED' ORDER_STATUS_EXPIRED = 'EXPIRED' + KLINE_INTERVAL_1SECOND = '1s' KLINE_INTERVAL_1MINUTE = '1m' KLINE_INTERVAL_3MINUTE = '3m' KLINE_INTERVAL_5MINUTE = '5m' @@ -55,6 +97,14 @@ class Client(object): ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' + FUTURE_ORDER_TYPE_LIMIT = 'LIMIT' + FUTURE_ORDER_TYPE_MARKET = 'MARKET' + FUTURE_ORDER_TYPE_STOP = 'STOP' + FUTURE_ORDER_TYPE_STOP_MARKET = 'STOP_MARKET' + FUTURE_ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' + FUTURE_ORDER_TYPE_TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET' + FUTURE_ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' + TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel TIME_IN_FORCE_FOK = 'FOK' # Fill or kill @@ -73,7 +123,34 @@ class Client(object): AGG_BUYER_MAKES = 'm' AGG_BEST_MATCH = 'M' - def __init__(self, api_key, api_secret, requests_params=None): + # new asset transfer api enum + SPOT_TO_FIAT = "MAIN_C2C" + SPOT_TO_USDT_FUTURE = "MAIN_UMFUTURE" + SPOT_TO_COIN_FUTURE = "MAIN_CMFUTURE" + SPOT_TO_MARGIN_CROSS = "MAIN_MARGIN" + SPOT_TO_MINING = "MAIN_MINING" + FIAT_TO_SPOT = "C2C_MAIN" + FIAT_TO_USDT_FUTURE = "C2C_UMFUTURE" + FIAT_TO_MINING = "C2C_MINING" + USDT_FUTURE_TO_SPOT = "UMFUTURE_MAIN" + USDT_FUTURE_TO_FIAT = "UMFUTURE_C2C" + USDT_FUTURE_TO_MARGIN_CROSS = "UMFUTURE_MARGIN" + COIN_FUTURE_TO_SPOT = "CMFUTURE_MAIN" + MARGIN_CROSS_TO_SPOT = "MARGIN_MAIN" + MARGIN_CROSS_TO_USDT_FUTURE = "MARGIN_UMFUTURE" + MINING_TO_SPOT = "MINING_MAIN" + MINING_TO_USDT_FUTURE = "MINING_UMFUTURE" + MINING_TO_FIAT = "MINING_C2C" + + ## order ids + SPOT_ORDER_PREFIX = "x-HNA2TXFJ" + CONTRACT_ORDER_PREFIX = "x-Cb7ytekJ" + + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', base_endpoint: str = BASE_ENDPOINT_DEFAULT, + testnet: bool = False, private_key: Optional[Union[str, Path]] = None, private_key_pass: Optional[str] = None + ): """Binance API Client constructor :param api_key: Api Key @@ -82,66 +159,181 @@ def __init__(self, api_key, api_secret, requests_params=None): :type api_secret: str. :param requests_params: optional - Dictionary of requests params to use for all calls :type requests_params: dict. + :param testnet: Use testnet environment - only available for vanilla options at the moment + :type testnet: bool + :param private_key: Path to private key, or string of file contents + :type private_key: optional - str or Path + :param private_key_pass: Password of private key + :type private_key_pass: optional - str """ + self.tld = tld + self.API_URL = self.API_URL.format(base_endpoint, tld) + self.MARGIN_API_URL = self.MARGIN_API_URL.format(base_endpoint, tld) + self.WEBSITE_URL = self.WEBSITE_URL.format(tld) + self.FUTURES_URL = self.FUTURES_URL.format(tld) + self.FUTURES_DATA_URL = self.FUTURES_DATA_URL.format(tld) + self.FUTURES_COIN_URL = self.FUTURES_COIN_URL.format(tld) + self.FUTURES_COIN_DATA_URL = self.FUTURES_COIN_DATA_URL.format(tld) + self.OPTIONS_URL = self.OPTIONS_URL.format(tld) + self.OPTIONS_TESTNET_URL = self.OPTIONS_TESTNET_URL.format(tld) + self.API_KEY = api_key self.API_SECRET = api_secret + self._is_rsa = False + self.PRIVATE_KEY = self._init_private_key(private_key, private_key_pass) self.session = self._init_session() self._requests_params = requests_params - - # init DNS and SSL cert - self.ping() + self.response = None + self.testnet = testnet + self.timestamp_offset = 0 + + def _get_headers(self) -> Dict: + headers = { + 'Accept': 'application/json', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36', # noqa + } + if self.API_KEY: + assert self.API_KEY + headers['X-MBX-APIKEY'] = self.API_KEY + return headers def _init_session(self): - - session = requests.session() - session.headers.update({'Accept': 'application/json', - 'User-Agent': 'binance/python', - 'X-MBX-APIKEY': self.API_KEY}) - return session - - def _create_api_uri(self, path, signed=True, version=PUBLIC_API_VERSION): + raise NotImplementedError + + def _init_private_key(self, private_key: Optional[Union[str, Path]], private_key_pass: Optional[str] = None): + if not private_key: + return + if isinstance(private_key, Path): + with open(private_key, "r") as f: + private_key = f.read() + if len(private_key) > 120: + self._is_rsa = True + return RSA.import_key(private_key, passphrase=private_key_pass) + return ECC.import_key(private_key) + + def _create_api_uri(self, path: str, signed: bool = True, version: str = PUBLIC_API_VERSION) -> str: + url = self.API_URL + if self.testnet: + url = self.API_TESTNET_URL v = self.PRIVATE_API_VERSION if signed else version - return self.API_URL + '/' + v + '/' + path + return url + '/' + v + '/' + path + + def _create_margin_api_uri(self, path: str, version: int = 1) -> str: + options = { + 1: self.MARGIN_API_VERSION, + 2: self.MARGIN_API_VERSION2, + 3: self.MARGIN_API_VERSION3, + 4: self.MARGIN_API_VERSION4, + } + return self.MARGIN_API_URL + '/' + options[version] + '/' + path - def _create_withdraw_api_uri(self, path): - return self.WITHDRAW_API_URL + '/' + self.WITHDRAW_API_VERSION + '/' + path + def _create_papi_api_uri(self, path: str, version: int = 1) -> str: + options = { + 1: self.PORTFOLIO_API_VERSION, + 2: self.PORTFOLIO_API_VERSION2 + } + return self.PAPI_URL.format(self.tld) + '/' + options[version] + '/' + path - def _create_website_uri(self, path): + def _create_website_uri(self, path: str) -> str: return self.WEBSITE_URL + '/' + path - def _generate_signature(self, data): - - ordered_data = self._order_params(data) - query_string = '&'.join(["{}={}".format(d[0], d[1]) for d in ordered_data]) + def _create_futures_api_uri(self, path: str, version: int = 1) -> str: + url = self.FUTURES_URL + if self.testnet: + url = self.FUTURES_TESTNET_URL + options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2, 3: self.FUTURES_API_VERSION3} + return url + '/' + options[version] + '/' + path + + def _create_futures_data_api_uri(self, path: str) -> str: + url = self.FUTURES_DATA_URL + if self.testnet: + url = self.FUTURES_DATA_TESTNET_URL + return url + '/' + path + + def _create_futures_coin_api_url(self, path: str, version: int = 1) -> str: + url = self.FUTURES_COIN_URL + if self.testnet: + url = self.FUTURES_COIN_TESTNET_URL + options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2, 3: self.FUTURES_API_VERSION3} + return url + "/" + options[version] + "/" + path + + def _create_futures_coin_data_api_url(self, path: str, version: int = 1) -> str: + url = self.FUTURES_COIN_DATA_URL + if self.testnet: + url = self.FUTURES_COIN_DATA_TESTNET_URL + return url + "/" + path + + def _create_options_api_uri(self, path: str) -> str: + url = self.OPTIONS_URL + if self.testnet: + url = self.OPTIONS_TESTNET_URL + return url + '/' + self.OPTIONS_API_VERSION + '/' + path + + def _rsa_signature(self, query_string: str): + assert self.PRIVATE_KEY + h = SHA256.new(query_string.encode("utf-8")) + signature = pkcs1_15.new(self.PRIVATE_KEY).sign(h) # type: ignore + return b64encode(signature).decode() + + def _ed25519_signature(self, query_string: str): + assert self.PRIVATE_KEY + return b64encode(eddsa.new(self.PRIVATE_KEY, "rfc8032").sign(query_string.encode())).decode() # type: ignore + + def _hmac_signature(self, query_string: str) -> str: + assert self.API_SECRET, "API Secret required for private endpoints" m = hmac.new(self.API_SECRET.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256) return m.hexdigest() - def _order_params(self, data): + def _generate_signature(self, data: Dict) -> str: + sig_func = self._hmac_signature + if self.PRIVATE_KEY: + if self._is_rsa: + sig_func = self._rsa_signature + else: + sig_func = self._ed25519_signature + query_string = '&'.join([f"{d[0]}={d[1]}" for d in self._order_params(data)]) + return sig_func(query_string) + + @staticmethod + def _get_version(version: int, **kwargs) -> int: + if 'data' in kwargs and 'version' in kwargs['data']: + version_override = kwargs['data'].get('version') + del kwargs['data']['version'] + return version_override + return version + + @staticmethod + def uuid22(length=22): + return format(random.getrandbits(length * 4), 'x') + + @staticmethod + def _order_params(data: Dict) -> List[Tuple[str, str]]: """Convert params to list with signature as last element :param data: :return: """ + data = dict(filter(lambda el: el[1] is not None, data.items())) has_signature = False params = [] for key, value in data.items(): if key == 'signature': has_signature = True else: - params.append((key, value)) + params.append((key, str(value))) # sort parameters by key params.sort(key=itemgetter(0)) if has_signature: params.append(('signature', data['signature'])) return params - def _request(self, method, uri, signed, force_params=False, **kwargs): + def _get_request_kwargs(self, method, signed: bool, force_params: bool = False, **kwargs) -> Dict: # set default requests timeout - kwargs['timeout'] = 10 + kwargs['timeout'] = self.REQUEST_TIMEOUT # add our global requests params if self._requests_params: @@ -150,74 +342,147 @@ def _request(self, method, uri, signed, force_params=False, **kwargs): data = kwargs.get('data', None) if data and isinstance(data, dict): kwargs['data'] = data - # find any requests params passed and apply them if 'requests_params' in kwargs['data']: # merge requests params into kwargs kwargs.update(kwargs['data']['requests_params']) - del(kwargs['data']['requests_params']) + del kwargs['data']['requests_params'] if signed: # generate signature - kwargs['data']['timestamp'] = int(time.time() * 1000) + kwargs['data']['timestamp'] = int(time.time() * 1000 + self.timestamp_offset) kwargs['data']['signature'] = self._generate_signature(kwargs['data']) # sort get and post params to match signature order if data: - # sort post params + # sort post params and remove any arguments with values of None kwargs['data'] = self._order_params(kwargs['data']) + # Remove any arguments with values of None. + null_args = [i for i, (key, value) in enumerate(kwargs['data']) if value is None] + for i in reversed(null_args): + del kwargs['data'][i] # if get request assign data array to params value for requests lib if data and (method == 'get' or force_params): - kwargs['params'] = kwargs['data'] - del(kwargs['data']) + kwargs['params'] = '&'.join('%s=%s' % (data[0], data[1]) for data in kwargs['data']) + del kwargs['data'] - response = getattr(self.session, method)(uri, **kwargs) - return self._handle_response(response) + #Temporary fix for Signature issue while using batchOrders in AsyncClient + if 'params' in kwargs.keys() and 'batchOrders' in kwargs['params']: + kwargs['data'] = kwargs['params'] + del kwargs['params'] - def _request_api(self, method, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): - uri = self._create_api_uri(path, signed, version) + return kwargs - return self._request(method, uri, signed, **kwargs) - def _request_withdraw_api(self, method, path, signed=False, **kwargs): - uri = self._create_withdraw_api_uri(path) +class Client(BaseClient): - return self._request(method, uri, signed, True, **kwargs) + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', + base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, testnet: bool = False, + private_key: Optional[Union[str, Path]] = None, private_key_pass: Optional[str] = None, + ping: Optional[bool] = True + ): - def _request_website(self, method, path, signed=False, **kwargs): + super().__init__(api_key, api_secret, requests_params, tld, base_endpoint, testnet, private_key, private_key_pass) - uri = self._create_website_uri(path) + # init DNS and SSL cert + if ping: + self.ping() - return self._request(method, uri, signed, **kwargs) + def _init_session(self) -> requests.Session: + + headers = self._get_headers() - def _handle_response(self, response): + session = requests.session() + session.headers.update(headers) + return session + + def _request(self, method, uri: str, signed: bool, force_params: bool = False, **kwargs): + + kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) + + self.response = getattr(self.session, method)(uri, **kwargs) + return self._handle_response(self.response) + + @staticmethod + def _handle_response(response: requests.Response): """Internal helper for handling API responses from the Binance server. Raises the appropriate exceptions when necessary; otherwise, returns the response. """ - if not str(response.status_code).startswith('2'): - raise BinanceAPIException(response) + if not (200 <= response.status_code < 300): + raise BinanceAPIException(response, response.status_code, response.text) try: return response.json() except ValueError: raise BinanceRequestException('Invalid Response: %s' % response.text) - def _get(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): + def _request_api( + self, method, path: str, signed: bool = False, version=BaseClient.PUBLIC_API_VERSION, **kwargs + ): + uri = self._create_api_uri(path, signed, version) + return self._request(method, uri, signed, **kwargs) + + def _request_futures_api(self, method, path, signed=False, version: int = 1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_api_uri(path, version) + + return self._request(method, uri, signed, True, **kwargs) + + def _request_futures_data_api(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_futures_data_api_uri(path) + + return self._request(method, uri, signed, True, **kwargs) + + def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_api_url(path, version=version) + + return self._request(method, uri, signed, False, **kwargs) + + def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_data_api_url(path, version=version) + + return self._request(method, uri, signed, True, **kwargs) + + def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_options_api_uri(path) + + return self._request(method, uri, signed, True, **kwargs) + + def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_margin_api_uri(path, version) + + return self._request(method, uri, signed, **kwargs) + + def _request_papi_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_papi_api_uri(path, version) + return self._request(method, uri, signed, **kwargs) + + def _request_website(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_website_uri(path) + return self._request(method, uri, signed, **kwargs) + + def _get(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs): return self._request_api('get', path, signed, version, **kwargs) - def _post(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): + def _post(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: return self._request_api('post', path, signed, version, **kwargs) - def _put(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): + def _put(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: return self._request_api('put', path, signed, version, **kwargs) - def _delete(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): + def _delete(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: return self._request_api('delete', path, signed, version, **kwargs) # Exchange Endpoints - def get_products(self): + def get_products(self) -> Dict: """Return list of products currently listed on Binance Use get_exchange_info() call instead @@ -227,11 +492,10 @@ def get_products(self): :raises: BinanceRequestException, BinanceAPIException """ - - products = self._request_website('get', 'exchange/public/product') + products = self._request_website('get', 'bapi/asset/v2/public/asset-service/product/get-products?includeEtf=true') return products - def get_exchange_info(self): + def get_exchange_info(self) -> Dict: """Return rate limits and list of symbols :returns: list - List of product dictionaries @@ -293,12 +557,12 @@ def get_exchange_info(self): """ - return self._get('exchangeInfo') + return self._get('exchangeInfo', version=self.PRIVATE_API_VERSION) - def get_symbol_info(self, symbol): + def get_symbol_info(self, symbol) -> Optional[Dict]: """Return information about a symbol - :param symbol: required e.g BNBBTC + :param symbol: required e.g. BNBBTC :type symbol: str :returns: Dict if found, None if not @@ -336,7 +600,7 @@ def get_symbol_info(self, symbol): """ - res = self._get('exchangeInfo') + res = self.get_exchange_info() for item in res['symbols']: if item['symbol'] == symbol.upper(): @@ -346,10 +610,10 @@ def get_symbol_info(self, symbol): # General Endpoints - def ping(self): + def ping(self) -> Dict: """Test connectivity to the Rest API. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#test-connectivity + https://binance-docs.github.io/apidocs/spot/en/#test-connectivity :returns: Empty array @@ -360,12 +624,12 @@ def ping(self): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('ping') + return self._get('ping', version=self.PRIVATE_API_VERSION) - def get_server_time(self): + def get_server_time(self) -> Dict: """Test connectivity to the Rest API and get the current server time. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#check-server-time + https://binance-docs.github.io/apidocs/spot/en/#check-server-time :returns: Current server time @@ -378,14 +642,14 @@ def get_server_time(self): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('time') + return self._get('time', version=self.PRIVATE_API_VERSION) # Market Data Endpoints - def get_all_tickers(self): + def get_all_tickers(self) -> List[Dict[str, str]]: """Latest price for all symbols. - https://www.binance.com/restapipub.html#symbols-price-ticker + https://binance-docs.github.io/apidocs/spot/en/#symbol-price-ticker :returns: List of market tickers @@ -405,12 +669,17 @@ def get_all_tickers(self): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('ticker/allPrices') + return self._get('ticker/price', version=self.PRIVATE_API_VERSION) - def get_orderbook_tickers(self): + def get_orderbook_tickers(self, **params) -> Dict: """Best price/qty on the order book for all symbols. - https://www.binance.com/restapipub.html#symbols-order-book-ticker + https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker + + :param symbol: optional + :type symbol: str + :param symbols: optional accepted format ["BTCUSDT","BNBUSDT"] or %5B%22BTCUSDT%22,%22BNBUSDT%22%5D + :type symbols: str :returns: List of order book market entries @@ -436,12 +705,17 @@ def get_orderbook_tickers(self): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('ticker/allBookTickers') - - def get_order_book(self, **params): + data = {} + if "symbol" in params: + data["symbol"] = params["symbol"] + elif "symbols" in params: + data["symbols"] = params["symbols"] + return self._get('ticker/bookTicker', data=data, version=self.PRIVATE_API_VERSION) + + def get_order_book(self, **params) -> Dict: """Get the Order Book for the market - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book + https://binance-docs.github.io/apidocs/spot/en/#order-book :param symbol: required :type symbol: str @@ -473,16 +747,16 @@ def get_order_book(self, **params): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('depth', data=params) + return self._get('depth', data=params, version=self.PRIVATE_API_VERSION) - def get_recent_trades(self, **params): + def get_recent_trades(self, **params) -> Dict: """Get recent trades (up to last 500). - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list + https://binance-docs.github.io/apidocs/spot/en/#recent-trades-list :param symbol: required :type symbol: str - :param limit: Default 500; max 500. + :param limit: Default 500; max 1000. :type limit: int :returns: API response @@ -505,14 +779,14 @@ def get_recent_trades(self, **params): """ return self._get('trades', data=params) - def get_historical_trades(self, **params): + def get_historical_trades(self, **params) -> Dict: """Get older trades. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list + https://binance-docs.github.io/apidocs/spot/en/#old-trade-lookup :param symbol: required :type symbol: str - :param limit: Default 500; max 500. + :param limit: Default 500; max 1000. :type limit: int :param fromId: TradeId to fetch from. Default gets most recent trades. :type fromId: str @@ -535,13 +809,13 @@ def get_historical_trades(self, **params): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('historicalTrades', data=params) + return self._get('historicalTrades', data=params, version=self.PRIVATE_API_VERSION) - def get_aggregate_trades(self, **params): + def get_aggregate_trades(self, **params) -> Dict: """Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same price will have the quantity aggregated. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list + https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list :param symbol: required :type symbol: str @@ -551,7 +825,7 @@ def get_aggregate_trades(self, **params): :type startTime: int :param endTime: Timestamp in ms to get aggregate trades until INCLUSIVE. :type endTime: int - :param limit: Default 500; max 500. + :param limit: Default 500; max 1000. :type limit: int :returns: API response @@ -574,9 +848,9 @@ def get_aggregate_trades(self, **params): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('aggTrades', data=params) + return self._get('aggTrades', data=params, version=self.PRIVATE_API_VERSION) - def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): + def aggregate_trade_iter(self, symbol: str, start_str=None, last_id=None): """Iterate over aggregate trade data from (start_time or last_id) to the end of the history so far. @@ -600,7 +874,7 @@ def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): return the first trade occurring later than this time. :type start_str: str|int :param last_id: aggregate trade ID of the last known aggregate trade. - Not a regular trade ID. See https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list. + Not a regular trade ID. See https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list :returns: an iterator of JSON objects, one per trade. The format of each object is identical to Client.aggregate_trades(). @@ -621,10 +895,7 @@ def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): # The difference between startTime and endTime should be less # or equal than an hour and the result set should contain at # least one trade. - if type(start_str) == int: - start_ts = start_str - else: - start_ts = date_to_milliseconds(start_str) + start_ts = convert_ts_str(start_str) # If the resulting set is empty (i.e. no trades in that interval) # then we just move forward hour by hour until we find at least one # trade or reach present moment @@ -662,16 +933,16 @@ def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): yield t last_id = trades[-1][self.AGG_ID] - def get_klines(self, **params): + def get_klines(self, **params) -> Dict: """Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data + https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data :param symbol: required :type symbol: str :param interval: - :type interval: str - :param limit: - Default 500; max 500. + :param limit: - Default 500; max 1000. :type limit: int :param startTime: :type startTime: int @@ -702,81 +973,130 @@ def get_klines(self, **params): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('klines', data=params) + return self._get('klines', data=params, version=self.PRIVATE_API_VERSION) + + def _klines(self, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, **params) -> Dict: + """Get klines of spot (get_klines) or futures (futures_klines) endpoints. + + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType + + :return: klines, see get_klines + + """ + if 'endTime' in params and not params['endTime']: + del params['endTime'] + + if HistoricalKlinesType.SPOT == klines_type: + return self.get_klines(**params) + elif HistoricalKlinesType.FUTURES == klines_type: + return self.futures_klines(**params) + elif HistoricalKlinesType.FUTURES_COIN == klines_type: + return self.futures_coin_klines(**params) + else: + raise NotImplementedException(klines_type) - def _get_earliest_valid_timestamp(self, symbol, interval): - """Get earliest valid open timestamp from Binance + def _get_earliest_valid_timestamp(self, symbol, interval, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + """Get the earliest valid open timestamp from Binance - :param symbol: Name of symbol pair e.g BNBBTC + :param symbol: Name of symbol pair e.g. BNBBTC :type symbol: str :param interval: Binance Kline interval :type interval: str + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType :return: first valid timestamp """ - kline = self.get_klines( + kline = self._klines( + klines_type=klines_type, symbol=symbol, interval=interval, limit=1, startTime=0, - endTime=None + endTime=int(time.time() * 1000) ) return kline[0][0] - def get_historical_klines(self, symbol, interval, start_str, end_str=None, - limit=500): + def get_historical_klines(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): """Get Historical Klines from Binance - See dateparser docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/ + :param symbol: Name of symbol pair e.g. BNBBTC + :type symbol: str + :param interval: Binance Kline interval + :type interval: str + :param start_str: optional - start date string in UTC format or timestamp in milliseconds + :type start_str: str|int + :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) + :type end_str: str|int + :param limit: Default 1000; max 1000. + :type limit: int + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType + + :return: list of OHLCV values (Open time, Open, High, Low, Close, Volume, Close time, Quote asset volume, Number of trades, Taker buy base asset volume, Taker buy quote asset volume, Ignore) + + """ + return self._historical_klines( + symbol, interval, start_str=start_str, end_str=end_str, limit=limit, klines_type=klines_type + ) + + def _historical_klines(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + """Get Historical Klines from Binance (spot or futures) + + See dateparser docs for valid start and end string formats https://dateparser.readthedocs.io/en/latest/ If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" - :param symbol: Name of symbol pair e.g BNBBTC + :param symbol: Name of symbol pair e.g. BNBBTC :type symbol: str :param interval: Binance Kline interval :type interval: str - :param start_str: Start date string in UTC format or timestamp in milliseconds + :param start_str: optional - start date string in UTC format or timestamp in milliseconds :type start_str: str|int :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) - :type end_str: str|int - :param limit: Default 500; max 1000. + :type end_str: None|str|int + :param limit: Default 1000; max 1000. :type limit: int + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType - :return: list of OHLCV values + :return: list of OHLCV values (Open time, Open, High, Low, Close, Volume, Close time, Quote asset volume, Number of trades, Taker buy base asset volume, Taker buy quote asset volume, Ignore) """ + + initial_limit_set = True + if limit is None: + limit = 1000 + initial_limit_set = False + # init our list output_data = [] - # setup the max limit - limit = limit - # convert interval to useful value in seconds timeframe = interval_to_milliseconds(interval) - # convert our date strings to milliseconds - if type(start_str) == int: - start_ts = start_str - else: - start_ts = date_to_milliseconds(start_str) + # if a start time was passed convert it + start_ts = convert_ts_str(start_str) # establish first available start timestamp - first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval) - start_ts = max(start_ts, first_valid_ts) + if start_ts is not None: + first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval, klines_type) + start_ts = max(start_ts, first_valid_ts) # if an end time was passed convert it - end_ts = None - if end_str: - if type(end_str) == int: - end_ts = end_str - else: - end_ts = date_to_milliseconds(end_str) + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return output_data idx = 0 while True: # fetch the klines from start_ts up to max 500 entries or the end_ts if set - temp_data = self.get_klines( + temp_data = self._klines( + klines_type=klines_type, symbol=symbol, interval=interval, limit=limit, @@ -784,79 +1104,107 @@ def get_historical_klines(self, symbol, interval, start_str, end_str=None, endTime=end_ts ) - # handle the case where exactly the limit amount of data was returned last loop - if not len(temp_data): - break - # append this loops data to our output data - output_data += temp_data + if temp_data: + output_data += temp_data - # set our start timestamp using the last value in the array - start_ts = temp_data[-1][0] + # check if output_data is greater than limit and truncate if needed and break loop + if initial_limit_set and len(output_data) > limit: + output_data = output_data[:limit] + break - idx += 1 + # handle the case where exactly the limit amount of data was returned last loop # check if we received less than the required limit and exit the loop - if len(temp_data) < limit: + if not len(temp_data) or len(temp_data) < limit: # exit the while loop break # increment next call by our timeframe - start_ts += timeframe + start_ts = temp_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break # sleep after every 3rd call to be kind to the API + idx += 1 if idx % 3 == 0: time.sleep(1) return output_data - def get_historical_klines_generator(self, symbol, interval, start_str, end_str=None): - """Get Historical Klines from Binance + def get_historical_klines_generator(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + """Get Historical Klines generator from Binance - See dateparser docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/ + :param symbol: Name of symbol pair e.g. BNBBTC + :type symbol: str + :param interval: Binance Kline interval + :type interval: str + :param start_str: optional - Start date string in UTC format or timestamp in milliseconds + :type start_str: str|int + :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) + :type end_str: str|int + :param limit: amount of candles to return per request (default 1000) + :type limit: int + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType + + :return: generator of OHLCV values + + """ + + return self._historical_klines_generator(symbol, interval, start_str, end_str, limit, klines_type=klines_type) + + def _historical_klines_generator(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + """Get Historical Klines generator from Binance (spot or futures) + + See dateparser docs for valid start and end string formats https://dateparser.readthedocs.io/en/latest/ If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" - :param symbol: Name of symbol pair e.g BNBBTC + :param symbol: Name of symbol pair e.g. BNBBTC :type symbol: str :param interval: Binance Kline interval :type interval: str - :param start_str: Start date string in UTC format or timestamp in milliseconds + :param start_str: optional - Start date string in UTC format or timestamp in milliseconds :type start_str: str|int :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) :type end_str: str|int + :param klines_type: Historical klines type: SPOT or FUTURES + :type klines_type: HistoricalKlinesType :return: generator of OHLCV values """ - # setup the max limit - limit = 500 + initial_limit_set = True + if limit is None: + limit = 1000 + initial_limit_set = False # convert interval to useful value in seconds timeframe = interval_to_milliseconds(interval) - # convert our date strings to milliseconds - if type(start_str) == int: - start_ts = start_str - else: - start_ts = date_to_milliseconds(start_str) + # if a start time was passed convert it + start_ts = convert_ts_str(start_str) # establish first available start timestamp - first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval) - start_ts = max(start_ts, first_valid_ts) + if start_ts is not None: + first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval, klines_type) + start_ts = max(start_ts, first_valid_ts) # if an end time was passed convert it - end_ts = None - if end_str: - if type(end_str) == int: - end_ts = end_str - else: - end_ts = date_to_milliseconds(end_str) + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return idx = 0 while True: # fetch the klines from start_ts up to max 500 entries or the end_ts if set - output_data = self.get_klines( + output_data = self._klines( + klines_type=klines_type, symbol=symbol, interval=interval, limit=limit, @@ -864,34 +1212,53 @@ def get_historical_klines_generator(self, symbol, interval, start_str, end_str=N endTime=end_ts ) - # handle the case where exactly the limit amount of data was returned last loop - if not len(output_data): - break - # yield data - for o in output_data: - yield o + if output_data: + for o in output_data: + yield o - # set our start timestamp using the last value in the array - start_ts = output_data[-1][0] - - idx += 1 + # handle the case where exactly the limit amount of data was returned last loop # check if we received less than the required limit and exit the loop - if len(output_data) < limit: + if not len(output_data) or len(output_data) < limit: # exit the while loop break + # set our start timestamp using the last value in the array # increment next call by our timeframe - start_ts += timeframe + start_ts = output_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break # sleep after every 3rd call to be kind to the API + idx += 1 if idx % 3 == 0: time.sleep(1) + def get_avg_price(self, **params): + """Current average price for a symbol. + + https://binance-docs.github.io/apidocs/spot/en/#current-average-price + + :param symbol: + :type symbol: str + + :returns: API response + + .. code-block:: python + + { + "mins": 5, + "price": "9.35751834" + } + """ + return self._get('avgPrice', data=params, version=self.PRIVATE_API_VERSION) + def get_ticker(self, **params): """24 hour price change statistics. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#24hr-ticker-price-change-statistics + https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics :param symbol: :type symbol: str @@ -947,12 +1314,12 @@ def get_ticker(self, **params): :raises: BinanceRequestException, BinanceAPIException """ - return self._get('ticker/24hr', data=params) + return self._get('ticker/24hr', data=params, version=self.PRIVATE_API_VERSION) def get_symbol_ticker(self, **params): """Latest price for a symbol or symbols. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#24hr-ticker-price-change-statistics + https://binance-docs.github.io/apidocs/spot/en/#symbol-price-ticker :param symbol: :type symbol: str @@ -986,10 +1353,49 @@ def get_symbol_ticker(self, **params): """ return self._get('ticker/price', data=params, version=self.PRIVATE_API_VERSION) + + def get_symbol_ticker_window(self, **params): + """Latest price for a symbol or symbols. + + https://binance-docs.github.io/apidocs/spot/en/#rolling-window-price-change-statistics + + :param symbol: + :type symbol: str + + :returns: API response + + .. code-block:: python + + { + "symbol": "LTCBTC", + "price": "4.00000200" + } + + OR + + .. code-block:: python + + [ + { + "symbol": "LTCBTC", + "price": "4.00000200" + }, + { + "symbol": "ETHBTC", + "price": "0.07946600" + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._get('ticker', data=params, version=self.PRIVATE_API_VERSION) + + def get_orderbook_ticker(self, **params): """Latest price for a symbol or symbols. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#symbol-order-book-ticker + https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker :param symbol: :type symbol: str @@ -1039,7 +1445,7 @@ def create_order(self, **params): Any order with an icebergQty MUST have timeInForce set to GTC. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#new-order--trade + https://binance-docs.github.io/apidocs/spot/en/#new-order-trade :param symbol: required :type symbol: str @@ -1051,6 +1457,9 @@ def create_order(self, **params): :type timeInForce: str :param quantity: required :type quantity: decimal + :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) + of the quote asset, applicable to MARKET orders + :type quoteOrderQty: decimal :param price: required :type price: str :param newClientOrderId: A unique id for the order. Automatically generated if not sent. @@ -1087,6 +1496,7 @@ def create_order(self, **params): "price": "0.00000000", "origQty": "10.00000000", "executedQty": "10.00000000", + "cummulativeQuoteQty": "10.00000000", "status": "FILLED", "timeInForce": "GTC", "type": "MARKET", @@ -1105,6 +1515,7 @@ def create_order(self, **params): "price": "0.00000000", "origQty": "10.00000000", "executedQty": "10.00000000", + "cummulativeQuoteQty": "10.00000000", "status": "FILLED", "timeInForce": "GTC", "type": "MARKET", @@ -1146,9 +1557,12 @@ def create_order(self, **params): :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() return self._post('order', True, data=params) - def order_limit(self, timeInForce=TIME_IN_FORCE_GTC, **params): + + def order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): """Send in a new limit order Any order with an icebergQty MUST have timeInForce set to GTC. @@ -1185,7 +1599,7 @@ def order_limit(self, timeInForce=TIME_IN_FORCE_GTC, **params): }) return self.create_order(**params) - def order_limit_buy(self, timeInForce=TIME_IN_FORCE_GTC, **params): + def order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): """Send in a new limit buy order Any order with an icebergQty MUST have timeInForce set to GTC. @@ -1221,7 +1635,7 @@ def order_limit_buy(self, timeInForce=TIME_IN_FORCE_GTC, **params): }) return self.order_limit(timeInForce=timeInForce, **params) - def order_limit_sell(self, timeInForce=TIME_IN_FORCE_GTC, **params): + def order_limit_sell(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): """Send in a new limit sell order :param symbol: required @@ -1264,6 +1678,9 @@ def order_market(self, **params): :type side: str :param quantity: required :type quantity: decimal + :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) + of the quote asset + :type quoteOrderQty: decimal :param newClientOrderId: A unique id for the order. Automatically generated if not sent. :type newClientOrderId: str :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. @@ -1290,6 +1707,8 @@ def order_market_buy(self, **params): :type symbol: str :param quantity: required :type quantity: decimal + :param quoteOrderQty: the amount the user wants to spend of the quote asset + :type quoteOrderQty: decimal :param newClientOrderId: A unique id for the order. Automatically generated if not sent. :type newClientOrderId: str :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. @@ -1316,6 +1735,8 @@ def order_market_sell(self, **params): :type symbol: str :param quantity: required :type quantity: decimal + :param quoteOrderQty: the amount the user wants to receive of the quote asset + :type quoteOrderQty: decimal :param newClientOrderId: A unique id for the order. Automatically generated if not sent. :type newClientOrderId: str :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. @@ -1335,111 +1756,261 @@ def order_market_sell(self, **params): }) return self.order_market(**params) - def create_test_order(self, **params): - """Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. + def create_oco_order(self, **params): + """Send in a new OCO order - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#test-new-order-trade + https://binance-docs.github.io/apidocs/spot/en/#new-oco-trade :param symbol: required :type symbol: str + :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. + :type listClientOrderId: str :param side: required :type side: str - :param type: required - :type type: str - :param timeInForce: required if limit order - :type timeInForce: str :param quantity: required :type quantity: decimal + :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. + :type limitClientOrderId: str :param price: required :type price: str - :param newClientOrderId: A unique id for the order. Automatically generated if not sent. - :type newClientOrderId: str - :param icebergQty: Used with iceberg orders - :type icebergQty: decimal + :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. + :type limitIcebergQty: decimal + :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. + :type stopClientOrderId: str + :param stopPrice: required + :type stopPrice: str + :param stopLimitPrice: If provided, stopLimitTimeInForce is required. + :type stopLimitPrice: str + :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. + :type stopIcebergQty: decimal + :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. + :type stopLimitTimeInForce: str :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. :type newOrderRespType: str - :param recvWindow: The number of milliseconds the request is valid for + :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int :returns: API response - .. code-block:: python - - {} - - :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException + Response ACK: + .. code-block:: python - """ - return self._post('order/test', True, data=params) + { + } - def get_order(self, **params): - """Check an order's status. Either orderId or origClientOrderId must be sent. + Response RESULT: - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#query-order-user_data + .. code-block:: python - :param symbol: required - :type symbol: str - :param orderId: The unique order id - :type orderId: int - :param origClientOrderId: optional - :type origClientOrderId: str - :param recvWindow: the number of milliseconds the request is valid for - :type recvWindow: int + { + } - :returns: API response + Response FULL: .. code-block:: python { - "symbol": "LTCBTC", - "orderId": 1, - "clientOrderId": "myOrder1", - "price": "0.1", - "origQty": "1.0", - "executedQty": "0.0", - "status": "NEW", - "timeInForce": "GTC", - "type": "LIMIT", - "side": "BUY", - "stopPrice": "0.0", - "icebergQty": "0.0", - "time": 1499827319559 } - :raises: BinanceRequestException, BinanceAPIException + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException """ - return self._get('order', True, data=params) - - def get_all_orders(self, **params): - """Get all account orders; active, canceled, or filled. + return self._post('order/oco', True, data=params) - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#all-orders-user_data + def order_oco_buy(self, **params): + """Send in a new OCO buy order :param symbol: required :type symbol: str - :param orderId: The unique order id - :type orderId: int - :param limit: Default 500; max 500. - :type limit: int + :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. + :type listClientOrderId: str + :param quantity: required + :type quantity: decimal + :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. + :type limitClientOrderId: str + :param price: required + :type price: str + :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. + :type limitIcebergQty: decimal + :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. + :type stopClientOrderId: str + :param stopPrice: required + :type stopPrice: str + :param stopLimitPrice: If provided, stopLimitTimeInForce is required. + :type stopLimitPrice: str + :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. + :type stopIcebergQty: decimal + :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. + :type stopLimitTimeInForce: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int :returns: API response - .. code-block:: python + See OCO order endpoint for full response options - [ - { - "symbol": "LTCBTC", - "orderId": 1, - "clientOrderId": "myOrder1", - "price": "0.1", - "origQty": "1.0", - "executedQty": "0.0", - "status": "NEW", - "timeInForce": "GTC", + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException + + """ + params.update({ + 'side': self.SIDE_BUY + }) + return self.create_oco_order(**params) + + def order_oco_sell(self, **params): + """Send in a new OCO sell order + + :param symbol: required + :type symbol: str + :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. + :type listClientOrderId: str + :param quantity: required + :type quantity: decimal + :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. + :type limitClientOrderId: str + :param price: required + :type price: str + :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. + :type limitIcebergQty: decimal + :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. + :type stopClientOrderId: str + :param stopPrice: required + :type stopPrice: str + :param stopLimitPrice: If provided, stopLimitTimeInForce is required. + :type stopLimitPrice: str + :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. + :type stopIcebergQty: decimal + :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. + :type stopLimitTimeInForce: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + See OCO order endpoint for full response options + + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException + + """ + params.update({ + 'side': self.SIDE_SELL + }) + return self.create_oco_order(**params) + + def create_test_order(self, **params): + """Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. + + https://binance-docs.github.io/apidocs/spot/en/#test-new-order-trade + + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param type: required + :type type: str + :param timeInForce: required if limit order + :type timeInForce: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: The number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException + + + """ + return self._post('order/test', True, data=params) + + def get_order(self, **params): + """Check an order's status. Either orderId or origClientOrderId must be sent. + + https://binance-docs.github.io/apidocs/spot/en/#query-order-user_data + + :param symbol: required + :type symbol: str + :param orderId: The unique order id + :type orderId: int + :param origClientOrderId: optional + :type origClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "myOrder1", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._get('order', True, data=params) + + def get_all_orders(self, **params): + """Get all account orders; active, canceled, or filled. + + https://binance-docs.github.io/apidocs/spot/en/#all-orders-user_data + + :param symbol: required + :type symbol: str + :param orderId: The unique order id + :type orderId: int + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: Default 500; max 1000. + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "symbol": "LTCBTC", + "orderId": 1, + "clientOrderId": "myOrder1", + "price": "0.1", + "origQty": "1.0", + "executedQty": "0.0", + "status": "NEW", + "timeInForce": "GTC", "type": "LIMIT", "side": "BUY", "stopPrice": "0.0", @@ -1456,7 +2027,7 @@ def get_all_orders(self, **params): def cancel_order(self, **params): """Cancel an active order. Either orderId or origClientOrderId must be sent. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#cancel-order-trade + https://binance-docs.github.io/apidocs/spot/en/#cancel-order-trade :param symbol: required :type symbol: str @@ -1488,7 +2059,7 @@ def cancel_order(self, **params): def get_open_orders(self, **params): """Get all open orders on a symbol. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#current-open-orders-user_data + https://binance-docs.github.io/apidocs/spot/en/#current-open-orders-user_data :param symbol: optional :type symbol: str @@ -1522,11 +2093,45 @@ def get_open_orders(self, **params): """ return self._get('openOrders', True, data=params) + def get_open_oco_orders(self, **params): + """Get all open orders on a symbol. + https://binance-docs.github.io/apidocs/spot/en/#query-open-oco-user_data + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: API response + .. code-block:: python + [ + { + "orderListId": 31, + "contingencyType": "OCO", + "listStatusType": "EXEC_STARTED", + "listOrderStatus": "EXECUTING", + "listClientOrderId": "wuB13fmulKj3YjdqWEcsnp", + "transactionTime": 1565246080644, + "symbol": "LTCBTC", + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 4, + "clientOrderId": "r3EH2N76dHfLoSZWIUw1bT" + }, + { + "symbol": "LTCBTC", + "orderId": 5, + "clientOrderId": "Cv1SnyPD3qhqpbjpYEHbd2" + } + ] + } + ] + :raises: BinanceRequestException, BinanceAPIException + """ + return self._get('openOrderList', True, data=params) + # User Stream Endpoints def get_account(self, **params): """Get current account information. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-information-user_data + https://binance-docs.github.io/apidocs/spot/en/#account-information-user_data :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -1565,8 +2170,6 @@ def get_account(self, **params): def get_asset_balance(self, asset, **params): """Get current asset balance. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-information-user_data - :param asset: required :type asset: str :param recvWindow: the number of milliseconds the request is valid for @@ -1596,11 +2199,15 @@ def get_asset_balance(self, asset, **params): def get_my_trades(self, **params): """Get trades for a specific symbol. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data + https://binance-docs.github.io/apidocs/spot/en/#account-trade-list-user_data :param symbol: required :type symbol: str - :param limit: Default 500; max 500. + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: Default 500; max 1000. :type limit: int :param fromId: TradeId to fetch from. Default gets most recent trades. :type fromId: int @@ -1630,10 +2237,120 @@ def get_my_trades(self, **params): """ return self._get('myTrades', True, data=params) + def get_current_order_count(self): + """Displays the user's current order count usage for all intervals. + + https://binance-docs.github.io/apidocs/spot/en/#query-current-order-count-usage-trade + + :returns: API response + + .. code-block:: python + [ + + { + "rateLimitType": "ORDERS", + "interval": "SECOND", + "intervalNum": 10, + "limit": 10000, + "count": 0 + }, + { + "rateLimitType": "ORDERS", + "interval": "DAY", + "intervalNum": 1, + "limit": 20000, + "count": 0 + } + ] + + """ + return self._get('rateLimit/order', True) + + def get_prevented_matches(self, **params): + """Displays the list of orders that were expired because of STP. + + https://binance-docs.github.io/apidocs/spot/en/#query-prevented-matches-user_data + + :param symbol: required + :type symbol: str + :param preventedMatchId: optional + :type preventedMatchId: int + :param orderId: optional + :type orderId: int + :param fromPreventedMatchId: optional + :type fromPreventedMatchId: int + :param limit: optional, Default: 500; Max: 1000 + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + [ + { + "symbol": "BTCUSDT", + "preventedMatchId": 1, + "takerOrderId": 5, + "makerOrderId": 3, + "tradeGroupId": 1, + "selfTradePreventionMode": "EXPIRE_MAKER", + "price": "1.100000", + "makerPreventedQuantity": "1.300000", + "transactTime": 1669101687094 + } + ] + """ + return self._get('myPreventedMatches', True, data=params) + + def get_allocations(self, **params): + """Retrieves allocations resulting from SOR order placement. + + https://binance-docs.github.io/apidocs/spot/en/#query-allocations-user_data + + :param symbol: required + :type symbol: str + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param fromAllocationId: optional + :type fromAllocationId: int + :param orderId: optional + :type orderId: int + :param limit: optional, Default: 500; Max: 1000 + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + [ + { + "symbol": "BTCUSDT", + "allocationId": 0, + "allocationType": "SOR", + "orderId": 1, + "orderListId": -1, + "price": "1.00000000", + "qty": "5.00000000", + "quoteQty": "5.00000000", + "commission": "0.00000000", + "commissionAsset": "BTC", + "time": 1687506878118, + "isBuyer": true, + "isMaker": false, + "isAllocator": false + } + ] + """ + return self._get('myAllocations', True, data=params) + def get_system_status(self): """Get system status detail. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#system-status-system + https://binance-docs.github.io/apidocs/spot/en/#system-status-sapi-system :returns: API response @@ -1647,13 +2364,14 @@ def get_system_status(self): :raises: BinanceAPIException """ - return self._request_withdraw_api('get', 'systemStatus.html') + return self._request_margin_api('get', 'system/status') - def get_account_status(self, **params): + def get_account_status(self, version=1,**params): """Get account status detail. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#account-status-user_data - + https://binance-docs.github.io/apidocs/spot/en/#account-status-sapi-user_data + :param version: the api version + :param version: int :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -1662,25 +2380,20 @@ def get_account_status(self, **params): .. code-block:: python { - "msg": "Order failed:Low Order fill rate! Will be reactivated after 5 minutes.", - "success": true, - "objs": [ - "5" - ] + "data": "Normal" } - :raises: BinanceWithdrawException - """ - res = self._request_withdraw_api('get', 'accountStatus.html', True, data=params) - if not res['success']: - raise BinanceWithdrawException(res['msg']) - return res + if self.tld == 'us': + path = 'accountStatus' + else: + path = 'account/status' + return self._request_margin_api('get', path, True, version, data=params) - def get_dust_log(self, **params): - """Get log of small amounts exchanged for BNB. + def get_account_api_trading_status(self, **params): + """Fetch account api trading status detail. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data + https://binance-docs.github.io/apidocs/spot/en/#account-api-trading-status-sapi-user_data :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -1690,81 +2403,68 @@ def get_dust_log(self, **params): .. code-block:: python { - "success": true, - "results": { - "total": 2, //Total counts of exchange - "rows": [ - { - "transfered_total": "0.00132256", # Total transfered BNB amount for this exchange. - "service_charge_total": "0.00002699", # Total service charge amount for this exchange. - "tran_id": 4359321, - "logs": [ # Details of this exchange. - { - "tranId": 4359321, - "serviceChargeAmount": "0.000009", - "uid": "10000015", - "amount": "0.0009", - "operateTime": "2018-05-03 17:07:04", - "transferedAmount": "0.000441", - "fromAsset": "USDT" - }, - { - "tranId": 4359321, - "serviceChargeAmount": "0.00001799", - "uid": "10000015", - "amount": "0.0009", - "operateTime": "2018-05-03 17:07:04", - "transferedAmount": "0.00088156", - "fromAsset": "ETH" - } - ], - "operate_time": "2018-05-03 17:07:04" //The time of this exchange. - }, - { - "transfered_total": "0.00058795", - "service_charge_total": "0.000012", - "tran_id": 4357015, - "logs": [ // Details of this exchange. - { - "tranId": 4357015, - "serviceChargeAmount": "0.00001", - "uid": "10000015", - "amount": "0.001", - "operateTime": "2018-05-02 13:52:24", - "transferedAmount": "0.00049", - "fromAsset": "USDT" - }, - { - "tranId": 4357015, - "serviceChargeAmount": "0.000002", - "uid": "10000015", - "amount": "0.0001", - "operateTime": "2018-05-02 13:51:11", - "transferedAmount": "0.00009795", - "fromAsset": "ETH" - } - ], - "operate_time": "2018-05-02 13:51:11" - } - ] + "data": { // API trading status detail + "isLocked": false, // API trading function is locked or not + "plannedRecoverTime": 0, // If API trading function is locked, this is the planned recover time + "triggerCondition": { + "GCR": 150, // Number of GTC orders + "IFER": 150, // Number of FOK/IOC orders + "UFR": 300 // Number of orders + }, + "indicators": { // The indicators updated every 30 seconds + "BTCUSDT": [ // The symbol + { + "i": "UFR", // Unfilled Ratio (UFR) + "c": 20, // Count of all orders + "v": 0.05, // Current UFR value + "t": 0.995 // Trigger UFR value + }, + { + "i": "IFER", // IOC/FOK Expiration Ratio (IFER) + "c": 20, // Count of FOK/IOC orders + "v": 0.99, // Current IFER value + "t": 0.99 // Trigger IFER value + }, + { + "i": "GCR", // GTC Cancellation Ratio (GCR) + "c": 20, // Count of GTC orders + "v": 0.99, // Current GCR value + "t": 0.99 // Trigger GCR value + } + ], + "ETHUSDT": [ + { + "i": "UFR", + "c": 20, + "v": 0.05, + "t": 0.995 + }, + { + "i": "IFER", + "c": 20, + "v": 0.99, + "t": 0.99 + }, + { + "i": "GCR", + "c": 20, + "v": 0.99, + "t": 0.99 + } + ] + }, + "updateTime": 1547630471725 } } - :raises: BinanceWithdrawException - """ - res = self._request_withdraw_api('get', 'userAssetDribbletLog.html', True, data=params) - if not res['success']: - raise BinanceWithdrawException(res['msg']) - return res + return self._request_margin_api('get', 'account/apiTradingStatus', True, data=params) - def get_trade_fee(self, **params): - """Get trade fee. + def get_account_api_permissions(self, **params): + """Fetch api key permissions. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#trade-fee-user_data + https://binance-docs.github.io/apidocs/spot/en/#get-api-key-permission-user_data - :param symbol: optional - :type symbol: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -1773,122 +2473,168 @@ def get_trade_fee(self, **params): .. code-block:: python { - "tradeFee": [ - { - "symbol": "ADABNB", - "maker": 0.9000, - "taker": 1.0000 - }, { - "symbol": "BNBBTC", - "maker": 0.3000, - "taker": 0.3000 - } - ], - "success": true + "ipRestrict": false, + "createTime": 1623840271000, + "enableWithdrawals": false, // This option allows you to withdraw via API. You must apply the IP Access Restriction filter in order to enable withdrawals + "enableInternalTransfer": true, // This option authorizes this key to transfer funds between your master account and your sub account instantly + "permitsUniversalTransfer": true, // Authorizes this key to be used for a dedicated universal transfer API to transfer multiple supported currencies. Each business's own transfer API rights are not affected by this authorization + "enableVanillaOptions": false, // Authorizes this key to Vanilla options trading + "enableReading": true, + "enableFutures": false, // API Key created before your futures account opened does not support futures API service + "enableMargin": false, // This option can be adjusted after the Cross Margin account transfer is completed + "enableSpotAndMarginTrading": false, // Spot and margin trading + "tradingAuthorityExpirationTime": 1628985600000 // Expiration time for spot and margin trading permission } - :raises: BinanceWithdrawException - """ - res = self._request_withdraw_api('get', 'tradeFee.html', True, data=params) - if not res['success']: - raise BinanceWithdrawException(res['msg']) - return res + return self._request_margin_api('get', 'account/apiRestrictions', True, data=params) - def get_asset_details(self, **params): - """Fetch details on assets. + def get_dust_assets(self, **params): + """Get assets that can be converted into BNB - https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#asset-detail-user_data - - :param recvWindow: the number of milliseconds the request is valid for - :type recvWindow: int + https://binance-docs.github.io/apidocs/spot/en/#get-assets-that-can-be-converted-into-bnb-user_data :returns: API response .. code-block:: python { - "success": true, - "assetDetail": { - "CTR": { - "minWithdrawAmount": "70.00000000", //min withdraw amount - "depositStatus": false,//deposit status - "withdrawFee": 35, // withdraw fee - "withdrawStatus": true, //withdraw status - "depositTip": "Delisted, Deposit Suspended" //reason - }, - "SKY": { - "minWithdrawAmount": "0.02000000", - "depositStatus": true, - "withdrawFee": 0.01, - "withdrawStatus": true + "details": [ + { + "asset": "ADA", + "assetFullName": "ADA", + "amountFree": "6.21", //Convertible amount + "toBTC": "0.00016848", //BTC amount + "toBNB": "0.01777302", //BNB amount(Not deducted commission fee) + "toBNBOffExchange": "0.01741756", //BNB amount(Deducted commission fee) + "exchange": "0.00035546" //Commission fee } - } + ], + "totalTransferBtc": "0.00016848", + "totalTransferBNB": "0.01777302", + "dribbletPercentage": "0.02" //Commission fee } - :raises: BinanceWithdrawException - """ - res = self._request_withdraw_api('get', 'assetDetail.html', True, data=params) - if not res['success']: - raise BinanceWithdrawException(res['msg']) - return res + return self._request_margin_api('post', 'asset/dust-btc', True, data=params) - # Withdraw Endpoints + def get_dust_log(self, **params): + """Get log of small amounts exchanged for BNB. - def withdraw(self, **params): - """Submit a withdraw request. + https://binance-docs.github.io/apidocs/spot/en/#dustlog-sapi-user_data - https://www.binance.com/restapipub.html + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int - Assumptions: + :returns: API response - - You must have Withdraw permissions enabled on your API key - - You must have withdrawn to the address specified through the website and approved the transaction via email + .. code-block:: python - :param asset: required + { + "total": 8, //Total counts of exchange + "userAssetDribblets": [ + { + "totalTransferedAmount": "0.00132256", // Total transfered BNB amount for this exchange. + "totalServiceChargeAmount": "0.00002699", //Total service charge amount for this exchange. + "transId": 45178372831, + "userAssetDribbletDetails": [ //Details of this exchange. + { + "transId": 4359321, + "serviceChargeAmount": "0.000009", + "amount": "0.0009", + "operateTime": 1615985535000, + "transferedAmount": "0.000441", + "fromAsset": "USDT" + }, + { + "transId": 4359321, + "serviceChargeAmount": "0.00001799", + "amount": "0.0009", + "operateTime": "2018-05-03 17:07:04", + "transferedAmount": "0.00088156", + "fromAsset": "ETH" + } + ] + }, + { + "operateTime":1616203180000, + "totalTransferedAmount": "0.00058795", + "totalServiceChargeAmount": "0.000012", + "transId": 4357015, + "userAssetDribbletDetails": [ + { + "transId": 4357015, + "serviceChargeAmount": "0.00001" + "amount": "0.001", + "operateTime": 1616203180000, + "transferedAmount": "0.00049", + "fromAsset": "USDT" + }, + { + "transId": 4357015, + "serviceChargeAmount": "0.000002" + "amount": "0.0001", + "operateTime": 1616203180000, + "transferedAmount": "0.00009795", + "fromAsset": "ETH" + } + ] + } + ] + } + + """ + return self._request_margin_api('get', 'asset/dribblet', True, data=params) + + def transfer_dust(self, **params): + """Convert dust assets to BNB. + + https://binance-docs.github.io/apidocs/spot/en/#dust-transfer-user_data + + :param asset: The asset being converted. e.g: 'ONE' :type asset: str - :type address: required - :type address: str - :type addressTag: optional - Secondary address identifier for coins like XRP,XMR etc. - :type address: str - :param amount: required - :type amount: decimal - :param name: optional - Description of the address, default asset value passed will be used - :type name: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int + .. code-block:: python + + result = client.transfer_dust(asset='ONE') + :returns: API response .. code-block:: python { - "msg": "success", - "success": true, - "id":"7213fea8e94b4a5593d507237e5a555b" + "totalServiceCharge":"0.02102542", + "totalTransfered":"1.05127099", + "transferResult":[ + { + "amount":"0.03000000", + "fromAsset":"ETH", + "operateTime":1563368549307, + "serviceChargeAmount":"0.00500000", + "tranId":2970932918, + "transferedAmount":"0.25000000" + } + ] } - :raises: BinanceRequestException, BinanceAPIException, BinanceWithdrawException + :raises: BinanceRequestException, BinanceAPIException """ - # force a name for the withdrawal if one not set - if 'asset' in params and 'name' not in params: - params['name'] = params['asset'] - res = self._request_withdraw_api('post', 'withdraw.html', True, data=params) - if not res['success']: - raise BinanceWithdrawException(res['msg']) - return res + return self._request_margin_api('post', 'asset/dust', True, data=params) - def get_deposit_history(self, **params): - """Fetch deposit history. + def get_asset_dividend_history(self, **params): + """Query asset dividend record. - https://www.binance.com/restapipub.html + https://binance-docs.github.io/apidocs/spot/en/#asset-dividend-record-user_data :param asset: optional :type asset: str - :type status: 0(0:pending,1:success) optional - :type status: int :param startTime: optional :type startTime: long :param endTime: optional @@ -1896,80 +2642,132 @@ def get_deposit_history(self, **params): :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int + .. code-block:: python + + result = client.get_asset_dividend_history() + :returns: API response .. code-block:: python { - "depositList": [ + "rows":[ { - "insertTime": 1508198532000, - "amount": 0.04670582, - "asset": "ETH", - "status": 1 + "amount":"10.00000000", + "asset":"BHFT", + "divTime":1563189166000, + "enInfo":"BHFT distribution", + "tranId":2968885920 + }, + { + "amount":"10.00000000", + "asset":"BHFT", + "divTime":1563189165000, + "enInfo":"BHFT distribution", + "tranId":2968885920 } ], - "success": true + "total":2 } :raises: BinanceRequestException, BinanceAPIException """ - return self._request_withdraw_api('get', 'depositHistory.html', True, data=params) + return self._request_margin_api('get', 'asset/assetDividend', True, data=params) - def get_withdraw_history(self, **params): - """Fetch withdraw history. + def make_universal_transfer(self, **params): + """User Universal Transfer - https://www.binance.com/restapipub.html + https://binance-docs.github.io/apidocs/spot/en/#user-universal-transfer - :param asset: optional + :param type: required + :type type: str (ENUM) + :param asset: required :type asset: str - :type status: 0(0:Email Sent,1:Cancelled 2:Awaiting Approval 3:Rejected 4:Processing 5:Failure 6Completed) optional - :type status: int + :param amount: required + :type amount: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer_status = client.make_universal_transfer(params) + + :returns: API response + + .. code-block:: python + + { + "tranId":13526853623 + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'asset/transfer', signed=True, data=params) + + def query_universal_transfer_history(self, **params): + """Query User Universal Transfer History + + https://binance-docs.github.io/apidocs/spot/en/#query-user-universal-transfer-history + + :param type: required + :type type: str (ENUM) :param startTime: optional - :type startTime: long + :type startTime: int :param endTime: optional - :type endTime: long + :type endTime: int + :param current: optional - Default 1 + :type current: int + :param size: required - Default 10, Max 100 + :type size: int :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int + .. code-block:: python + + transfer_status = client.query_universal_transfer_history(params) + :returns: API response .. code-block:: python { - "withdrawList": [ + "total":2, + "rows":[ { - "amount": 1, - "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", - "asset": "ETH", - "applyTime": 1508198532000 - "status": 4 + "asset":"USDT", + "amount":"1", + "type":"MAIN_UMFUTURE" + "status": "CONFIRMED", + "tranId": 11415955596, + "timestamp":1544433328000 }, { - "amount": 0.005, - "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", - "txId": "0x80aaabed54bdab3f6de5868f89929a2371ad21d666f20f7393d1a3389fad95a1", - "asset": "ETH", - "applyTime": 1508198532000, - "status": 4 + "asset":"USDT", + "amount":"2", + "type":"MAIN_UMFUTURE", + "status": "CONFIRMED", + "tranId": 11366865406, + "timestamp":1544433328000 } - ], - "success": true + ] } + :raises: BinanceRequestException, BinanceAPIException """ - return self._request_withdraw_api('get', 'withdrawHistory.html', True, data=params) + return self._request_margin_api('get', 'asset/transfer', signed=True, data=params) - def get_deposit_address(self, **params): - """Fetch a deposit address for a symbol + def get_trade_fee(self, **params): + """Get trade fee. - https://www.binance.com/restapipub.html + https://binance-docs.github.io/apidocs/spot/en/#trade-fee-sapi-user_data - :param asset: required - :type asset: str + :param symbol: optional + :type symbol: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -1977,22 +2775,33 @@ def get_deposit_address(self, **params): .. code-block:: python - { - "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", - "success": true, - "addressTag": "1231212", - "asset": "BNB" - } - - :raises: BinanceRequestException, BinanceAPIException + [ + { + "symbol": "ADABNB", + "makerCommission": "0.001", + "takerCommission": "0.001" + }, + { + "symbol": "BNBBTC", + "makerCommission": "0.001", + "takerCommission": "0.001" + } + ] """ - return self._request_withdraw_api('get', 'depositAddress.html', True, data=params) + if self.tld == 'us': + endpoint = 'asset/query/trading-fee' + else: + endpoint = 'asset/tradeFee' - def get_withdraw_fee(self, **params): - """Fetch the withdrawal fee for an asset + return self._request_margin_api('get', endpoint, True, data=params) - :param asset: required + def get_asset_details(self, **params): + """Fetch details on assets. + + https://binance-docs.github.io/apidocs/spot/en/#asset-detail-sapi-user_data + + :param asset: optional :type asset: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -2002,80 +2811,9586 @@ def get_withdraw_fee(self, **params): .. code-block:: python { - "withdrawFee": "0.0005", - "success": true + "CTR": { + "minWithdrawAmount": "70.00000000", //min withdraw amount + "depositStatus": false,//deposit status (false if ALL of networks' are false) + "withdrawFee": 35, // withdraw fee + "withdrawStatus": true, //withdraw status (false if ALL of networks' are false) + "depositTip": "Delisted, Deposit Suspended" //reason + }, + "SKY": { + "minWithdrawAmount": "0.02000000", + "depositStatus": true, + "withdrawFee": 0.01, + "withdrawStatus": true + } } - :raises: BinanceRequestException, BinanceAPIException + """ + return self._request_margin_api('get', 'asset/assetDetail', True, data=params) + + def get_spot_delist_schedule(self, **params): + """Get symbols delist schedule for spot + + https://binance-docs.github.io/apidocs/spot/en/#get-symbols-delist-schedule-for-spot-market_data + + :param recvWindow: optional - the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + .. code-block:: python + [ + { + "delistTime": 1686161202000, + "symbols": [ + "ADAUSDT", + "BNBUSDT" + ] + }, + { + "delistTime": 1686222232000, + "symbols": [ + "ETHUSDT" + ] + } + ] """ - return self._request_withdraw_api('get', 'withdrawFee.html', True, data=params) + return self._request_margin_api('get', '/spot/delist-schedule', True, data=params) - # User Stream Endpoints + # Withdraw Endpoints - def stream_get_listen_key(self): - """Start a new user data stream and return the listen key - If a stream already exists it should return the same key. - If the stream becomes invalid a new key is returned. + def withdraw(self, **params): + """Submit a withdraw request. - Can be used to keep the user stream alive. + https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi + + Assumptions: + + - You must have Withdraw permissions enabled on your API key + - You must have withdrawn to the address specified through the website and approved the transaction via email - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#start-user-data-stream-user_stream + :param coin: required + :type coin: str + :param withdrawOrderId: optional - client id for withdraw + :type withdrawOrderId: str + :param network: optional + :type network: str + :param address: optional + :type address: str + :type addressTag: optional - Secondary address identifier for coins like XRP,XMR etc. + :param amount: required + :type amount: decimal + :param transactionFeeFlag: required - When making internal transfer, true for returning the fee to the destination account; false for returning the fee back to the departure account. Default false. + :type transactionFeeFlag: bool + :param name: optional - Description of the address, default asset value passed will be used + :type name: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int :returns: API response .. code-block:: python { - "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" + "id":"7213fea8e94b4a5593d507237e5a555b" } :raises: BinanceRequestException, BinanceAPIException """ - res = self._post('userDataStream', False, data={}) - return res['listenKey'] + return self._request_margin_api('post', 'capital/withdraw/apply', True, data=params) - def stream_keepalive(self, listenKey): - """PING a user data stream to prevent a time out. + def get_deposit_history(self, **params): + """Fetch deposit history. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#keepalive-user-data-stream-user_stream + https://binance-docs.github.io/apidocs/spot/en/#deposit-history-supporting-network-user_data - :param listenKey: required - :type listenKey: str + :param coin: optional + :type coin: str + :type status: optional - 0(0:pending,1:success) optional + :type status: int + :param startTime: optional + :type startTime: long + :param endTime: optional + :type endTime: long + :param offset: optional - default:0 + :type offset: long + :param limit: optional + :type limit: long + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int :returns: API response .. code-block:: python - {} + [ + { + "amount":"0.00999800", + "coin":"PAXG", + "network":"ETH", + "status":1, + "address":"0x788cabe9236ce061e5a892e1a59395a81fc8d62c", + "addressTag":"", + "txId":"0xaad4654a3234aa6118af9b4b335f5ae81c360b2394721c019b5d1e75328b09f3", + "insertTime":1599621997000, + "transferType":0, + "confirmTimes":"12/12" + }, + { + "amount":"0.50000000", + "coin":"IOTA", + "network":"IOTA", + "status":1, + "address":"SIZ9VLMHWATXKV99LH99CIGFJFUMLEHGWVZVNNZXRJJVWBPHYWPPBOSDORZ9EQSHCZAMPVAPGFYQAUUV9DROOXJLNW", + "addressTag":"", + "txId":"ESBFVQUTPIWQNJSPXFNHNYHSQNTGKRVKPRABQWTAXCDWOAKDKYWPTVG9BGXNVNKTLEJGESAVXIKIZ9999", + "insertTime":1599620082000, + "transferType":0, + "confirmTimes":"1/1" + } + ] :raises: BinanceRequestException, BinanceAPIException """ - params = { - 'listenKey': listenKey - } - return self._put('userDataStream', False, data=params) + return self._request_margin_api('get', 'capital/deposit/hisrec', True, data=params) - def stream_close(self, listenKey): - """Close out a user data stream. + def get_withdraw_history(self, **params): + """Fetch withdraw history. - https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#close-user-data-stream-user_stream + https://binance-docs.github.io/apidocs/spot/en/#withdraw-history-supporting-network-user_data - :param listenKey: required - :type listenKey: str + :param coin: optional + :type coin: str + :type status: 0(0:Email Sent,1:Cancelled 2:Awaiting Approval 3:Rejected 4:Processing 5:Failure 6Completed) optional + :type status: int + :param offset: optional - default:0 + :type offset: int + :param limit: optional + :type limit: int + :param startTime: optional - Default: 90 days from current timestamp + :type startTime: int + :param endTime: optional - Default: present timestamp + :type endTime: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int :returns: API response .. code-block:: python - {} + [ + { + "address": "0x94df8b352de7f46f64b01d3666bf6e936e44ce60", + "amount": "8.91000000", + "applyTime": "2019-10-12 11:12:02", + "coin": "USDT", + "id": "b6ae22b3aa844210a7041aee7589627c", + "withdrawOrderId": "WITHDRAWtest123", // will not be returned if there's no withdrawOrderId for this withdraw. + "network": "ETH", + "transferType": 0, // 1 for internal transfer, 0 for external transfer + "status": 6, + "txId": "0xb5ef8c13b968a406cc62a93a8bd80f9e9a906ef1b3fcf20a2e48573c17659268" + }, + { + "address": "1FZdVHtiBqMrWdjPyRPULCUceZPJ2WLCsB", + "amount": "0.00150000", + "applyTime": "2019-09-24 12:43:45", + "coin": "BTC", + "id": "156ec387f49b41df8724fa744fa82719", + "network": "BTC", + "status": 6, + "txId": "60fd9007ebfddc753455f95fafa808c4302c836e4d1eebc5a132c36c1d8ac354" + } + ] :raises: BinanceRequestException, BinanceAPIException """ - params = { - 'listenKey': listenKey - } - return self._delete('userDataStream', False, data=params) + return self._request_margin_api('get', 'capital/withdraw/history', True, data=params) + + def get_withdraw_history_id(self, withdraw_id, **params): + """Fetch withdraw history. + + https://binance-docs.github.io/apidocs/spot/en/#withdraw-history-supporting-network-user_data + + :param withdraw_id: required + :type withdraw_id: str + :param asset: optional + :type asset: str + :type status: 0(0:Email Sent,1:Cancelled 2:Awaiting Approval 3:Rejected 4:Processing 5:Failure 6Completed) optional + :type status: int + :param startTime: optional + :type startTime: long + :param endTime: optional + :type endTime: long + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "id":"7213fea8e94b4a5593d507237e5a555b", + "withdrawOrderId": None, + "amount": 0.99, + "transactionFee": 0.01, + "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", + "asset": "ETH", + "txId": "0xdf33b22bdb2b28b1f75ccd201a4a4m6e7g83jy5fc5d5a9d1340961598cfcb0a1", + "applyTime": 1508198532000, + "status": 4 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + result = self.get_withdraw_history(**params) + + for entry in result: + if 'id' in entry and entry['id'] == withdraw_id: + return entry + + raise Exception("There is no entry with withdraw id", result) + + def get_deposit_address(self, coin: str, network: Optional[str] = None, **params): + """Fetch a deposit address for a symbol + + https://binance-docs.github.io/apidocs/spot/en/#deposit-address-supporting-network-user_data + + :param coin: required + :type coin: str + :param network: optional + :type network: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "address": "1HPn8Rx2y6nNSfagQBKy27GB99Vbzg89wv", + "coin": "BTC", + "tag": "", + "url": "https://btc.com/1HPn8Rx2y6nNSfagQBKy27GB99Vbzg89wv" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params['coin'] = coin + if network: + params['network'] = network + return self._request_margin_api('get', 'capital/deposit/address', True, data=params) + + # User Stream Endpoints + + def stream_get_listen_key(self): + """Start a new user data stream and return the listen key + If a stream already exists it should return the same key. + If the stream becomes invalid a new key is returned. + + Can be used to keep the user stream alive. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot + + :returns: API response + + .. code-block:: python + + { + "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + res = self._post('userDataStream', False, data={}, version=self.PRIVATE_API_VERSION) + return res['listenKey'] + + def stream_keepalive(self, listenKey): + """PING a user data stream to prevent a time out. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot + + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'listenKey': listenKey + } + return self._put('userDataStream', False, data=params, version=self.PRIVATE_API_VERSION) + + def stream_close(self, listenKey): + """Close out a user data stream. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot + + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'listenKey': listenKey + } + return self._delete('userDataStream', False, data=params, version=self.PRIVATE_API_VERSION) + + # Margin Trading Endpoints + + def get_margin_account(self, **params): + """Query cross-margin account details + + https://binance-docs.github.io/apidocs/spot/en/#query-cross-margin-account-details-user_data + + :returns: API response + + .. code-block:: python + + { + "borrowEnabled": true, + "marginLevel": "11.64405625", + "totalAssetOfBtc": "6.82728457", + "totalLiabilityOfBtc": "0.58633215", + "totalNetAssetOfBtc": "6.24095242", + "tradeEnabled": true, + "transferEnabled": true, + "userAssets": [ + { + "asset": "BTC", + "borrowed": "0.00000000", + "free": "0.00499500", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00499500" + }, + { + "asset": "BNB", + "borrowed": "201.66666672", + "free": "2346.50000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "2144.83333328" + }, + { + "asset": "ETH", + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000" + }, + { + "asset": "USDT", + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/account', True, data=params) + + def get_isolated_margin_account(self, **params): + """Query isolated margin account details + + https://binance-docs.github.io/apidocs/spot/en/#query-isolated-margin-account-info-user_data + + :param symbols: optional up to 5 margin pairs as a comma separated string + :type asset: str + + .. code:: python + + account_info = client.get_isolated_margin_account() + account_info = client.get_isolated_margin_account(symbols="BTCUSDT,ETHUSDT") + + :returns: API response + + .. code-block:: python + + If "symbols" is not sent: + + { + "assets":[ + { + "baseAsset": + { + "asset": "BTC", + "borrowEnabled": true, + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000", + "netAssetOfBtc": "0.00000000", + "repayEnabled": true, + "totalAsset": "0.00000000" + }, + "quoteAsset": + { + "asset": "USDT", + "borrowEnabled": true, + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000", + "netAssetOfBtc": "0.00000000", + "repayEnabled": true, + "totalAsset": "0.00000000" + }, + "symbol": "BTCUSDT" + "isolatedCreated": true, + "marginLevel": "0.00000000", + "marginLevelStatus": "EXCESSIVE", // "EXCESSIVE", "NORMAL", "MARGIN_CALL", "PRE_LIQUIDATION", "FORCE_LIQUIDATION" + "marginRatio": "0.00000000", + "indexPrice": "10000.00000000" + "liquidatePrice": "1000.00000000", + "liquidateRate": "1.00000000" + "tradeEnabled": true + } + ], + "totalAssetOfBtc": "0.00000000", + "totalLiabilityOfBtc": "0.00000000", + "totalNetAssetOfBtc": "0.00000000" + } + + If "symbols" is sent: + + { + "assets":[ + { + "baseAsset": + { + "asset": "BTC", + "borrowEnabled": true, + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000", + "netAssetOfBtc": "0.00000000", + "repayEnabled": true, + "totalAsset": "0.00000000" + }, + "quoteAsset": + { + "asset": "USDT", + "borrowEnabled": true, + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000", + "netAssetOfBtc": "0.00000000", + "repayEnabled": true, + "totalAsset": "0.00000000" + }, + "symbol": "BTCUSDT" + "isolatedCreated": true, + "marginLevel": "0.00000000", + "marginLevelStatus": "EXCESSIVE", // "EXCESSIVE", "NORMAL", "MARGIN_CALL", "PRE_LIQUIDATION", "FORCE_LIQUIDATION" + "marginRatio": "0.00000000", + "indexPrice": "10000.00000000" + "liquidatePrice": "1000.00000000", + "liquidateRate": "1.00000000" + "tradeEnabled": true + } + ] + } + + """ + return self._request_margin_api('get', 'margin/isolated/account', True, data=params) + + def enable_isolated_margin_account(self, **params): + """Enable isolated margin account for a specific symbol. + + https://binance-docs.github.io/apidocs/spot/en/#enable-isolated-margin-account-trade + + :param symbol: + :type asset: str + + :returns: API response + + .. code-block:: python + + { + "success": true, + "symbol": "BTCUSDT" + } + + + """ + return self._request_margin_api('post', 'margin/isolated/account', True, data=params) + + def disable_isolated_margin_account(self, **params): + """Disable isolated margin account for a specific symbol. Each trading pair can only + be deactivated once every 24 hours. + + https://binance-docs.github.io/apidocs/spot/en/#disable-isolated-margin-account-trade + + :param symbol: + :type asset: str + + :returns: API response + + .. code-block:: python + + { + "success": true, + "symbol": "BTCUSDT" + } + + """ + return self._request_margin_api('delete', 'margin/isolated/account', True, data=params) + + def get_enabled_isolated_margin_account_limit(self, **params): + """Query enabled isolated margin account limit. + + https://binance-docs.github.io/apidocs/spot/en/#query-enabled-isolated-margin-account-limit-user_data + + :returns: API response + + .. code-block:: python + { + "enabledAccount": 5, + "maxAccount": 20 + } + + """ + return self._request_margin_api('get', 'margin/isolated/accountLimit', True, data=params) + + def get_margin_dustlog(self, **params): + """ + Query the historical information of user's margin account small-value asset conversion BNB. + + https://binance-docs.github.io/apidocs/spot/en/#margin-dustlog-user_data + + :param startTime: optional + :type startTime: long + :param endTime: optional + :type endTime: long + + :returns: API response + + .. code-block:: python + { + "total": 8, //Total counts of exchange + "userAssetDribblets": [ + { + "operateTime": 1615985535000, + "totalTransferedAmount": "0.00132256", // Total transfered BNB amount for this exchange. + "totalServiceChargeAmount": "0.00002699", //Total service charge amount for this exchange. + "transId": 45178372831, + "userAssetDribbletDetails": [ //Details of this exchange. + { + "transId": 4359321, + "serviceChargeAmount": "0.000009", + "amount": "0.0009", + "operateTime": 1615985535000, + "transferedAmount": "0.000441", + "fromAsset": "USDT" + }, + { + "transId": 4359321, + "serviceChargeAmount": "0.00001799", + "amount": "0.0009", + "operateTime": 1615985535000, + "transferedAmount": "0.00088156", + "fromAsset": "ETH" + } + ] + }, + { + "operateTime":1616203180000, + "totalTransferedAmount": "0.00058795", + "totalServiceChargeAmount": "0.000012", + "transId": 4357015, + "userAssetDribbletDetails": [ + { + "transId": 4357015, + "serviceChargeAmount": "0.00001", + "amount": "0.001", + "operateTime": 1616203180000, + "transferedAmount": "0.00049", + "fromAsset": "USDT" + }, + { + "transId": 4357015, + "serviceChargeAmount": "0.000002", + "amount": "0.0001", + "operateTime": 1616203180000, + "transferedAmount": "0.00009795", + "fromAsset": "ETH" + } + ] + } + ] + } + + """ + return self._request_margin_api('get', 'margin/dribblet', True, data=params) + + def get_margin_dust_assets(self, **params): + """Get margin assets that can be converted into BNB. + + https://binance-docs.github.io/apidocs/spot/en/#margin-dustlog-user_data + + :returns: API response + + .. code-block:: python + { + "details": [ + { + "asset": "ADA", + "assetFullName": "ADA", + "amountFree": "6.21", + "toBTC": "0.00016848", + "toBNB": "0.01777302", + "toBNBOffExchange": "0.01741756", + "exchange": "0.00035546" + } + ], + "totalTransferBtc": "0.00016848", + "totalTransferBNB": "0.01777302", + "dribbletPercentage": "0.02" + } + + """ + return self._request_margin_api('get', 'margin/dust', True, data=params) + + def transfer_margin_dust(self, **params): + """Convert dust assets to BNB. + + https://binance-docs.github.io/apidocs/spot/en/#dust-transfer-trade + + :returns: API response + + .. code-block:: python + { + "totalServiceCharge":"0.02102542", + "totalTransfered":"1.05127099", + "transferResult":[ + { + "amount":"0.03000000", + "fromAsset":"ETH", + "operateTime":1563368549307, + "serviceChargeAmount":"0.00500000", + "tranId":2970932918, + "transferedAmount":"0.25000000" + }, + { + "amount":"0.09000000", + "fromAsset":"LTC", + "operateTime":1563368549404, + "serviceChargeAmount":"0.01548000", + "tranId":2970932918, + "transferedAmount":"0.77400000" + } + ] + } + + """ + return self._request_margin_api('post', 'margin/dust', True, data=params) + + def get_cross_margin_collateral_ratio(self, **params): + """ + https://binance-docs.github.io/apidocs/spot/en/#cross-margin-collateral-ratio-market_data + + :param none + + :returns: API response + + .. code-block:: python + [ + { + "collaterals": [ + { + "minUsdValue": "0", + "maxUsdValue": "13000000", + "discountRate": "1" + }, + { + "minUsdValue": "13000000", + "maxUsdValue": "20000000", + "discountRate": "0.975" + }, + { + "minUsdValue": "20000000", + "discountRate": "0" + } + ], + "assetNames": [ + "BNX" + ] + }, + { + "collaterals": [ + { + "minUsdValue": "0", + "discountRate": "1" + } + ], + "assetNames": [ + "BTC", + "BUSD", + "ETH", + "USDT" + ] + } + ] + """ + return self._request_margin_api('get', 'margin/crossMarginCollateralRatio', True, data=params) + + def get_small_liability_exchange_assets(self, **params): + """Query the coins which can be small liability exchange + + https://binance-docs.github.io/apidocs/spot/en/#get-small-liability-exchange-coin-list-user_data + + :returns: API response + + .. code-block:: python + [ + { + "asset": "ETH", + "interest": "0.00083334", + "principal": "0.001", + "liabilityAsset": "USDT", + "liabilityQty": 0.3552 + } + ] + + """ + return self._request_margin_api('get', 'margin/exchange-small-liability', True, data=params) + + def exchange_small_liability_assets(self, **params): + """Cross Margin Small Liability Exchange + + https://binance-docs.github.io/apidocs/spot/en/#small-liability-exchange-margin + + :param assetNames: The assets list of small liability exchange + :type assetNames: array + + :returns: API response + + .. code-block:: python + none + + """ + return self._request_margin_api('post', 'margin/exchange-small-liability', True, data=params) + + def get_small_liability_exchange_history(self, **params): + """Get Small liability Exchange History + + https://binance-docs.github.io/apidocs/spot/en/#get-small-liability-exchange-history-user_data + + :param current: Currently querying page. Start from 1. Default:1 + :type current: int + :param size: Default:10, Max:100 + :type size: int + :param startTime: Default: 30 days from current timestamp + :type startTime: long + :param endTime: Default: present timestamp + :type endTIme: long + + :returns: API response + + .. code-block:: python + { + "total": 1, + "rows": [ + { + "asset": "ETH", + "amount": "0.00083434", + "targetAsset": "BUSD", + "targetAmount": "1.37576819", + "bizType": "EXCHANGE_SMALL_LIABILITY", + "timestamp": 1672801339253 + } + ] + } + + """ + return self._request_margin_api('get', 'margin/exchange-small-liability-history', True, data=params) + + def get_future_hourly_interest_rate(self, **params): + """Get user the next hourly estimate interest + + https://binance-docs.github.io/apidocs/spot/en/#get-a-future-hourly-interest-rate-user_data + + :param assets: List of assets, separated by commas, up to 20 + :type assets: str + :param isIsolated: for isolated margin or not, "TRUE", "FALSE" + :type isIsolated: bool + + :returns: API response + + .. code-block:: python + [ + { + "asset": "BTC", + "nextHourlyInterestRate": "0.00000571" + }, + { + "asset": "ETH", + "nextHourlyInterestRate": "0.00000578" + } + ] + + """ + return self._request_margin_api('get', 'margin/next-hourly-interest-rate', True, data=params) + + def get_margin_capital_flow(self, **params): + """Get cross or isolated margin capital flow + + https://binance-docs.github.io/apidocs/spot/en/#get-cross-or-isolated-margin-capital-flow-user_data + + :param asset: optional + :type asset: str + :param symbol: Required when querying isolated data + :type symbol: str + :param type: optional + :type type: string + :param startTime: Only supports querying the data of the last 90 days + :type startTime: long + :param endTime: optional + :type endTime: long + :param formId: If fromId is set, the data with id > fromId will be returned. Otherwise the latest data will be returned + :type formId: long + :param limit: The number of data items returned each time is limited. Default 500; Max 1000. + :type limit: long + + :returns: API response + + .. code-block:: python + [ + { + "id": 123456, + "tranId": 123123, + "timestamp": 1691116657000, + "asset": "USDT, + "symbol": "BTCUSDT", + "type": "BORROW", + "amount": "101" + }, + { + "id": 123457, + "tranId": 123124, + "timestamp": 1691116658000, + "asset": "BTC", + "symbol": "BTCUSDT", + "type": "REPAY", + "amount": "10" + } + ] + + """ + return self._request_margin_api('get', 'margin/capital-flow', True, data=params) + + def get_margin_asset(self, **params): + """Query cross-margin asset + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-asset-market_data + + :param asset: name of the asset + :type asset: str + + .. code-block:: python + + asset_details = client.get_margin_asset(asset='BNB') + + :returns: API response + + .. code-block:: python + + { + "assetFullName": "Binance Coin", + "assetName": "BNB", + "isBorrowable": false, + "isMortgageable": true, + "userMinBorrow": "0.00000000", + "userMinRepay": "0.00000000" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/asset', data=params) + + def get_margin_symbol(self, **params): + """Query cross-margin symbol info + + https://binance-docs.github.io/apidocs/spot/en/#query-cross-margin-pair-market_data + + :param symbol: name of the symbol pair + :type symbol: str + + .. code:: python + + pair_details = client.get_margin_symbol(symbol='BTCUSDT') + + :returns: API response + + .. code-block:: python + + { + "id":323355778339572400, + "symbol":"BTCUSDT", + "base":"BTC", + "quote":"USDT", + "isMarginTrade":true, + "isBuyAllowed":true, + "isSellAllowed":true + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/pair', data=params) + + def get_margin_all_assets(self, **params): + """Get All Margin Assets (MARKET_DATA) + + https://binance-docs.github.io/apidocs/spot/en/#get-all-margin-assets-market_data + + .. code:: python + + margin_assets = client.get_margin_all_assets() + + :returns: API response + + .. code-block:: python + + [ + { + "assetFullName": "USD coin", + "assetName": "USDC", + "isBorrowable": true, + "isMortgageable": true, + "userMinBorrow": "0.00000000", + "userMinRepay": "0.00000000" + }, + { + "assetFullName": "BNB-coin", + "assetName": "BNB", + "isBorrowable": true, + "isMortgageable": true, + "userMinBorrow": "1.00000000", + "userMinRepay": "0.00000000" + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/allAssets', data=params) + + def get_margin_all_pairs(self, **params): + """Get All Cross Margin Pairs (MARKET_DATA) + + https://binance-docs.github.io/apidocs/spot/en/#get-all-cross-margin-pairs-market_data + + .. code:: python + + margin_pairs = client.get_margin_all_pairs() + + :returns: API response + + .. code-block:: python + + [ + { + "base": "BNB", + "id": 351637150141315861, + "isBuyAllowed": true, + "isMarginTrade": true, + "isSellAllowed": true, + "quote": "BTC", + "symbol": "BNBBTC" + }, + { + "base": "TRX", + "id": 351637923235429141, + "isBuyAllowed": true, + "isMarginTrade": true, + "isSellAllowed": true, + "quote": "BTC", + "symbol": "TRXBTC" + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/allPairs', data=params) + + def create_isolated_margin_account(self, **params): + """Create isolated margin account for symbol + + https://binance-docs.github.io/apidocs/spot/en/#create-isolated-margin-account-margin + + :param base: Base asset of symbol + :type base: str + :param quote: Quote asset of symbol + :type quote: str + + .. code:: python + + pair_details = client.create_isolated_margin_account(base='USDT', quote='BTC') + + :returns: API response + + .. code-block:: python + + { + "success": true, + "symbol": "BTCUSDT" + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'margin/isolated/create', signed=True, data=params) + + def get_isolated_margin_symbol(self, **params): + """Query isolated margin symbol info + + https://binance-docs.github.io/apidocs/spot/en/#query-isolated-margin-symbol-user_data + + :param symbol: name of the symbol pair + :type symbol: str + + .. code:: python + + pair_details = client.get_isolated_margin_symbol(symbol='BTCUSDT') + + :returns: API response + + .. code-block:: python + + { + "symbol":"BTCUSDT", + "base":"BTC", + "quote":"USDT", + "isMarginTrade":true, + "isBuyAllowed":true, + "isSellAllowed":true + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/isolated/pair', signed=True, data=params) + + def get_all_isolated_margin_symbols(self, **params): + """Query isolated margin symbol info for all pairs + + https://binance-docs.github.io/apidocs/spot/en/#get-all-isolated-margin-symbol-user_data + + .. code:: python + + pair_details = client.get_all_isolated_margin_symbols() + + :returns: API response + + .. code-block:: python + + [ + { + "base": "BNB", + "isBuyAllowed": true, + "isMarginTrade": true, + "isSellAllowed": true, + "quote": "BTC", + "symbol": "BNBBTC" + }, + { + "base": "TRX", + "isBuyAllowed": true, + "isMarginTrade": true, + "isSellAllowed": true, + "quote": "BTC", + "symbol": "TRXBTC" + } + ] + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/isolated/allPairs', signed=True, data=params) + + def get_isolated_margin_fee_data(self, **params): + """Get isolated margin fee data collection with any vip level or user's current specific data as https://www.binance.com/en/margin-fee + + https://binance-docs.github.io/apidocs/spot/en/#query-isolated-margin-fee-data-user_data + + :param vipLevel: User's current specific margin data will be returned if vipLevel is omitted + :type vipLevel: int + :param symbol: optional + :type symbol: str + + :returns: API response + + .. code-block:: python + [ + { + "vipLevel": 0, + "symbol": "BTCUSDT", + "leverage": "10", + "data": [ + { + "coin": "BTC", + "dailyInterest": "0.00026125", + "borrowLimit": "270" + }, + { + "coin": "USDT", + "dailyInterest": "0.000475", + "borrowLimit": "2100000" + } + ] + } + ] + """ + return self._request_margin_api('get', 'margin/isolatedMarginData', True, data=params) + + def get_isolated_margin_tier_data(self, **params): + """Get isolated margin tier data collection with any tier as https://www.binance.com/en/margin-data + + https://binance-docs.github.io/apidocs/spot/en/#query-isolated-margin-tier-data-user_data + + :param symbol: required + :type symbol: str + :param tier: All margin tier data will be returned if tier is omitted + :type tier: int + :param recvWindow: optional: No more than 60000 + :type recvWindow: + + :returns: API response + + .. code-block:: python + [ + { + "symbol": "BTCUSDT", + "tier": 1, + "effectiveMultiple": "10", + "initialRiskRatio": "1.111", + "liquidationRiskRatio": "1.05", + "baseAssetMaxBorrowable": "9", + "quoteAssetMaxBorrowable": "70000" + } + ] + + """ + return self._request_margin_api('get', 'margin/isolatedMarginTier', True, data=params) + + def margin_manual_liquidation(self, **params): + """ + + https://binance-docs.github.io/apidocs/spot/en/#margin-manual-liquidation-margin + + + + :param type: required + :type symbol: str: When type selected is "ISOLATED", symbol must be filled in + + :returns: API response + + [ + { + "asset": "ETH", + "interest": "0.00083334", + "principal": "0.001", + "liabilityAsset": "USDT", + "liabilityQty": 0.3552 + } + ] + + """ + return self._request_margin_api('post', 'margin/manual-liquidation', True, data=params) + + def toggle_bnb_burn_spot_margin(self, **params): + """Toggle BNB Burn On Spot Trade And Margin Interest + + https://binance-docs.github.io/apidocs/spot/en/#toggle-bnb-burn-on-spot-trade-and-margin-interest-user_data + + :param spotBNBBurn: Determines whether to use BNB to pay for trading fees on SPOT + :type spotBNBBurn: bool + :param interestBNBBurn: Determines whether to use BNB to pay for margin loan's interest + :type interestBNBBurn: bool + + .. code:: python + + response = client.toggle_bnb_burn_spot_margin() + + :returns: API response + + .. code-block:: python + + { + "spotBNBBurn":true, + "interestBNBBurn": false + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'bnbBurn', signed=True, data=params) + + def get_bnb_burn_spot_margin(self, **params): + """Get BNB Burn Status + + https://binance-docs.github.io/apidocs/spot/en/#get-bnb-burn-status-user_data + + .. code:: python + + status = client.get_bnb_burn_spot_margin() + + :returns: API response + + .. code-block:: python + + { + "spotBNBBurn":true, + "interestBNBBurn": false + } + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'bnbBurn', signed=True, data=params) + + def get_margin_price_index(self, **params): + """Query margin priceIndex + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-priceindex-market_data + + :param symbol: name of the symbol pair + :type symbol: str + + .. code:: python + + price_index_details = client.get_margin_price_index(symbol='BTCUSDT') + + :returns: API response + + .. code-block:: python + + { + "calcTime": 1562046418000, + "price": "0.00333930", + "symbol": "BNBBTC" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/priceIndex', data=params) + + def transfer_margin_to_spot(self, **params): + """Execute transfer between cross-margin account and spot account. + + https://binance-docs.github.io/apidocs/spot/en/#cross-margin-account-transfer-margin + + :param asset: name of the asset + :type asset: str + :param amount: amount to transfer + :type amount: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer = client.transfer_margin_to_spot(asset='BTC', amount='1.1') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params['type'] = 2 + return self._request_margin_api('post', 'margin/transfer', signed=True, data=params) + + def transfer_spot_to_margin(self, **params): + """Execute transfer between spot account and cross-margin account. + + https://binance-docs.github.io/apidocs/spot/en/#cross-margin-account-transfer-margin + + :param asset: name of the asset + :type asset: str + :param amount: amount to transfer + :type amount: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer = client.transfer_spot_to_margin(asset='BTC', amount='1.1') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params['type'] = 1 + return self._request_margin_api('post', 'margin/transfer', signed=True, data=params) + + def transfer_isolated_margin_to_spot(self, **params): + """Execute transfer between isolated margin account and spot account. + + https://binance-docs.github.io/apidocs/spot/en/#isolated-margin-account-transfer-margin + + :param asset: name of the asset + :type asset: str + :param symbol: pair symbol + :type symbol: str + :param amount: amount to transfer + :type amount: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer = client.transfer_isolated_margin_to_spot(asset='BTC', + symbol='ETHBTC', amount='1.1') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params['transFrom'] = "ISOLATED_MARGIN" + params['transTo'] = "SPOT" + return self._request_margin_api('post', 'margin/isolated/transfer', signed=True, data=params) + + def transfer_spot_to_isolated_margin(self, **params): + """Execute transfer between spot account and isolated margin account. + + https://binance-docs.github.io/apidocs/spot/en/#isolated-margin-account-transfer-margin + + :param asset: name of the asset + :type asset: str + :param symbol: pair symbol + :type symbol: str + :param amount: amount to transfer + :type amount: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer = client.transfer_spot_to_isolated_margin(asset='BTC', + symbol='ETHBTC', amount='1.1') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params['transFrom'] = "SPOT" + params['transTo'] = "ISOLATED_MARGIN" + return self._request_margin_api('post', 'margin/isolated/transfer', signed=True, data=params) + + def get_isolated_margin_tranfer_history(self, **params): + """Get transfers to isolated margin account. + + https://binance-docs.github.io/apidocs/spot/en/#get-isolated-margin-transfer-history-user_data + + :param asset: name of the asset + :type asset: str + :param symbol: pair required + :type symbol: str + :param transFrom: optional SPOT, ISOLATED_MARGIN + :param transFrom: str SPOT, ISOLATED_MARGIN + :param transTo: optional + :param transTo: str + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transfer = client.transfer_spot_to_isolated_margin(symbol='ETHBTC') + + :returns: API response + + .. code-block:: python + + { + "rows": [ + { + "amount": "0.10000000", + "asset": "BNB", + "status": "CONFIRMED", + "timestamp": 1566898617000, + "txId": 5240372201, + "transFrom": "SPOT", + "transTo": "ISOLATED_MARGIN" + }, + { + "amount": "5.00000000", + "asset": "USDT", + "status": "CONFIRMED", + "timestamp": 1566888436123, + "txId": 5239810406, + "transFrom": "ISOLATED_MARGIN", + "transTo": "SPOT" + } + ], + "total": 2 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/isolated/transfer', signed=True, data=params) + + def create_margin_loan(self, **params): + """Apply for a loan in cross-margin or isolated-margin account. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-borrow-margin + + :param asset: name of the asset + :type asset: str + :param amount: amount to transfer + :type amount: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param symbol: Isolated margin symbol (default blank for cross-margin) + :type symbol: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transaction = client.margin_create_loan(asset='BTC', amount='1.1') + + transaction = client.margin_create_loan(asset='BTC', amount='1.1', + isIsolated='TRUE', symbol='ETHBTC') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'margin/loan', signed=True, data=params) + + def repay_margin_loan(self, **params): + """Repay loan in cross-margin or isolated-margin account. + + If amount is more than the amount borrowed, the full loan will be repaid. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-repay-margin + + :param asset: name of the asset + :type asset: str + :param amount: amount to transfer + :type amount: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param symbol: Isolated margin symbol (default blank for cross-margin) + :type symbol: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + .. code-block:: python + + transaction = client.margin_repay_loan(asset='BTC', amount='1.1') + + transaction = client.margin_repay_loan(asset='BTC', amount='1.1', + isIsolated='TRUE', symbol='ETHBTC') + + :returns: API response + + .. code-block:: python + + { + "tranId": 100000001 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'margin/repay', signed=True, data=params) + + def create_margin_order(self, **params): + """Post a new order for margin account. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-new-order-trade + + :param symbol: required + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param side: required + :type side: str + :param type: required + :type type: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param stopPrice: Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders. + :type stopPrice: str + :param timeInForce: required if limit order GTC,IOC,FOK + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. + :type icebergQty: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to + FULL, all other orders default to ACK. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + Response ACK: + + .. code-block:: python + + { + "symbol": "BTCUSDT", + "orderId": 28, + "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", + "transactTime": 1507725176595 + } + + Response RESULT: + + .. code-block:: python + + { + "symbol": "BTCUSDT", + "orderId": 28, + "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", + "transactTime": 1507725176595, + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "10.00000000", + "cummulativeQuoteQty": "10.00000000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "MARKET", + "side": "SELL" + } + + Response FULL: + + .. code-block:: python + + { + "symbol": "BTCUSDT", + "orderId": 28, + "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", + "transactTime": 1507725176595, + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "10.00000000", + "cummulativeQuoteQty": "10.00000000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "MARKET", + "side": "SELL", + "fills": [ + { + "price": "4000.00000000", + "qty": "1.00000000", + "commission": "4.00000000", + "commissionAsset": "USDT" + }, + { + "price": "3999.00000000", + "qty": "5.00000000", + "commission": "19.99500000", + "commissionAsset": "USDT" + }, + { + "price": "3998.00000000", + "qty": "2.00000000", + "commission": "7.99600000", + "commissionAsset": "USDT" + }, + { + "price": "3997.00000000", + "qty": "1.00000000", + "commission": "3.99700000", + "commissionAsset": "USDT" + }, + { + "price": "3995.00000000", + "qty": "1.00000000", + "commission": "3.99500000", + "commissionAsset": "USDT" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, + BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, + BinanceOrderInactiveSymbolException + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() + return self._request_margin_api('post', 'margin/order', signed=True, data=params) + + def cancel_margin_order(self, **params): + """Cancel an active order for margin account. + + Either orderId or origClientOrderId must be sent. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-cancel-order-trade + + :param symbol: required + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param orderId: + :type orderId: str + :param origClientOrderId: + :type origClientOrderId: str + :param newClientOrderId: Used to uniquely identify this cancel. Automatically generated by default. + :type newClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "symbol": "LTCBTC", + "orderId": 28, + "origClientOrderId": "myOrder1", + "clientOrderId": "cancelMyOrder1", + "transactTime": 1507725176595, + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "8.00000000", + "cummulativeQuoteQty": "8.00000000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "SELL" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('delete', 'margin/order', signed=True, data=params) + + def set_margin_max_leverage(self, **params): + """Adjust cross margin max leverage + + https://binance-docs.github.io/apidocs/spot/en/#adjust-cross-margin-max-leverage-user_data + + :param maxLeverage: required Can only adjust 3 or 5,Example: maxLeverage=3 + :type maxLeverage: int + + :returns: API response + + { + "success": true + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'margin/max-leverage', signed=True, data=params) + + def get_margin_transfer_history(self, **params): + """Query margin transfer history + + https://binance-docs.github.io/apidocs/spot/en/#get-cross-margin-transfer-history-user_data + + :param asset: optional + :type asset: str + :param type: optional Transfer Type: ROLL_IN, ROLL_OUT + :type type: str + :param archived: optional Default: false. Set to true for archived data from 6 months ago + :type archived: str + :param startTime: earliest timestamp to filter transactions + :type startTime: str + :param endTime: Used to uniquely identify this cancel. Automatically generated by default. + :type endTime: str + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "rows": [ + { + "amount": "0.10000000", + "asset": "BNB", + "status": "CONFIRMED", + "timestamp": 1566898617, + "txId": 5240372201, + "type": "ROLL_IN" + }, + { + "amount": "5.00000000", + "asset": "USDT", + "status": "CONFIRMED", + "timestamp": 1566888436, + "txId": 5239810406, + "type": "ROLL_OUT" + }, + { + "amount": "1.00000000", + "asset": "EOS", + "status": "CONFIRMED", + "timestamp": 1566888403, + "txId": 5239808703, + "type": "ROLL_IN" + } + ], + "total": 3 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/transfer', signed=True, data=params) + + def get_margin_loan_details(self, **params): + """Query loan record + + txId or startTime must be sent. txId takes precedence. + + https://binance-docs.github.io/apidocs/spot/en/#query-loan-record-user_data + + :param asset: required + :type asset: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param txId: the tranId in of the created loan + :type txId: str + :param startTime: earliest timestamp to filter transactions + :type startTime: str + :param endTime: Used to uniquely identify this cancel. Automatically generated by default. + :type endTime: str + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "rows": [ + { + "asset": "BNB", + "principal": "0.84624403", + "timestamp": 1555056425000, + //one of PENDING (pending to execution), CONFIRMED (successfully loaned), FAILED (execution failed, nothing happened to your account); + "status": "CONFIRMED" + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/loan', signed=True, data=params) + + def get_margin_repay_details(self, **params): + """Query repay record + + txId or startTime must be sent. txId takes precedence. + + https://binance-docs.github.io/apidocs/spot/en/#query-repay-record-user_data + + :param asset: required + :type asset: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param txId: the tranId in of the created loan + :type txId: str + :param startTime: + :type startTime: str + :param endTime: Used to uniquely identify this cancel. Automatically generated by default. + :type endTime: str + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "rows": [ + { + //Total amount repaid + "amount": "14.00000000", + "asset": "BNB", + //Interest repaid + "interest": "0.01866667", + //Principal repaid + "principal": "13.98133333", + //one of PENDING (pending to execution), CONFIRMED (successfully loaned), FAILED (execution failed, nothing happened to your account); + "status": "CONFIRMED", + "timestamp": 1563438204000, + "txId": 2970933056 + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/repay', signed=True, data=params) + + def get_cross_margin_data(self, **params): + """Query Cross Margin Fee Data (USER_DATA) + + https://binance-docs.github.io/apidocs/spot/en/#query-cross-margin-fee-data-user_data + :param vipLevel: User's current specific margin data will be returned if vipLevel is omitted + :type vipLevel: int + :param coin + :type coin: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: API response (example): + [ + { + "vipLevel": 0, + "coin": "BTC", + "transferIn": true, + "borrowable": true, + "dailyInterest": "0.00026125", + "yearlyInterest": "0.0953", + "borrowLimit": "180", + "marginablePairs": [ + "BNBBTC", + "TRXBTC", + "ETHBTC", + "BTCUSDT" + ] + } + ] + """ + return self._request_margin_api('get', 'margin/crossMarginData', signed=True, data=params) + + def get_margin_interest_history(self, **params): + """Get Interest History (USER_DATA) + + https://binance-docs.github.io/apidocs/spot/en/#get-interest-history-user_data + + :param asset: + :type asset: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param startTime: + :type startTime: str + :param endTime: + :type endTime: str + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param archived: Default: false. Set to true for archived data from 6 months ago + :type archived: bool + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "rows":[ + { + "isolatedSymbol": "BNBUSDT", // isolated symbol, will not be returned for crossed margin + "asset": "BNB", + "interest": "0.02414667", + "interestAccuredTime": 1566813600000, + "interestRate": "0.01600000", + "principal": "36.22000000", + "type": "ON_BORROW" + } + ], + "total": 1 + } + + + """ + return self._request_margin_api('get', 'margin/interestHistory', signed=True, data=params) + + def get_margin_force_liquidation_rec(self, **params): + """Get Force Liquidation Record (USER_DATA) + + https://binance-docs.github.io/apidocs/spot/en/#get-force-liquidation-record-user_data + + :param startTime: + :type startTime: str + :param endTime: + :type endTime: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param current: Currently querying page. Start from 1. Default:1 + :type current: str + :param size: Default:10 Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "rows": [ + { + "avgPrice": "0.00388359", + "executedQty": "31.39000000", + "orderId": 180015097, + "price": "0.00388110", + "qty": "31.39000000", + "side": "SELL", + "symbol": "BNBBTC", + "timeInForce": "GTC", + "isIsolated": true, + "updatedTime": 1558941374745 + } + ], + "total": 1 + } + + """ + return self._request_margin_api('get', 'margin/forceLiquidationRec', signed=True, data=params) + + def get_margin_order(self, **params): + """Query margin accounts order + + Either orderId or origClientOrderId must be sent. + + For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time. + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-order-user_data + + :param symbol: required + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param orderId: + :type orderId: str + :param origClientOrderId: + :type origClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "clientOrderId": "ZwfQzuDIGpceVhKW5DvCmO", + "cummulativeQuoteQty": "0.00000000", + "executedQty": "0.00000000", + "icebergQty": "0.00000000", + "isWorking": true, + "orderId": 213205622, + "origQty": "0.30000000", + "price": "0.00493630", + "side": "SELL", + "status": "NEW", + "stopPrice": "0.00000000", + "symbol": "BNBBTC", + "time": 1562133008725, + "timeInForce": "GTC", + "type": "LIMIT", + "updateTime": 1562133008725 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/order', signed=True, data=params) + + def get_open_margin_orders(self, **params): + """Query margin accounts open orders + + If the symbol is not sent, orders for all symbols will be returned in an array (cross-margin only). + + If querying isolated margin orders, both the isIsolated='TRUE' and symbol=symbol_name must be set. + + When all symbols are returned, the number of requests counted against the rate limiter is equal to the number + of symbols currently trading on the exchange. + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-open-order-user_data + + :param symbol: optional + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + [ + { + "clientOrderId": "qhcZw71gAkCCTv0t0k8LUK", + "cummulativeQuoteQty": "0.00000000", + "executedQty": "0.00000000", + "icebergQty": "0.00000000", + "isWorking": true, + "orderId": 211842552, + "origQty": "0.30000000", + "price": "0.00475010", + "side": "SELL", + "status": "NEW", + "stopPrice": "0.00000000", + "symbol": "BNBBTC", + "time": 1562040170089, + "timeInForce": "GTC", + "type": "LIMIT", + "updateTime": 1562040170089 + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/openOrders', signed=True, data=params) + + def get_all_margin_orders(self, **params): + """Query all margin accounts orders + + If orderId is set, it will get orders >= that orderId. Otherwise most recent orders are returned. + + For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time. + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-all-order-user_data + + :param symbol: required + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param orderId: optional + :type orderId: str + :param startTime: optional + :type startTime: str + :param endTime: optional + :type endTime: str + :param limit: Default 500; max 1000 + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + [ + { + "id": 43123876, + "price": "0.00395740", + "qty": "4.06000000", + "quoteQty": "0.01606704", + "symbol": "BNBBTC", + "time": 1556089977693 + }, + { + "id": 43123877, + "price": "0.00395740", + "qty": "0.77000000", + "quoteQty": "0.00304719", + "symbol": "BNBBTC", + "time": 1556089977693 + }, + { + "id": 43253549, + "price": "0.00428930", + "qty": "23.30000000", + "quoteQty": "0.09994069", + "symbol": "BNBBTC", + "time": 1556163963504 + } + ] + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/allOrders', signed=True, data=params) + + def get_margin_trades(self, **params): + """Query margin accounts trades + + If fromId is set, it will get orders >= that fromId. Otherwise most recent orders are returned. + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-trade-list-user_data + + :param symbol: required + :type symbol: str + :param isIsolated: set to 'TRUE' for isolated margin (default 'FALSE') + :type isIsolated: str + :param fromId: optional + :type fromId: str + :param startTime: optional + :type startTime: str + :param endTime: optional + :type endTime: str + :param limit: Default 500; max 1000 + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + [ + { + "commission": "0.00006000", + "commissionAsset": "BTC", + "id": 34, + "isBestMatch": true, + "isBuyer": false, + "isMaker": false, + "orderId": 39324, + "price": "0.02000000", + "qty": "3.00000000", + "symbol": "BNBBTC", + "time": 1561973357171 + }, { + "commission": "0.00002950", + "commissionAsset": "BTC", + "id": 32, + "isBestMatch": true, + "isBuyer": false, + "isMaker": true, + "orderId": 39319, + "price": "0.00590000", + "qty": "5.00000000", + "symbol": "BNBBTC", + "time": 1561964645345 + } + ] + + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/myTrades', signed=True, data=params) + + def get_max_margin_loan(self, **params): + """Query max borrow amount for an asset + + https://binance-docs.github.io/apidocs/spot/en/#query-max-borrow-user_data + + :param asset: required + :type asset: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "amount": "1.69248805" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/maxBorrowable', signed=True, data=params) + + def get_max_margin_transfer(self, **params): + """Query max transfer-out amount + + https://binance-docs.github.io/apidocs/spot/en/#query-max-transfer-out-amount-user_data + + :param asset: required + :type asset: str + :param isolatedSymbol: isolated symbol (if querying isolated margin) + :type isolatedSymbol: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "amount": "3.59498107" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'margin/maxTransferable', signed=True, data=params) + + def get_margin_delist_schedule(self, **params): + """Get tokens or symbols delist schedule for cross margin and isolated margin + + https://binance-docs.github.io/apidocs/spot/en/#get-tokens-or-symbols-delist-schedule-for-cross-margin-and-isolated-margin-market_data + + :param recvWindow: optional - the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + [ + { + "delistTime": 1686161202000, + "crossMarginAssets": [ + "BTC", + "USDT" + ], + "isolatedMarginSymbols": [ + "ADAUSDT", + "BNBUSDT" + ] + }, + { + "delistTime": 1686222232000, + "crossMarginAssets": [ + "ADA" + ], + "isolatedMarginSymbols": [] + } + ] + """ + return self._request_margin_api('get', '/margin/delist-schedule', signed=True, data=params) + + # Margin OCO + + def create_margin_oco_order(self, **params): + """Post a new OCO trade for margin account. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-new-oco-trade + + + :param symbol: required + :type symbol: str + :param isIsolated: for isolated margin or not, "TRUE", "FALSE",default "FALSE" + :type symbol: str + :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. + :type listClientOrderId: str + :param side: required + :type side: str + :param quantity: required + :type quantity: decimal + :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. + :type limitClientOrderId: str + :param price: required + :type price: str + :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. + :type limitIcebergQty: decimal + :param stopClientOrderId: A unique Id for the stop loss/stop loss limit leg. Automatically generated if not sent. + :type stopClientOrderId: str + :param stopPrice: required + :type stopPrice: str + :param stopLimitPrice: If provided, stopLimitTimeInForce is required. + :type stopLimitPrice: str + :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. + :type stopIcebergQty: decimal + :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. + :type stopLimitTimeInForce: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param sideEffectType: NO_SIDE_EFFECT, MARGIN_BUY, AUTO_REPAY; default NO_SIDE_EFFECT. + :type sideEffectType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "orderListId": 0, + "contingencyType": "OCO", + "listStatusType": "EXEC_STARTED", + "listOrderStatus": "EXECUTING", + "listClientOrderId": "JYVpp3F0f5CAG15DhtrqLp", + "transactionTime": 1563417480525, + "symbol": "LTCBTC", + "marginBuyBorrowAmount": "5", // will not return if no margin trade happens + "marginBuyBorrowAsset": "BTC", // will not return if no margin trade happens + "isIsolated": false, // if isolated margin + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 2, + "clientOrderId": "Kk7sqHb9J6mJWTMDVW7Vos" + }, + { + "symbol": "LTCBTC", + "orderId": 3, + "clientOrderId": "xTXKaGYd4bluPVp78IVRvl" + } + ], + "orderReports": [ + { + "symbol": "LTCBTC", + "orderId": 2, + "orderListId": 0, + "clientOrderId": "Kk7sqHb9J6mJWTMDVW7Vos", + "transactTime": 1563417480525, + "price": "0.000000", + "origQty": "0.624363", + "executedQty": "0.000000", + "cummulativeQuoteQty": "0.000000", + "status": "NEW", + "timeInForce": "GTC", + "type": "STOP_LOSS", + "side": "BUY", + "stopPrice": "0.960664" + }, + { + "symbol": "LTCBTC", + "orderId": 3, + "orderListId": 0, + "clientOrderId": "xTXKaGYd4bluPVp78IVRvl", + "transactTime": 1563417480525, + "price": "0.036435", + "origQty": "0.624363", + "executedQty": "0.000000", + "cummulativeQuoteQty": "0.000000", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT_MAKER", + "side": "BUY" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, + BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, + BinanceOrderInactiveSymbolException + + """ + return self._request_margin_api('post', 'margin/order/oco', signed=True, data=params) + + def cancel_margin_oco_order(self, **params): + """Cancel an entire Order List for a margin account. + + https://binance-docs.github.io/apidocs/spot/en/#margin-account-cancel-oco-trade + + :param symbol: required + :type symbol: str + :param isIsolated: for isolated margin or not, "TRUE", "FALSE",default "FALSE" + :type symbol: str + :param orderListId: Either orderListId or listClientOrderId must be provided + :type orderListId: int + :param listClientOrderId: Either orderListId or listClientOrderId must be provided + :type listClientOrderId: str + :param newClientOrderId: Used to uniquely identify this cancel. Automatically generated by default. + :type newClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "orderListId": 0, + "contingencyType": "OCO", + "listStatusType": "ALL_DONE", + "listOrderStatus": "ALL_DONE", + "listClientOrderId": "C3wyj4WVEktd7u9aVBRXcN", + "transactionTime": 1574040868128, + "symbol": "LTCBTC", + "isIsolated": false, // if isolated margin + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 2, + "clientOrderId": "pO9ufTiFGg3nw2fOdgeOXa" + }, + { + "symbol": "LTCBTC", + "orderId": 3, + "clientOrderId": "TXOvglzXuaubXAaENpaRCB" + } + ], + "orderReports": [ + { + "symbol": "LTCBTC", + "origClientOrderId": "pO9ufTiFGg3nw2fOdgeOXa", + "orderId": 2, + "orderListId": 0, + "clientOrderId": "unfWT8ig8i0uj6lPuYLez6", + "price": "1.00000000", + "origQty": "10.00000000", + "executedQty": "0.00000000", + "cummulativeQuoteQty": "0.00000000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "STOP_LOSS_LIMIT", + "side": "SELL", + "stopPrice": "1.00000000" + }, + { + "symbol": "LTCBTC", + "origClientOrderId": "TXOvglzXuaubXAaENpaRCB", + "orderId": 3, + "orderListId": 0, + "clientOrderId": "unfWT8ig8i0uj6lPuYLez6", + "price": "3.00000000", + "origQty": "10.00000000", + "executedQty": "0.00000000", + "cummulativeQuoteQty": "0.00000000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT_MAKER", + "side": "SELL" + } + ] + } + + """ + return self._request_margin_api('delete', 'margin/orderList', signed=True, data=params) + + def get_margin_oco_order(self, **params): + """Retrieves a specific OCO based on provided optional parameters + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-oco-user_data + + :param isIsolated: for isolated margin or not, "TRUE", "FALSE",default "FALSE" + :type symbol: str + :param symbol: mandatory for isolated margin, not supported for cross margin + :type symbol: str + :param orderListId: Either orderListId or listClientOrderId must be provided + :type orderListId: int + :param listClientOrderId: Either orderListId or listClientOrderId must be provided + :type listClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + { + "orderListId": 27, + "contingencyType": "OCO", + "listStatusType": "EXEC_STARTED", + "listOrderStatus": "EXECUTING", + "listClientOrderId": "h2USkA5YQpaXHPIrkd96xE", + "transactionTime": 1565245656253, + "symbol": "LTCBTC", + "isIsolated": false, // if isolated margin + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 4, + "clientOrderId": "qD1gy3kc3Gx0rihm9Y3xwS" + }, + { + "symbol": "LTCBTC", + "orderId": 5, + "clientOrderId": "ARzZ9I00CPM8i3NhmU9Ega" + } + ] + } + + """ + return self._request_margin_api('get', 'margin/orderList', signed=True, data=params) + + def get_open_margin_oco_orders(self, **params): + """Retrieves open OCO trades + + https://binance-docs.github.io/apidocs/spot/en/#query-margin-account-39-s-open-oco-user_data + + :param isIsolated: for isolated margin or not, "TRUE", "FALSE",default "FALSE" + :type symbol: str + :param symbol: mandatory for isolated margin, not supported for cross margin + :type symbol: str + :param fromId: If supplied, neither startTime or endTime can be provided + :type fromId: int + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: optional Default Value: 500; Max Value: 1000 + :type limit: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + [ + { + "orderListId": 29, + "contingencyType": "OCO", + "listStatusType": "EXEC_STARTED", + "listOrderStatus": "EXECUTING", + "listClientOrderId": "amEEAXryFzFwYF1FeRpUoZ", + "transactionTime": 1565245913483, + "symbol": "LTCBTC", + "isIsolated": true, // if isolated margin + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 4, + "clientOrderId": "oD7aesZqjEGlZrbtRpy5zB" + }, + { + "symbol": "LTCBTC", + "orderId": 5, + "clientOrderId": "Jr1h6xirOxgeJOUuYQS7V3" + } + ] + }, + { + "orderListId": 28, + "contingencyType": "OCO", + "listStatusType": "EXEC_STARTED", + "listOrderStatus": "EXECUTING", + "listClientOrderId": "hG7hFNxJV6cZy3Ze4AUT4d", + "transactionTime": 1565245913407, + "symbol": "LTCBTC", + "orders": [ + { + "symbol": "LTCBTC", + "orderId": 2, + "clientOrderId": "j6lFOfbmFMRjTYA7rRJ0LP" + }, + { + "symbol": "LTCBTC", + "orderId": 3, + "clientOrderId": "z0KCjOdditiLS5ekAFtK81" + } + ] + } + ] + + """ + return self._request_margin_api('get', 'margin/openOrderList', signed=True, data=params) + + # Cross-margin + + def margin_stream_get_listen_key(self): + """Start a new cross-margin data stream and return the listen key + If a stream already exists it should return the same key. + If the stream becomes invalid a new key is returned. + + Can be used to keep the stream alive. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin + + :returns: API response + + .. code-block:: python + + { + "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + res = self._request_margin_api('post', 'userDataStream', signed=False, data={}) + return res['listenKey'] + + def margin_stream_keepalive(self, listenKey): + """PING a cross-margin data stream to prevent a time out. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin + + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'listenKey': listenKey + } + return self._request_margin_api('put', 'userDataStream', signed=False, data=params) + + def margin_stream_close(self, listenKey): + """Close out a cross-margin data stream. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin + + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'listenKey': listenKey + } + return self._request_margin_api('delete', 'userDataStream', signed=False, data=params) + + # Isolated margin + + def isolated_margin_stream_get_listen_key(self, symbol): + """Start a new isolated margin data stream and return the listen key + If a stream already exists it should return the same key. + If the stream becomes invalid a new key is returned. + + Can be used to keep the stream alive. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin + + :param symbol: required - symbol for the isolated margin account + :type symbol: str + + :returns: API response + + .. code-block:: python + + { + "listenKey": "T3ee22BIYuWqmvne0HNq2A2WsFlEtLhvWCtItw6ffhhdmjifQ2tRbuKkTHhr" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'symbol': symbol + } + res = self._request_margin_api('post', 'userDataStream/isolated', signed=False, data=params) + return res['listenKey'] + + def isolated_margin_stream_keepalive(self, symbol, listenKey): + """PING an isolated margin data stream to prevent a time out. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin + + :param symbol: required - symbol for the isolated margin account + :type symbol: str + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'symbol': symbol, + 'listenKey': listenKey + } + return self._request_margin_api('put', 'userDataStream/isolated', signed=False, data=params) + + def isolated_margin_stream_close(self, symbol, listenKey): + """Close out an isolated margin data stream. + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin + + :param symbol: required - symbol for the isolated margin account + :type symbol: str + :param listenKey: required + :type listenKey: str + + :returns: API response + + .. code-block:: python + + {} + + :raises: BinanceRequestException, BinanceAPIException + + """ + params = { + 'symbol': symbol, + 'listenKey': listenKey + } + return self._request_margin_api('delete', 'userDataStream/isolated', signed=False, data=params) + + # Simple Earn Endpoints + + def get_simple_earn_flexible_product_list(self, **params): + """Get available Simple Earn flexible product list + + https://binance-docs.github.io/apidocs/spot/en/#get-simple-earn-flexible-product-list-user_data + + :param asset: optional + :type asset: str + :param current: optional - Currently querying page. Start from 1. Default:1 + :type current: int + :param size: optional - Default:10, Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "rows":[ + { + "asset": "BTC", + "latestAnnualPercentageRate": "0.05000000", + "tierAnnualPercentageRate": { + "0-5BTC": 0.05, + "5-10BTC": 0.03 + }, + "airDropPercentageRate": "0.05000000", + "canPurchase": true, + "canRedeem": true, + "isSoldOut": true, + "hot": true, + "minPurchaseAmount": "0.01000000", + "productId": "BTC001", + "subscriptionStartTime": "1646182276000", + "status": "PURCHASING" + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'simple-earn/flexible/list', signed=True, data=params) + + def get_simple_earn_locked_product_list(self, **params): + """Get available Simple Earn flexible product list + + https://binance-docs.github.io/apidocs/spot/en/#get-simple-earn-locked-product-list-user_data + + :param asset: optional + :type asset: str + :param current: optional - Currently querying page. Start from 1. Default:1 + :type current: int + :param size: optional - Default:10, Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "rows": [ + { + "projectId": "Axs*90", + "detail": { + "asset": "AXS", + "rewardAsset": "AXS", + "duration": 90, + "renewable": true, + "isSoldOut": true, + "apr": "1.2069", + "status": "CREATED", + "subscriptionStartTime": "1646182276000", + "extraRewardAsset": "BNB", + "extraRewardAPR": "0.23" + }, + "quota": { + "totalPersonalQuota": "2", + "minimum": "0.001" + } + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'simple-earn/locked/list', signed=True, data=params) + + def subscribe_simple_earn_flexible_product(self, **params): + """Subscribe to a simple earn flexible product + + https://binance-docs.github.io/apidocs/spot/en/#subscribe-locked-product-trade + + :param productId: required + :type productId: str + :param amount: required + :type amount: str + :param autoSubscribe: optional - Default True + :type autoSubscribe: bool + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "purchaseId": 40607, + "success": true + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'simple-earn/flexible/subscribe', signed=True, data=params) + + def subscribe_simple_earn_locked_product(self, **params): + """Subscribe to a simple earn locked product + + https://binance-docs.github.io/apidocs/spot/en/#subscribe-locked-product-trade + + :param productId: required + :type productId: str + :param amount: required + :type amount: str + :param autoSubscribe: optional - Default True + :type autoSubscribe: bool + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "purchaseId": 40607, + "positionId": "12345", + "success": true + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'simple-earn/locked/subscribe', signed=True, data=params) + + def redeem_simple_earn_flexible_product(self, **params): + """Redeem a simple earn flexible product + + https://binance-docs.github.io/apidocs/spot/en/#redeem-flexible-product-trade + + :param productId: required + :type productId: str + :param amount: optional + :type amount: str + :param redeemAll: optional - Default False + :type redeemAll: bool + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "redeemId": 40607, + "success": true + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'simple-earn/flexible/redeem', signed=True, data=params) + + def redeem_simple_earn_locked_product(self, **params): + """Redeem a simple earn locked product + + https://binance-docs.github.io/apidocs/spot/en/#redeem-locked-product-trade + + :param productId: required + :type productId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "redeemId": 40607, + "success": true + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'simple-earn/locked/redeem', signed=True, data=params) + + def get_simple_earn_flexible_product_position(self, **params): + """ + + https://binance-docs.github.io/apidocs/spot/en/#get-flexible-product-position-user_data + + :param asset: optional + :type asset: str + :param current: optional - Currently querying page. Start from 1. Default:1 + :type current: int + :param size: optional - Default:10, Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "rows":[ + { + "totalAmount": "75.46000000", + "tierAnnualPercentageRate": { + "0-5BTC": 0.05, + "5-10BTC": 0.03 + }, + "latestAnnualPercentageRate": "0.02599895", + "yesterdayAirdropPercentageRate": "0.02599895", + "asset": "USDT", + "airDropAsset": "BETH", + "canRedeem": true, + "collateralAmount": "232.23123213", + "productId": "USDT001", + "yesterdayRealTimeRewards": "0.10293829", + "cumulativeBonusRewards": "0.22759183", + "cumulativeRealTimeRewards": "0.22759183", + "cumulativeTotalRewards": "0.45459183", + "autoSubscribe": true + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'simple-earn/flexible/position', signed=True, data=params) + + def get_simple_earn_locked_product_position(self, **params): + """ + + https://binance-docs.github.io/apidocs/spot/en/#get-locked-product-position-user_data + + :param asset: optional + :type asset: str + :param current: optional - Currently querying page. Start from 1. Default:1 + :type current: int + :param size: optional - Default:10, Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "rows":[ + { + "positionId": "123123", + "projectId": "Axs*90", + "asset": "AXS", + "amount": "122.09202928", + "purchaseTime": "1646182276000", + "duration": "60", + "accrualDays": "4", + "rewardAsset": "AXS", + "APY": "0.23", + "isRenewable": true, + "isAutoRenew": true, + "redeemDate": "1732182276000" + } + ], + "total": 1 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'simple-earn/locked/position', signed=True, data=params) + + def get_simple_earn_account(self, **params): + """ + + https://binance-docs.github.io/apidocs/spot/en/#simple-account-user_data + + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "totalAmountInBTC": "0.01067982", + "totalAmountInUSDT": "77.13289230", + "totalFlexibleAmountInBTC": "0.00000000", + "totalFlexibleAmountInUSDT": "0.00000000", + "totalLockedInBTC": "0.01067982", + "totalLockedInUSDT": "77.13289230" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'simple-earn/account', signed=True, data=params) + + # Lending Endpoints + + def get_fixed_activity_project_list(self, **params): + """Get Fixed and Activity Project List + + https://binance-docs.github.io/apidocs/spot/en/#get-fixed-and-activity-project-list-user_data + + :param asset: optional + :type asset: str + :param type: required - "ACTIVITY", "CUSTOMIZED_FIXED" + :type type: str + :param status: optional - "ALL", "SUBSCRIBABLE", "UNSUBSCRIBABLE"; default "ALL" + :type status: str + :param sortBy: optional - "START_TIME", "LOT_SIZE", "INTEREST_RATE", "DURATION"; default "START_TIME" + :type sortBy: str + :param current: optional - Currently querying page. Start from 1. Default:1 + :type current: int + :param size: optional - Default:10, Max:100 + :type size: int + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "asset": "USDT", + "displayPriority": 1, + "duration": 90, + "interestPerLot": "1.35810000", + "interestRate": "0.05510000", + "lotSize": "100.00000000", + "lotsLowLimit": 1, + "lotsPurchased": 74155, + "lotsUpLimit": 80000, + "maxLotsPerUser": 2000, + "needKyc": False, + "projectId": "CUSDT90DAYSS001", + "projectName": "USDT", + "status": "PURCHASING", + "type": "CUSTOMIZED_FIXED", + "withAreaLimitation": False + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'lending/project/list', signed=True, data=params) + + def change_fixed_activity_to_daily_position(self, **params): + """Change Fixed/Activity Position to Daily Position + + https://binance-docs.github.io/apidocs/spot/en/#change-fixed-activity-position-to-daily-position-user_data + + """ + return self._request_margin_api('post', 'lending/positionChanged', signed=True, data=params) + + # Staking Endpoints + + def get_staking_product_list(self, **params): + """Get Staking Product List + + https://binance-docs.github.io/apidocs/spot/en/#get-staking-product-list-user_data + + """ + return self._request_margin_api('get', 'staking/productList', signed=True, data=params) + + def purchase_staking_product(self, **params): + """Purchase Staking Product + + https://binance-docs.github.io/apidocs/spot/en/#purchase-staking-product-user_data + + """ + return self._request_margin_api('post', 'staking/purchase', signed=True, data=params) + + def redeem_staking_product(self, **params): + """Redeem Staking Product + + https://binance-docs.github.io/apidocs/spot/en/#redeem-staking-product-user_data + + """ + return self._request_margin_api('post', 'staking/redeem', signed=True, data=params) + + def get_staking_position(self, **params): + """Get Staking Product Position + + https://binance-docs.github.io/apidocs/spot/en/#get-staking-product-position-user_data + + """ + return self._request_margin_api('get', 'staking/position', signed=True, data=params) + + def get_staking_purchase_history(self, **params): + """Get Staking Purchase History + + https://binance-docs.github.io/apidocs/spot/en/#get-staking-history-user_data + + """ + return self._request_margin_api('get', 'staking/purchaseRecord', signed=True, data=params) + + def set_auto_staking(self, **params): + """Set Auto Staking on Locked Staking or Locked DeFi Staking + + https://binance-docs.github.io/apidocs/spot/en/#set-auto-staking-user_data + + """ + return self._request_margin_api('post', 'staking/setAutoStaking', signed=True, data=params) + + def get_personal_left_quota(self, **params): + """Get Personal Left Quota of Staking Product + + https://binance-docs.github.io/apidocs/spot/en/#get-personal-left-quota-of-staking-product-user_data + + """ + return self._request_margin_api('get', 'staking/personalLeftQuota', signed=True, data=params) + + # US Staking Endpoints + + def get_staking_asset_us(self, **params): + """Get staking information for a supported asset (or assets) + + https://docs.binance.us/#get-staking-asset-information + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("get", "staking/asset", True, data=params) + + def stake_asset_us(self, **params): + """Stake a supported asset. + + https://docs.binance.us/#stake-asset + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("post", "staking/stake", True, data=params) + + def unstake_asset_us(self, **params): + """Unstake a staked asset + + https://docs.binance.us/#unstake-asset + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("post", "staking/unstake", True, data=params) + + def get_staking_balance_us(self, **params): + """Get staking balance + + https://docs.binance.us/#get-staking-balance + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("get", "staking/stakingBalance", True, data=params) + + def get_staking_history_us(self, **params): + """Get staking history + + https://docs.binance.us/#get-staking-history + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("get", "staking/history", True, data=params) + + def get_staking_rewards_history_us(self, **params): + """Get staking rewards history for an asset(or assets) within a given time range. + + https://docs.binance.us/#get-staking-rewards-history + + """ + assert self.tld == "us", "Endpoint only available on binance.us" + return self._request_margin_api("get", "staking/stakingRewardsHistory", True, data=params) + + # Sub Accounts + + def get_sub_account_list(self, **params): + """Query Sub-account List. + + https://binance-docs.github.io/apidocs/spot/en/#query-sub-account-list-sapi-for-master-account + + :param email: optional - Sub-account email + :type email: str + :param isFreeze: optional + :type isFreeze: str + :param page: optional - Default value: 1 + :type page: int + :param limit: optional - Default value: 1, Max value: 200 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "subAccounts":[ + { + "email":"testsub@gmail.com", + "isFreeze":false, + "createTime":1544433328000 + }, + { + "email":"virtual@oxebmvfonoemail.com", + "isFreeze":false, + "createTime":1544433328000 + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/list', True, data=params) + + def get_sub_account_transfer_history(self, **params): + """Query Sub-account Transfer History. + + https://binance-docs.github.io/apidocs/spot/en/#query-sub-account-spot-asset-transfer-history-sapi-for-master-account + + :param fromEmail: optional + :type fromEmail: str + :param toEmail: optional + :type toEmail: str + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param page: optional - Default value: 1 + :type page: int + :param limit: optional - Default value: 500 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "from":"aaa@test.com", + "to":"bbb@test.com", + "asset":"BTC", + "qty":"10", + "status": "SUCCESS", + "tranId": 6489943656, + "time":1544433328000 + }, + { + "from":"bbb@test.com", + "to":"ccc@test.com", + "asset":"ETH", + "qty":"2", + "status": "SUCCESS", + "tranId": 6489938713, + "time":1544433328000 + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/sub/transfer/history', True, data=params) + + def get_sub_account_futures_transfer_history(self, **params): + """Query Sub-account Futures Transfer History. + + https://binance-docs.github.io/apidocs/spot/en/#query-sub-account-futures-asset-transfer-history-for-master-account + + :param email: required + :type email: str + :param futuresType: required + :type futuresType: int + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param page: optional + :type page: int + :param limit: optional + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "success":true, + "futuresType": 2, + "transfers":[ + { + "from":"aaa@test.com", + "to":"bbb@test.com", + "asset":"BTC", + "qty":"1", + "time":1544433328000 + }, + { + "from":"bbb@test.com", + "to":"ccc@test.com", + "asset":"ETH", + "qty":"2", + "time":1544433328000 + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/futures/internalTransfer', True, data=params) + + def create_sub_account_futures_transfer(self, **params): + """Execute sub-account Futures transfer + + https://github.com/binance-exchange/binance-official-api-docs/blob/9dbe0e961b80557bb19708a707c7fad08842b28e/wapi-api.md#sub-account-transferfor-master-account + + :param fromEmail: required - Sender email + :type fromEmail: str + :param toEmail: required - Recipient email + :type toEmail: str + :param futuresType: required + :type futuresType: int + :param asset: required + :type asset: str + :param amount: required + :type amount: decimal + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "success":true, + "txnId":"2934662589" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/futures/internalTransfer', True, data=params) + + def get_sub_account_assets(self, **params): + """Fetch sub-account assets + + https://binance-docs.github.io/apidocs/spot/en/#query-sub-account-assets-sapi-for-master-account + + :param email: required + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "balances":[ + { + "asset":"ADA", + "free":10000, + "locked":0 + }, + { + "asset":"BNB", + "free":10003, + "locked":0 + }, + { + "asset":"BTC", + "free":11467.6399, + "locked":0 + }, + { + "asset":"ETH", + "free":10004.995, + "locked":0 + }, + { + "asset":"USDT", + "free":11652.14213, + "locked":0 + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/assets', True, data=params, version=4) + + def query_subaccount_spot_summary(self, **params): + """Query Sub-account Spot Assets Summary (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#query-sub-account-spot-assets-summary-for-master-account + + :param email: optional - Sub account email + :type email: str + :param page: optional - default 1 + :type page: int + :param size: optional - default 10, max 20 + :type size: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "totalCount":2, + "masterAccountTotalAsset": "0.23231201", + "spotSubUserAssetBtcVoList":[ + { + "email":"sub123@test.com", + "totalAsset":"9999.00000000" + }, + { + "email":"test456@test.com", + "totalAsset":"0.00000000" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/spotSummary', True, data=params) + + def get_subaccount_deposit_address(self, **params): + """Get Sub-account Deposit Address (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-sub-account-deposit-address-for-master-account + + :param email: required - Sub account email + :type email: str + :param coin: required + :type coin: str + :param network: optional + :type network: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "address":"TDunhSa7jkTNuKrusUTU1MUHtqXoBPKETV", + "coin":"USDT", + "tag":"", + "url":"https://tronscan.org/#/address/TDunhSa7jkTNuKrusUTU1MUHtqXoBPKETV" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'capital/deposit/subAddress', True, data=params) + + def get_subaccount_deposit_history(self, **params): + """Get Sub-account Deposit History (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-sub-account-deposit-address-for-master-account + + :param email: required - Sub account email + :type email: str + :param coin: optional + :type coin: str + :param status: optional - (0:pending,6: credited but cannot withdraw, 1:success) + :type status: int + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: optional + :type limit: int + :param offset: optional - default:0 + :type offset: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "amount":"0.00999800", + "coin":"PAXG", + "network":"ETH", + "status":1, + "address":"0x788cabe9236ce061e5a892e1a59395a81fc8d62c", + "addressTag":"", + "txId":"0xaad4654a3234aa6118af9b4b335f5ae81c360b2394721c019b5d1e75328b09f3", + "insertTime":1599621997000, + "transferType":0, + "confirmTimes":"12/12" + }, + { + "amount":"0.50000000", + "coin":"IOTA", + "network":"IOTA", + "status":1, + "address":"SIZ9VLMHWATXKV99LH99CIGFJFUMLEHGWVZVNNZXRJJVWBPHYWPPBOSDORZ9EQSHCZAMPVAPGFYQAUUV9DROOXJLNW", + "addressTag":"", + "txId":"ESBFVQUTPIWQNJSPXFNHNYHSQNTGKRVKPRABQWTAXCDWOAKDKYWPTVG9BGXNVNKTLEJGESAVXIKIZ9999", + "insertTime":1599620082000, + "transferType":0, + "confirmTimes":"1/1" + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'capital/deposit/subHisrec', True, data=params) + + def get_subaccount_futures_margin_status(self, **params): + """Get Sub-account's Status on Margin/Futures (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-sub-account-39-s-status-on-margin-futures-for-master-account + + :param email: optional - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "email":"123@test.com", // user email + "isSubUserEnabled": true, // true or false + "isUserActive": true, // true or false + "insertTime": 1570791523523 // sub account create time + "isMarginEnabled": true, // true or false for margin + "isFutureEnabled": true // true or false for futures. + "mobile": 1570791523523 // user mobile number + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/status', True, data=params) + + def enable_subaccount_margin(self, **params): + """Enable Margin for Sub-account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#enable-margin-for-sub-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + + "email":"123@test.com", + + "isMarginEnabled": true + + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/margin/enable', True, data=params) + + def get_subaccount_margin_details(self, **params): + """Get Detail on Sub-account's Margin Account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-detail-on-sub-account-39-s-margin-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "email":"123@test.com", + "marginLevel": "11.64405625", + "totalAssetOfBtc": "6.82728457", + "totalLiabilityOfBtc": "0.58633215", + "totalNetAssetOfBtc": "6.24095242", + "marginTradeCoeffVo": + { + "forceLiquidationBar": "1.10000000", // Liquidation margin ratio + "marginCallBar": "1.50000000", // Margin call margin ratio + "normalBar": "2.00000000" // Initial margin ratio + }, + "marginUserAssetVoList": [ + { + "asset": "BTC", + "borrowed": "0.00000000", + "free": "0.00499500", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00499500" + }, + { + "asset": "BNB", + "borrowed": "201.66666672", + "free": "2346.50000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "2144.83333328" + }, + { + "asset": "ETH", + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000" + }, + { + "asset": "USDT", + "borrowed": "0.00000000", + "free": "0.00000000", + "interest": "0.00000000", + "locked": "0.00000000", + "netAsset": "0.00000000" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/margin/account', True, data=params) + + def get_subaccount_margin_summary(self, **params): + """Get Summary of Sub-account's Margin Account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-summary-of-sub-account-39-s-margin-account-for-master-account + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "totalAssetOfBtc": "4.33333333", + "totalLiabilityOfBtc": "2.11111112", + "totalNetAssetOfBtc": "2.22222221", + "subAccountList":[ + { + "email":"123@test.com", + "totalAssetOfBtc": "2.11111111", + "totalLiabilityOfBtc": "1.11111111", + "totalNetAssetOfBtc": "1.00000000" + }, + { + "email":"345@test.com", + "totalAssetOfBtc": "2.22222222", + "totalLiabilityOfBtc": "1.00000001", + "totalNetAssetOfBtc": "1.22222221" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/margin/accountSummary', True, data=params) + + def enable_subaccount_futures(self, **params): + """Enable Futures for Sub-account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#enable-futures-for-sub-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + + "email":"123@test.com", + + "isFuturesEnabled": true // true or false + + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/futures/enable', True, data=params) + + def get_subaccount_futures_details(self, **params): + """Get Detail on Sub-account's Futures Account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-detail-on-sub-account-39-s-futures-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "email": "abc@test.com", + "asset": "USDT", + "assets":[ + { + "asset": "USDT", + "initialMargin": "0.00000000", + "maintenanceMargin": "0.00000000", + "marginBalance": "0.88308000", + "maxWithdrawAmount": "0.88308000", + "openOrderInitialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "unrealizedProfit": "0.00000000", + "walletBalance": "0.88308000" + } + ], + "canDeposit": true, + "canTrade": true, + "canWithdraw": true, + "feeTier": 2, + "maxWithdrawAmount": "0.88308000", + "totalInitialMargin": "0.00000000", + "totalMaintenanceMargin": "0.00000000", + "totalMarginBalance": "0.88308000", + "totalOpenOrderInitialMargin": "0.00000000", + "totalPositionInitialMargin": "0.00000000", + "totalUnrealizedProfit": "0.00000000", + "totalWalletBalance": "0.88308000", + "updateTime": 1576756674610 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/futures/account', True, data=params, version=2) + + def get_subaccount_futures_summary(self, **params): + """Get Summary of Sub-account's Futures Account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-summary-of-sub-account-39-s-futures-account-for-master-account + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "totalInitialMargin": "9.83137400", + "totalMaintenanceMargin": "0.41568700", + "totalMarginBalance": "23.03235621", + "totalOpenOrderInitialMargin": "9.00000000", + "totalPositionInitialMargin": "0.83137400", + "totalUnrealizedProfit": "0.03219710", + "totalWalletBalance": "22.15879444", + "asset": "USDT", + "subAccountList":[ + { + "email": "123@test.com", + "totalInitialMargin": "9.00000000", + "totalMaintenanceMargin": "0.00000000", + "totalMarginBalance": "22.12659734", + "totalOpenOrderInitialMargin": "9.00000000", + "totalPositionInitialMargin": "0.00000000", + "totalUnrealizedProfit": "0.00000000", + "totalWalletBalance": "22.12659734", + "asset": "USDT" + }, + { + "email": "345@test.com", + "totalInitialMargin": "0.83137400", + "totalMaintenanceMargin": "0.41568700", + "totalMarginBalance": "0.90575887", + "totalOpenOrderInitialMargin": "0.00000000", + "totalPositionInitialMargin": "0.83137400", + "totalUnrealizedProfit": "0.03219710", + "totalWalletBalance": "0.87356177", + "asset": "USDT" + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/futures/accountSummary', True, data=params, version=2) + + def get_subaccount_futures_positionrisk(self, **params): + """Get Futures Position-Risk of Sub-account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#get-futures-position-risk-of-sub-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "entryPrice": "9975.12000", + "leverage": "50", // current initial leverage + "maxNotional": "1000000", // notional value limit of current initial leverage + "liquidationPrice": "7963.54", + "markPrice": "9973.50770517", + "positionAmount": "0.010", + "symbol": "BTCUSDT", + "unrealizedProfit": "-0.01612295" + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/futures/positionRisk', True, data=params, version=2) + + def make_subaccount_futures_transfer(self, **params): + """Futures Transfer for Sub-account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#futures-transfer-for-sub-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param amount: required - The amount to be transferred + :type amount: float + :param type: required - 1: transfer from subaccount's spot account to its USDT-margined futures account + 2: transfer from subaccount's USDT-margined futures account to its spot account + 3: transfer from subaccount's spot account to its COIN-margined futures account + 4: transfer from subaccount's COIN-margined futures account to its spot account + :type type: int + + :returns: API response + + .. code-block:: python + + { + "txnId":"2966662589" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/futures/transfer', True, data=params) + + def make_subaccount_margin_transfer(self, **params): + """Margin Transfer for Sub-account (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#margin-transfer-for-sub-account-for-master-account + + :param email: required - Sub account email + :type email: str + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param amount: required - The amount to be transferred + :type amount: float + :param type: required - 1: transfer from subaccount's spot account to margin account + 2: transfer from subaccount's margin account to its spot account + :type type: int + + :returns: API response + + .. code-block:: python + + { + "txnId":"2966662589" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/margin/transfer', True, data=params) + + def make_subaccount_to_subaccount_transfer(self, **params): + """Transfer to Sub-account of Same Master (For Sub-account) + + https://binance-docs.github.io/apidocs/spot/en/#transfer-to-sub-account-of-same-master-for-sub-account + + :param toEmail: required - Sub account email + :type toEmail: str + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param amount: required - The amount to be transferred + :type amount: float + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "txnId":"2966662589" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/transfer/subToSub', True, data=params) + + def make_subaccount_to_master_transfer(self, **params): + """Transfer to Master (For Sub-account) + + https://binance-docs.github.io/apidocs/spot/en/#transfer-to-master-for-sub-account + + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param amount: required - The amount to be transferred + :type amount: float + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "txnId":"2966662589" + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/transfer/subToMaster', True, data=params) + + def get_subaccount_transfer_history(self, **params): + """Sub-account Transfer History (For Sub-account) + + https://binance-docs.github.io/apidocs/spot/en/#transfer-to-master-for-sub-account + + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param type: optional - 1: transfer in, 2: transfer out + :type type: int + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: optional - Default 500 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "counterParty":"master", + "email":"master@test.com", + "type":1, // 1 for transfer in, 2 for transfer out + "asset":"BTC", + "qty":"1", + "status":"SUCCESS", + "tranId":11798835829, + "time":1544433325000 + }, + { + "counterParty":"subAccount", + "email":"sub2@test.com", + "type":2, + "asset":"ETH", + "qty":"2", + "status":"SUCCESS", + "tranId":11798829519, + "time":1544433326000 + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/transfer/subUserHistory', True, data=params) + + def make_subaccount_universal_transfer(self, **params): + """Universal Transfer (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#universal-transfer-for-master-account + + :param fromEmail: optional + :type fromEmail: str + :param toEmail: optional + :type toEmail: str + :param fromAccountType: required - "SPOT","USDT_FUTURE","COIN_FUTURE" + :type fromAccountType: str + :param toAccountType: required - "SPOT","USDT_FUTURE","COIN_FUTURE" + :type toAccountType: str + :param asset: required - The asset being transferred, e.g., USDT + :type asset: str + :param amount: required + :type amount: float + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "tranId":11945860693 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'sub-account/universalTransfer', True, data=params) + + def get_universal_transfer_history(self, **params): + """Universal Transfer (For Master Account) + + https://binance-docs.github.io/apidocs/spot/en/#query-universal-transfer-history + + :param fromEmail: optional + :type fromEmail: str + :param toEmail: optional + :type toEmail: str + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param page: optional + :type page: int + :param limit: optional + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + [ + { + "tranId":11945860693, + "fromEmail":"master@test.com", + "toEmail":"subaccount1@test.com", + "asset":"BTC", + "amount":"0.1", + "fromAccountType":"SPOT", + "toAccountType":"COIN_FUTURE", + "status":"SUCCESS", + "createTimeStamp":1544433325000 + }, + { + "tranId":11945857955, + "fromEmail":"master@test.com", + "toEmail":"subaccount2@test.com", + "asset":"ETH", + "amount":"0.2", + "fromAccountType":"SPOT", + "toAccountType":"USDT_FUTURE", + "status":"SUCCESS", + "createTimeStamp":1544433326000 + } + ] + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'sub-account/universalTransfer', True, data=params) + + # Futures API + + def futures_ping(self): + """Test connectivity to the Rest API + + https://binance-docs.github.io/apidocs/futures/en/#test-connectivity + + """ + return self._request_futures_api('get', 'ping') + + def futures_time(self): + """Test connectivity to the Rest API and get the current server time. + + https://binance-docs.github.io/apidocs/futures/en/#check-server-time + + """ + return self._request_futures_api('get', 'time') + + def futures_exchange_info(self): + """Current exchange trading rules and symbol information + + https://binance-docs.github.io/apidocs/futures/en/#exchange-information-market_data + + """ + return self._request_futures_api('get', 'exchangeInfo') + + def futures_order_book(self, **params): + """Get the Order Book for the market + + https://binance-docs.github.io/apidocs/futures/en/#order-book-market_data + + """ + return self._request_futures_api('get', 'depth', data=params) + + def futures_recent_trades(self, **params): + """Get recent trades (up to last 500). + + https://binance-docs.github.io/apidocs/futures/en/#recent-trades-list-market_data + + """ + return self._request_futures_api('get', 'trades', data=params) + + def futures_historical_trades(self, **params): + """Get older market historical trades. + + https://binance-docs.github.io/apidocs/futures/en/#old-trades-lookup-market_data + + """ + return self._request_futures_api('get', 'historicalTrades', data=params) + + def futures_aggregate_trades(self, **params): + """Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same + price will have the quantity aggregated. + + https://binance-docs.github.io/apidocs/futures/en/#compressed-aggregate-trades-list-market_data + + """ + return self._request_futures_api('get', 'aggTrades', data=params) + + def futures_klines(self, **params): + """Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. + + https://binance-docs.github.io/apidocs/futures/en/#kline-candlestick-data-market_data + + """ + return self._request_futures_api('get', 'klines', data=params) + + def futures_continous_klines(self, **params): + """Kline/candlestick bars for a specific contract type. Klines are uniquely identified by their open time. + + https://binance-docs.github.io/apidocs/futures/en/#continuous-contract-kline-candlestick-data + + """ + return self._request_futures_api('get', 'continuousKlines', data=params) + + def futures_historical_klines(self, symbol, interval, start_str, end_str=None, limit=500): + """Get historical futures klines from Binance + + :param symbol: Name of symbol pair e.g. BNBBTC + :type symbol: str + :param interval: Binance Kline interval + :type interval: str + :param start_str: Start date string in UTC format or timestamp in milliseconds + :type start_str: str|int + :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) + :type end_str: str|int + :param limit: Default 500; max 1000. + :type limit: int + + :return: list of OHLCV values (Open time, Open, High, Low, Close, Volume, Close time, Quote asset volume, Number of trades, Taker buy base asset volume, Taker buy quote asset volume, Ignore) + + """ + return self._historical_klines(symbol, interval, start_str, end_str=end_str, limit=limit, klines_type=HistoricalKlinesType.FUTURES) + + def futures_historical_klines_generator(self, symbol, interval, start_str, end_str=None): + """Get historical futures klines generator from Binance + + :param symbol: Name of symbol pair e.g. BNBBTC + :type symbol: str + :param interval: Binance Kline interval + :type interval: str + :param start_str: Start date string in UTC format or timestamp in milliseconds + :type start_str: str|int + :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) + :type end_str: str|int + + :return: generator of OHLCV values + + """ + + return self._historical_klines_generator(symbol, interval, start_str, end_str=end_str, klines_type=HistoricalKlinesType.FUTURES) + + def futures_mark_price(self, **params): + """Get Mark Price and Funding Rate + + https://binance-docs.github.io/apidocs/futures/en/#mark-price-market_data + + """ + return self._request_futures_api('get', 'premiumIndex', data=params) + + def futures_funding_rate(self, **params): + """Get funding rate history + + https://binance-docs.github.io/apidocs/futures/en/#get-funding-rate-history-market_data + + """ + return self._request_futures_api('get', 'fundingRate', data=params) + + def futures_top_longshort_account_ratio(self, **params): + """Get present long to short ratio for top accounts of a specific symbol. + + https://binance-docs.github.io/apidocs/futures/en/#top-trader-long-short-ratio-accounts-market_data + """ + return self._request_futures_data_api('get', 'topLongShortAccountRatio', data=params) + + def futures_top_longshort_position_ratio(self, **params): + """Get present long to short ratio for top positions of a specific symbol. + + https://binance-docs.github.io/apidocs/futures/en/#top-trader-long-short-ratio-positions + """ + return self._request_futures_data_api('get', 'topLongShortPositionRatio', data=params) + + def futures_global_longshort_ratio(self, **params): + """Get present global long to short ratio of a specific symbol. + + https://binance-docs.github.io/apidocs/futures/en/#long-short-ratio + """ + return self._request_futures_data_api('get', 'globalLongShortAccountRatio', data=params) + + def futures_ticker(self, **params): + """24 hour rolling window price change statistics. + + https://binance-docs.github.io/apidocs/futures/en/#24hr-ticker-price-change-statistics-market_data + + """ + return self._request_futures_api('get', 'ticker/24hr', data=params) + + def futures_symbol_ticker(self, **params): + """Latest price for a symbol or symbols. + + https://binance-docs.github.io/apidocs/futures/en/#symbol-price-ticker-market_data + + """ + return self._request_futures_api('get', 'ticker/price', data=params) + + def futures_orderbook_ticker(self, **params): + """Best price/qty on the order book for a symbol or symbols. + + https://binance-docs.github.io/apidocs/futures/en/#symbol-order-book-ticker-market_data + + """ + return self._request_futures_api('get', 'ticker/bookTicker', data=params) + + def futures_liquidation_orders(self, **params): + """Get all liquidation orders + + https://binance-docs.github.io/apidocs/futures/en/#get-all-liquidation-orders-market_data + + """ + return self._request_futures_api('get', 'forceOrders', signed=True, data=params) + + def futures_api_trading_status(self, **params): + """Get quantitative trading rules for order placement, such as Unfilled Ratio (UFR), Good-Til-Canceled Ratio (GCR), + Immediate-or-Cancel (IOC) & Fill-or-Kill (FOK) Expire Ratio (IFER), among others. + https://www.binance.com/en/support/faq/binance-futures-trading-quantitative-rules-4f462ebe6ff445d4a170be7d9e897272 + + https://binance-docs.github.io/apidocs/futures/en/#futures-trading-quantitative-rules-indicators-user_data + + :param symbol: optional + :type symbol: str + + :returns: API response + + .. code-block:: python + + { + "indicators": { // indicator: quantitative rules indicators, value: user's indicators value, triggerValue: trigger indicator value threshold of quantitative rules. + "BTCUSDT": [ + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "UFR", // Unfilled Ratio (UFR) + "value": 0.05, // Current value + "triggerValue": 0.995 // Trigger value + }, + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "IFER", // IOC/FOK Expiration Ratio (IFER) + "value": 0.99, // Current value + "triggerValue": 0.99 // Trigger value + }, + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "GCR", // GTC Cancellation Ratio (GCR) + "value": 0.99, // Current value + "triggerValue": 0.99 // Trigger value + }, + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "DR", // Dust Ratio (DR) + "value": 0.99, // Current value + "triggerValue": 0.99 // Trigger value + } + ], + "ETHUSDT": [ + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "UFR", + "value": 0.05, + "triggerValue": 0.995 + }, + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "IFER", + "value": 0.99, + "triggerValue": 0.99 + }, + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "GCR", + "value": 0.99, + "triggerValue": 0.99 + } + { + "isLocked": true, + "plannedRecoverTime": 1545741270000, + "indicator": "DR", + "value": 0.99, + "triggerValue": 0.99 + } + ] + }, + "updateTime": 1545741270000 + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_futures_api('get', 'apiTradingStatus', signed=True, data=params) + + def futures_commission_rate(self, **params): + """Get Futures commission rate + + https://binance-docs.github.io/apidocs/futures/en/#user-commission-rate-user_data + + :param symbol: required + :type symbol: str + + :returns: API response + + .. code-block:: python + + { + "symbol": "BTCUSDT", + "makerCommissionRate": "0.0002", // 0.02% + "takerCommissionRate": "0.0004" // 0.04% + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_futures_api('get', 'commissionRate', signed=True, data=params) + + def futures_adl_quantile_estimate(self, **params): + """Get Position ADL Quantile Estimate + + https://binance-docs.github.io/apidocs/futures/en/#position-adl-quantile-estimation-user_data + + """ + return self._request_futures_api('get', 'adlQuantile', signed=True, data=params) + + def futures_open_interest(self, **params): + """Get present open interest of a specific symbol. + + https://binance-docs.github.io/apidocs/futures/en/#open-interest + + """ + return self._request_futures_api('get', 'openInterest', data=params) + + def futures_index_info(self, **params): + """Get index_info + + https://binance-docs.github.io/apidocs/futures/en/#indexInfo + + """ + return self._request_futures_api('get', 'indexInfo', data=params) + + def futures_open_interest_hist(self, **params): + """Get open interest statistics of a specific symbol. + + https://binance-docs.github.io/apidocs/futures/en/#open-interest-statistics + + """ + return self._request_futures_data_api('get', 'openInterestHist', data=params) + + def futures_leverage_bracket(self, **params): + """Notional and Leverage Brackets + + https://binance-docs.github.io/apidocs/futures/en/#notional-and-leverage-brackets-market_data + + """ + return self._request_futures_api('get', 'leverageBracket', True, data=params) + + def futures_account_transfer(self, **params): + """Execute transfer between spot account and futures account. + + https://binance-docs.github.io/apidocs/futures/en/#new-future-account-transfer + + """ + return self._request_margin_api('post', 'futures/transfer', True, data=params) + + def transfer_history(self, **params): + """Get future account transaction history list + + https://binance-docs.github.io/apidocs/futures/en/#get-future-account-transaction-history-list-user_data + + """ + return self._request_margin_api('get', 'futures/transfer', True, data=params) + + def futures_loan_borrow_history(self, **params): + return self._request_margin_api('get', 'futures/loan/borrow/history', True, data=params) + + def futures_loan_repay_history(self, **params): + return self._request_margin_api('get', 'futures/loan/repay/history', True, data=params) + + def futures_loan_wallet(self, **params): + return self._request_margin_api('get', 'futures/loan/wallet', True, data=params, version=2) + + def futures_cross_collateral_adjust_history(self, **params): + return self._request_margin_api('get', 'futures/loan/adjustCollateral/history', True, data=params) + + def futures_cross_collateral_liquidation_history(self, **params): + return self._request_margin_api('get', 'futures/loan/liquidationHistory', True, data=params) + + def futures_loan_interest_history(self, **params): + return self._request_margin_api('get', 'futures/loan/interestHistory', True, data=params) + + def futures_create_order(self, **params): + """Send in a new order. + + https://binance-docs.github.io/apidocs/futures/en/#new-order-trade + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_futures_api('post', 'order', True, data=params) + + def futures_modify_order(self, **params): + """Modify an existing order. Currently only LIMIT order modification is supported. + + https://binance-docs.github.io/apidocs/futures/en/#modify-order-trade + + """ + return self._request_futures_api('put', 'order', True, data=params) + + def futures_create_test_order(self, **params): + """Testing order request, this order will not be submitted to matching engine + + https://binance-docs.github.io/apidocs/futures/en/#test-order-trade + + """ + return self._request_futures_api('post', 'order/test', True, data=params) + + def futures_place_batch_order(self, **params): + """Send in new orders. + + https://binance-docs.github.io/apidocs/futures/en/#place-multiple-orders-trade + + To avoid modifying the existing signature generation and parameter order logic, + the url encoding is done on the special query param, batchOrders, in the early stage. + + """ + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace('%27', '%22') + params['batchOrders'] = query_string[12:] + return self._request_futures_api('post', 'batchOrders', True, data=params) + + def futures_get_order(self, **params): + """Check an order's status. + + https://binance-docs.github.io/apidocs/futures/en/#query-order-user_data + + """ + return self._request_futures_api('get', 'order', True, data=params) + + def futures_get_open_orders(self, **params): + """Get all open orders on a symbol. + + https://binance-docs.github.io/apidocs/futures/en/#current-open-orders-user_data + + """ + return self._request_futures_api('get', 'openOrders', True, data=params) + + def futures_get_all_orders(self, **params): + """Get all futures account orders; active, canceled, or filled. + + https://binance-docs.github.io/apidocs/futures/en/#all-orders-user_data + + """ + return self._request_futures_api('get', 'allOrders', True, data=params) + + def futures_cancel_order(self, **params): + """Cancel an active futures order. + + https://binance-docs.github.io/apidocs/futures/en/#cancel-order-trade + + """ + return self._request_futures_api('delete', 'order', True, data=params) + + def futures_cancel_all_open_orders(self, **params): + """Cancel all open futures orders + + https://binance-docs.github.io/apidocs/futures/en/#cancel-all-open-orders-trade + + """ + return self._request_futures_api('delete', 'allOpenOrders', True, data=params) + + def futures_cancel_orders(self, **params): + """Cancel multiple futures orders + + https://binance-docs.github.io/apidocs/futures/en/#cancel-multiple-orders-trade + + """ + return self._request_futures_api('delete', 'batchOrders', True, data=params) + + def futures_countdown_cancel_all(self, **params): + """Cancel all open orders of the specified symbol at the end of the specified countdown. + + https://binance-docs.github.io/apidocs/futures/en/#auto-cancel-all-open-orders-trade + + :param symbol: required + :type symbol: str + :param countdownTime: required + :type countdownTime: int + :param recvWindow: optional - the number of milliseconds the request is valid for + :type recvWindow: int + + :returns: API response + + .. code-block:: python + { + "symbol": "BTCUSDT", + "countdownTime": "100000" + } + + """ + return self._request_futures_api('post', 'countdownCancelAll', True, data=params) + + def futures_account_balance(self, **params): + """Get futures account balance + + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Futures-Account-Balance-V3 + + """ + return self._request_futures_api('get', 'balance', True, 3, data=params) + + def futures_account(self, **params): + """Get current account information. + + https://binance-docs.github.io/apidocs/futures/en/#account-information-user_data + + """ + return self._request_futures_api('get', 'account', True, 2, data=params) + + def futures_change_leverage(self, **params): + """Change user's initial leverage of specific symbol market + + https://binance-docs.github.io/apidocs/futures/en/#change-initial-leverage-trade + + """ + return self._request_futures_api('post', 'leverage', True, data=params) + + def futures_change_margin_type(self, **params): + """Change the margin type for a symbol + + https://binance-docs.github.io/apidocs/futures/en/#change-margin-type-trade + + """ + return self._request_futures_api('post', 'marginType', True, data=params) + + def futures_change_position_margin(self, **params): + """Change the position margin for a symbol + + https://binance-docs.github.io/apidocs/futures/en/#modify-isolated-position-margin-trade + + """ + return self._request_futures_api('post', 'positionMargin', True, data=params) + + def futures_position_margin_history(self, **params): + """Get position margin change history + + https://binance-docs.github.io/apidocs/futures/en/#get-postion-margin-change-history-trade + + """ + return self._request_futures_api('get', 'positionMargin/history', True, data=params) + + def futures_position_information(self, **params): + """Get position information + + https://binance-docs.github.io/apidocs/futures/en/#position-information-user_data + + """ + return self._request_futures_api('get', 'positionRisk', True, 3, data=params) + + def futures_account_trades(self, **params): + """Get trades for the authenticated account and symbol. + + https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data + + """ + return self._request_futures_api('get', 'userTrades', True, data=params) + + def futures_income_history(self, **params): + """Get income history for authenticated account + + https://binance-docs.github.io/apidocs/futures/en/#get-income-history-user_data + + """ + return self._request_futures_api('get', 'income', True, data=params) + + def futures_change_position_mode(self, **params): + """Change position mode for authenticated account + + https://binance-docs.github.io/apidocs/futures/en/#change-position-mode-trade + + """ + return self._request_futures_api('post', 'positionSide/dual', True, data=params) + + def futures_get_position_mode(self, **params): + """Get position mode for authenticated account + + https://binance-docs.github.io/apidocs/futures/en/#get-current-position-mode-user_data + + """ + return self._request_futures_api('get', 'positionSide/dual', True, data=params) + + def futures_change_multi_assets_mode(self, multiAssetsMargin: bool): + """Change user's Multi-Assets mode (Multi-Assets Mode or Single-Asset Mode) on Every symbol + + https://binance-docs.github.io/apidocs/futures/en/#change-multi-assets-mode-trade + + """ + params = { + 'multiAssetsMargin': 'true' if multiAssetsMargin else 'false' + } + return self._request_futures_api('post', 'multiAssetsMargin', True, data=params) + + def futures_get_multi_assets_mode(self): + """Get user's Multi-Assets mode (Multi-Assets Mode or Single-Asset Mode) on Every symbol + + https://binance-docs.github.io/apidocs/futures/en/#get-current-multi-assets-mode-user_data + + """ + return self._request_futures_api('get', 'multiAssetsMargin', True, data={}) + + def futures_stream_get_listen_key(self): + res = self._request_futures_api('post', 'listenKey', signed=False, data={}) + return res['listenKey'] + + def futures_stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return self._request_futures_api('put', 'listenKey', signed=False, data=params) + + def futures_stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return self._request_futures_api('delete', 'listenKey', signed=False, data=params) + + # new methods + def futures_account_config(self, **params): + return self._request_futures_api('get', 'accountConfig', signed=True, version=1, data=params) + + def futures_symbol_config(self, **params): + return self._request_futures_api('get', 'symbolConfig', signed=True, version=1, data=params) + + # COIN Futures API + def futures_coin_ping(self): + """Test connectivity to the Rest API + + https://binance-docs.github.io/apidocs/delivery/en/#test-connectivity + + """ + return self._request_futures_coin_api("get", "ping") + + def futures_coin_time(self): + """Test connectivity to the Rest API and get the current server time. + + https://binance-docs.github.io/apidocs/delivery/en/#check-server-time + + """ + return self._request_futures_coin_api("get", "time") + + def futures_coin_exchange_info(self): + """Current exchange trading rules and symbol information + + https://binance-docs.github.io/apidocs/delivery/en/#exchange-information + + """ + return self._request_futures_coin_api("get", "exchangeInfo") + + def futures_coin_order_book(self, **params): + """Get the Order Book for the market + + https://binance-docs.github.io/apidocs/delivery/en/#order-book + + """ + return self._request_futures_coin_api("get", "depth", data=params) + + def futures_coin_recent_trades(self, **params): + """Get recent trades (up to last 500). + + https://binance-docs.github.io/apidocs/delivery/en/#recent-trades-list + + """ + return self._request_futures_coin_api("get", "trades", data=params) + + def futures_coin_historical_trades(self, **params): + """Get older market historical trades. + + https://binance-docs.github.io/apidocs/delivery/en/#old-trades-lookup-market_data + + """ + return self._request_futures_coin_api("get", "historicalTrades", data=params) + + def futures_coin_aggregate_trades(self, **params): + """Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same + price will have the quantity aggregated. + + https://binance-docs.github.io/apidocs/delivery/en/#compressed-aggregate-trades-list + + """ + return self._request_futures_coin_api("get", "aggTrades", data=params) + + def futures_coin_klines(self, **params): + """Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. + + https://binance-docs.github.io/apidocs/delivery/en/#kline-candlestick-data + + """ + return self._request_futures_coin_api("get", "klines", data=params) + + def futures_coin_continous_klines(self, **params): + """Kline/candlestick bars for a specific contract type. Klines are uniquely identified by their open time. + + https://binance-docs.github.io/apidocs/delivery/en/#continuous-contract-kline-candlestick-data + + """ + return self._request_futures_coin_api("get", "continuousKlines", data=params) + + def futures_coin_index_price_klines(self, **params): + """Kline/candlestick bars for the index price of a pair.. + + https://binance-docs.github.io/apidocs/delivery/en/#index-price-kline-candlestick-data + + """ + return self._request_futures_coin_api("get", "indexPriceKlines", data=params) + + def futures_coin_mark_price_klines(self, **params): + """Kline/candlestick bars for the index price of a pair.. + + https://binance-docs.github.io/apidocs/delivery/en/#mark-price-kline-candlestick-data + + """ + return self._request_futures_coin_api("get", "markPriceKlines", data=params) + + def futures_coin_mark_price(self, **params): + """Get Mark Price and Funding Rate + + https://binance-docs.github.io/apidocs/delivery/en/#index-price-and-mark-price + + """ + return self._request_futures_coin_api("get", "premiumIndex", data=params) + + def futures_coin_funding_rate(self, **params): + """Get funding rate history + + https://binance-docs.github.io/apidocs/delivery/en/#get-funding-rate-history-of-perpetual-futures + + """ + return self._request_futures_coin_api("get", "fundingRate", data=params) + + def futures_coin_ticker(self, **params): + """24 hour rolling window price change statistics. + + https://binance-docs.github.io/apidocs/delivery/en/#24hr-ticker-price-change-statistics + + """ + return self._request_futures_coin_api("get", "ticker/24hr", data=params) + + def futures_coin_symbol_ticker(self, **params): + """Latest price for a symbol or symbols. + + https://binance-docs.github.io/apidocs/delivery/en/#symbol-price-ticker + + """ + return self._request_futures_coin_api("get", "ticker/price", data=params) + + def futures_coin_orderbook_ticker(self, **params): + """Best price/qty on the order book for a symbol or symbols. + + https://binance-docs.github.io/apidocs/delivery/en/#symbol-order-book-ticker + + """ + return self._request_futures_coin_api("get", "ticker/bookTicker", data=params) + + def futures_coin_liquidation_orders(self, **params): + """Get all liquidation orders + + https://binance-docs.github.io/apidocs/delivery/en/#user-39-s-force-orders-user_data + + """ + return self._request_futures_coin_api("get", "forceOrders", signed=True, data=params) + + def futures_coin_open_interest(self, **params): + """Get present open interest of a specific symbol. + + https://binance-docs.github.io/apidocs/delivery/en/#open-interest + + """ + return self._request_futures_coin_api("get", "openInterest", data=params) + + def futures_coin_open_interest_hist(self, **params): + """Get open interest statistics of a specific symbol. + + https://binance-docs.github.io/apidocs/delivery/en/#open-interest-statistics-market-data + + """ + return self._request_futures_coin_data_api("get", "openInterestHist", data=params) + + def futures_coin_leverage_bracket(self, **params): + """Notional and Leverage Brackets + + https://binance-docs.github.io/apidocs/delivery/en/#notional-bracket-for-pair-user_data + + """ + return self._request_futures_coin_api( + "get", "leverageBracket", version=2, signed=True, data=params + ) + + def new_transfer_history(self, **params): + """Get future account transaction history list + + https://binance-docs.github.io/apidocs/delivery/en/#new-future-account-transfer + + """ + return self._request_margin_api("get", "asset/transfer", True, data=params) + + def funding_wallet(self, **params): + return self._request_margin_api("post", "asset/get-funding-asset", True, data=params) + + def get_user_asset(self, **params): + return self._request_margin_api("post", "asset/getUserAsset", True, data=params, version=3) + + def universal_transfer(self, **params): + """Unviversal transfer api accross different binance account types + + https://binance-docs.github.io/apidocs/spot/en/#user-universal-transfer + """ + return self._request_margin_api( + "post", "asset/transfer", signed=True, data=params + ) + + def futures_coin_create_order(self, **params): + """Send in a new order. + + https://binance-docs.github.io/apidocs/delivery/en/#new-order-trade + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_futures_coin_api("post", "order", True, data=params) + + def futures_coin_place_batch_order(self, **params): + """Send in new orders. + + https://binance-docs.github.io/apidocs/delivery/en/#place-multiple-orders-trade + + To avoid modifying the existing signature generation and parameter order logic, + the url encoding is done on the special query param, batchOrders, in the early stage. + + """ + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace('%27', '%22') + params['batchOrders'] = query_string[12:] + + return self._request_futures_coin_api('post', 'batchOrders', True, data=params) + + def futures_coin_get_order(self, **params): + """Check an order's status. + + https://binance-docs.github.io/apidocs/delivery/en/#query-order-user_data + + """ + return self._request_futures_coin_api("get", "order", True, data=params) + + def futures_coin_get_open_orders(self, **params): + """Get all open orders on a symbol. + + https://binance-docs.github.io/apidocs/delivery/en/#current-all-open-orders-user_data + + """ + return self._request_futures_coin_api("get", "openOrders", True, data=params) + + def futures_coin_get_all_orders(self, **params): + """Get all futures account orders; active, canceled, or filled. + + https://binance-docs.github.io/apidocs/delivery/en/#all-orders-user_data + + """ + return self._request_futures_coin_api( + "get", "allOrders", signed=True, data=params + ) + + def futures_coin_cancel_order(self, **params): + """Cancel an active futures order. + + https://binance-docs.github.io/apidocs/delivery/en/#cancel-order-trade + + """ + return self._request_futures_coin_api( + "delete", "order", signed=True, data=params + ) + + def futures_coin_cancel_all_open_orders(self, **params): + """Cancel all open futures orders + + https://binance-docs.github.io/apidocs/delivery/en/#cancel-all-open-orders-trade + + """ + return self._request_futures_coin_api( + "delete", "allOpenOrders", signed=True, data=params + ) + + def futures_coin_cancel_orders(self, **params): + """Cancel multiple futures orders + + https://binance-docs.github.io/apidocs/delivery/en/#cancel-multiple-orders-trade + + """ + return self._request_futures_coin_api( + "delete", "batchOrders", True, data=params + ) + + def futures_coin_account_balance(self, **params): + """Get futures account balance + + https://binance-docs.github.io/apidocs/delivery/en/#futures-account-balance-user_data + + """ + return self._request_futures_coin_api( + "get", "balance", signed=True, data=params + ) + + def futures_coin_account(self, **params): + """Get current account information. + + https://binance-docs.github.io/apidocs/delivery/en/#account-information-user_data + + """ + return self._request_futures_coin_api( + "get", "account", signed=True, data=params + ) + + def futures_coin_change_leverage(self, **params): + """Change user's initial leverage of specific symbol market + + https://binance-docs.github.io/apidocs/delivery/en/#change-initial-leverage-trade + + """ + return self._request_futures_coin_api( + "post", "leverage", signed=True, data=params + ) + + def futures_coin_change_margin_type(self, **params): + """Change the margin type for a symbol + + https://binance-docs.github.io/apidocs/delivery/en/#change-margin-type-trade + + """ + return self._request_futures_coin_api( + "post", "marginType", signed=True, data=params + ) + + def futures_coin_change_position_margin(self, **params): + """Change the position margin for a symbol + + https://binance-docs.github.io/apidocs/delivery/en/#modify-isolated-position-margin-trade + + """ + return self._request_futures_coin_api( + "post", "positionMargin", True, data=params + ) + + def futures_coin_position_margin_history(self, **params): + """Get position margin change history + + https://binance-docs.github.io/apidocs/delivery/en/#get-position-margin-change-history-trade + + """ + return self._request_futures_coin_api( + "get", "positionMargin/history", True, data=params + ) + + def futures_coin_position_information(self, **params): + """Get position information + + https://binance-docs.github.io/apidocs/delivery/en/#position-information-user_data + + """ + return self._request_futures_coin_api("get", "positionRisk", True, data=params) + + def futures_coin_account_trades(self, **params): + """Get trades for the authenticated account and symbol. + + https://binance-docs.github.io/apidocs/delivery/en/#account-trade-list-user_data + + """ + return self._request_futures_coin_api("get", "userTrades", True, data=params) + + def futures_coin_income_history(self, **params): + """Get income history for authenticated account + + https://binance-docs.github.io/apidocs/delivery/en/#get-income-history-user_data + + """ + return self._request_futures_coin_api("get", "income", True, data=params) + + def futures_coin_change_position_mode(self, **params): + """Change user's position mode (Hedge Mode or One-way Mode ) on EVERY symbol + + https://binance-docs.github.io/apidocs/delivery/en/#change-position-mode-trade + """ + return self._request_futures_coin_api("post", "positionSide/dual", True, data=params) + + def futures_coin_get_position_mode(self, **params): + """Get user's position mode (Hedge Mode or One-way Mode ) on EVERY symbol + + https://binance-docs.github.io/apidocs/delivery/en/#get-current-position-mode-user_data + + """ + return self._request_futures_coin_api("get", "positionSide/dual", True, data=params) + + def futures_coin_stream_get_listen_key(self): + res = self._request_futures_coin_api('post', 'listenKey', signed=False, data={}) + return res['listenKey'] + + def futures_coin_stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return self._request_futures_coin_api('put', 'listenKey', signed=False, data=params) + + def futures_coin_stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return self._request_futures_coin_api('delete', 'listenKey', signed=False, data=params) + + def get_all_coins_info(self, **params): + """Get information of coins (available for deposit and withdraw) for user. + + https://binance-docs.github.io/apidocs/spot/en/#all-coins-39-information-user_data + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "coin": "BTC", + "depositAllEnable": true, + "withdrawAllEnable": true, + "name": "Bitcoin", + "free": "0", + "locked": "0", + "freeze": "0", + "withdrawing": "0", + "ipoing": "0", + "ipoable": "0", + "storage": "0", + "isLegalMoney": false, + "trading": true, + "networkList": [ + { + "network": "BNB", + "coin": "BTC", + "withdrawIntegerMultiple": "0.00000001", + "isDefault": false, + "depositEnable": true, + "withdrawEnable": true, + "depositDesc": "", + "withdrawDesc": "", + "specialTips": "Both a MEMO and an Address are required to successfully deposit your BEP2-BTCB tokens to Binance.", + "name": "BEP2", + "resetAddressStatus": false, + "addressRegex": "^(bnb1)[0-9a-z]{38}$", + "memoRegex": "^[0-9A-Za-z-_]{1,120}$", + "withdrawFee": "0.0000026", + "withdrawMin": "0.0000052", + "withdrawMax": "0", + "minConfirm": 1, + "unLockConfirm": 0 + }, + { + "network": "BTC", + "coin": "BTC", + "withdrawIntegerMultiple": "0.00000001", + "isDefault": true, + "depositEnable": true, + "withdrawEnable": true, + "depositDesc": "", + "withdrawDesc": "", + "specialTips": "", + "name": "BTC", + "resetAddressStatus": false, + "addressRegex": "^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$|^(bc1)[0-9A-Za-z]{39,59}$", + "memoRegex": "", + "withdrawFee": "0.0005", + "withdrawMin": "0.001", + "withdrawMax": "0", + "minConfirm": 1, + "unLockConfirm": 2 + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'capital/config/getall', True, data=params) + + def get_account_snapshot(self, **params): + """Get daily account snapshot of specific type. + + https://binance-docs.github.io/apidocs/spot/en/#daily-account-snapshot-user_data + + :param type: required. Valid values are SPOT/MARGIN/FUTURES. + :type type: string + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: optional + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + .. code-block:: python + + { + "code":200, // 200 for success; others are error codes + "msg":"", // error message + "snapshotVos":[ + { + "data":{ + "balances":[ + { + "asset":"BTC", + "free":"0.09905021", + "locked":"0.00000000" + }, + { + "asset":"USDT", + "free":"1.89109409", + "locked":"0.00000000" + } + ], + "totalAssetOfBtc":"0.09942700" + }, + "type":"spot", + "updateTime":1576281599000 + } + ] + } + + OR + + .. code-block:: python + + { + "code":200, // 200 for success; others are error codes + "msg":"", // error message + "snapshotVos":[ + { + "data":{ + "marginLevel":"2748.02909813", + "totalAssetOfBtc":"0.00274803", + "totalLiabilityOfBtc":"0.00000100", + "totalNetAssetOfBtc":"0.00274750", + "userAssets":[ + { + "asset":"XRP", + "borrowed":"0.00000000", + "free":"1.00000000", + "interest":"0.00000000", + "locked":"0.00000000", + "netAsset":"1.00000000" + } + ] + }, + "type":"margin", + "updateTime":1576281599000 + } + ] + } + + OR + + .. code-block:: python + + { + "code":200, // 200 for success; others are error codes + "msg":"", // error message + "snapshotVos":[ + { + "data":{ + "assets":[ + { + "asset":"USDT", + "marginBalance":"118.99782335", + "walletBalance":"120.23811389" + } + ], + "position":[ + { + "entryPrice":"7130.41000000", + "markPrice":"7257.66239673", + "positionAmt":"0.01000000", + "symbol":"BTCUSDT", + "unRealizedProfit":"1.24029054" + } + ] + }, + "type":"futures", + "updateTime":1576281599000 + } + ] + } + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('get', 'accountSnapshot', True, data=params) + + def disable_fast_withdraw_switch(self, **params): + """Disable Fast Withdraw Switch + + https://binance-docs.github.io/apidocs/spot/en/#disable-fast-withdraw-switch-user_data + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'disableFastWithdrawSwitch', True, data=params) + + def enable_fast_withdraw_switch(self, **params): + """Enable Fast Withdraw Switch + + https://binance-docs.github.io/apidocs/spot/en/#enable-fast-withdraw-switch-user_data + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + :raises: BinanceRequestException, BinanceAPIException + + """ + return self._request_margin_api('post', 'enableFastWithdrawSwitch', True, data=params) + + """ + ==================================================================================================================== + Options API + ==================================================================================================================== + """ + # Quoting interface endpoints + + def options_ping(self): + """Test connectivity + + https://binance-docs.github.io/apidocs/voptions/en/#test-connectivity + + """ + return self._request_options_api('get', 'ping') + + def options_time(self): + """Get server time + + https://binance-docs.github.io/apidocs/voptions/en/#get-server-time + + """ + return self._request_options_api('get', 'time') + + def options_info(self): + """Get current trading pair info + + https://binance-docs.github.io/apidocs/voptions/en/#get-current-trading-pair-info + + """ + return self._request_options_api('get', 'optionInfo') + + def options_exchange_info(self): + """Get current limit info and trading pair info + + https://binance-docs.github.io/apidocs/voptions/en/#get-current-limit-info-and-trading-pair-info + + """ + return self._request_options_api('get', 'exchangeInfo') + + def options_index_price(self, **params): + """Get the spot index price + + https://binance-docs.github.io/apidocs/voptions/en/#get-the-spot-index-price + + :param underlying: required - Spot pair(Option contract underlying asset)- BTCUSDT + :type underlying: str + + """ + return self._request_options_api('get', 'index', data=params) + + def options_price(self, **params): + """Get the latest price + + https://binance-docs.github.io/apidocs/voptions/en/#get-the-latest-price + + :param symbol: optional - Option trading pair - BTC-200730-9000-C + :type symbol: str + + """ + return self._request_options_api('get', 'ticker', data=params) + + def options_mark_price(self, **params): + """Get the latest mark price + + https://binance-docs.github.io/apidocs/voptions/en/#get-the-latest-mark-price + + :param symbol: optional - Option trading pair - BTC-200730-9000-C + :type symbol: str + + """ + return self._request_options_api('get', 'mark', data=params) + + def options_order_book(self, **params): + """Depth information + + https://binance-docs.github.io/apidocs/voptions/en/#depth-information + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param limit: optional - Default:100 Max:1000.Optional value:[10, 20, 50, 100, 500, 1000] - 100 + :type limit: int + + """ + return self._request_options_api('get', 'depth', data=params) + + def options_klines(self, **params): + """Candle data + + https://binance-docs.github.io/apidocs/voptions/en/#candle-data + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param interval: required - Time interval - 5m + :type interval: str + :param startTime: optional - Start Time - 1592317127349 + :type startTime: int + :param endTime: optional - End Time - 1592317127349 + :type endTime: int + :param limit: optional - Number of records Default:500 Max:1500 - 500 + :type limit: int + + """ + return self._request_options_api('get', 'klines', data=params) + + def options_recent_trades(self, **params): + """Recently completed Option trades + + https://binance-docs.github.io/apidocs/voptions/en/#recently-completed-option-trades + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param limit: optional - Number of records Default:100 Max:500 - 100 + :type limit: int + + """ + return self._request_options_api('get', 'trades', data=params) + + def options_historical_trades(self, **params): + """Query trade history + + https://binance-docs.github.io/apidocs/voptions/en/#query-trade-history + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param fromId: optional - The deal ID from which to return. The latest deal record is returned by default - 1592317127349 + :type fromId: int + :param limit: optional - Number of records Default:100 Max:500 - 100 + :type limit: int + + """ + return self._request_options_api('get', 'historicalTrades', data=params) + + # Account and trading interface endpoints + + def options_account_info(self, **params): + """Account asset info (USER_DATA) + + https://binance-docs.github.io/apidocs/voptions/en/#account-asset-info-user_data + + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'account', signed=True, data=params) + + def options_funds_transfer(self, **params): + """Funds transfer (USER_DATA) + + https://binance-docs.github.io/apidocs/voptions/en/#funds-transfer-user_data + + :param currency: required - Asset type - USDT + :type currency: str + :param type: required - IN: Transfer from spot account to option account OUT: Transfer from option account to spot account - IN + :type type: str (ENUM) + :param amount: required - Amount - 10000 + :type amount: float + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('post', 'transfer', signed=True, data=params) + + def options_positions(self, **params): + """Option holdings info (USER_DATA) + + https://binance-docs.github.io/apidocs/voptions/en/#option-holdings-info-user_data + + :param symbol: optional - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'position', signed=True, data=params) + + def options_bill(self, **params): + """Account funding flow (USER_DATA) + + https://binance-docs.github.io/apidocs/voptions/en/#account-funding-flow-user_data + + :param currency: required - Asset type - USDT + :type currency: str + :param recordId: optional - Return the recordId and subsequent data, the latest data is returned by default - 100000 + :type recordId: int + :param startTime: optional - Start Time - 1593511200000 + :type startTime: int + :param endTime: optional - End Time - 1593511200000 + :type endTime: int + :param limit: optional - Number of result sets returned Default:100 Max:1000 - 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('post', 'bill', signed=True, data=params) + + def options_place_order(self, **params): + """Option order (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#option-order-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param side: required - Buy/sell direction: SELL, BUY - BUY + :type side: str (ENUM) + :param type: required - Order Type: LIMIT, MARKET - LIMIT + :type type: str (ENUM) + :param quantity: required - Order Quantity - 3 + :type quantity: float + :param price: optional - Order Price - 1000 + :type price: float + :param timeInForce: optional - Time in force method(Default GTC) - GTC + :type timeInForce: str (ENUM) + :param reduceOnly: optional - Reduce Only (Default false) - false + :type reduceOnly: bool + :param postOnly: optional - Post Only (Default false) - false + :type postOnly: bool + :param newOrderRespType: optional - "ACK", "RESULT", Default "ACK" - ACK + :type newOrderRespType: str (ENUM) + :param clientOrderId: optional - User-defined order ID cannot be repeated in pending orders - 10000 + :type clientOrderId: str + :param recvWindow: optional + :type recvWindow: int + + """ + if 'clientOrderId' not in params: + params['clientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_options_api('post', 'order', signed=True, data=params) + + def options_place_batch_order(self, **params): + """Place Multiple Option orders (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#place-multiple-option-orders-trade + + :param orders: required - order list. Max 5 orders - [{"symbol":"BTC-210115-35000-C","price":"100","quantity":"0.0001","side":"BUY","type":"LIMIT"}] + :type orders: list + :param recvWindow: optional + :type recvWindow: int + + """ + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_options_api('post', 'batchOrders', signed=True, data=params) + + def options_cancel_order(self, **params): + """Cancel Option order (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#cancel-option-order-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param orderId: optional - Order ID - 4611875134427365377 + :type orderId: str + :param clientOrderId: optional - User-defined order ID - 10000 + :type clientOrderId: str + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('delete', 'order', signed=True, data=params) + + def options_cancel_batch_order(self, **params): + """Cancel Multiple Option orders (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#cancel-multiple-option-orders-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param orderIds: optional - Order ID - [4611875134427365377,4611875134427365378] + :type orderId: list + :param clientOrderIds: optional - User-defined order ID - ["my_id_1","my_id_2"] + :type clientOrderIds: list + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('delete', 'batchOrders', signed=True, data=params) + + def options_cancel_all_orders(self, **params): + """Cancel all Option orders (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#cancel-all-option-orders-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('delete', 'allOpenOrders', signed=True, data=params) + + def options_query_order(self, **params): + """Query Option order (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#query-option-order-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param orderId: optional - Order ID - 4611875134427365377 + :type orderId: str + :param clientOrderId: optional - User-defined order ID - 10000 + :type clientOrderId: str + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'order', signed=True, data=params) + + def options_query_pending_orders(self, **params): + """Query current pending Option orders (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#query-current-pending-option-orders-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param orderId: optional - Returns the orderId and subsequent orders, the most recent order is returned by default - 100000 + :type orderId: str + :param startTime: optional - Start Time - 1593511200000 + :type startTime: int + :param endTime: optional - End Time - 1593511200000 + :type endTime: int + :param limit: optional - Number of result sets returned Default:100 Max:1000 - 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'openOrders', signed=True, data=params) + + def options_query_order_history(self, **params): + """Query Option order history (TRADE) + + https://binance-docs.github.io/apidocs/voptions/en/#query-option-order-history-trade + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param orderId: optional - Returns the orderId and subsequent orders, the most recent order is returned by default - 100000 + :type orderId: str + :param startTime: optional - Start Time - 1593511200000 + :type startTime: int + :param endTime: optional - End Time - 1593511200000 + :type endTime: int + :param limit: optional - Number of result sets returned Default:100 Max:1000 - 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'historyOrders', signed=True, data=params) + + def options_user_trades(self, **params): + """Option Trade List (USER_DATA) + + https://binance-docs.github.io/apidocs/voptions/en/#option-trade-list-user_data + + :param symbol: required - Option trading pair - BTC-200730-9000-C + :type symbol: str + :param fromId: optional - Trade id to fetch from. Default gets most recent trades. - 4611875134427365376 + :type fromId: int + :param startTime: optional - Start Time - 1593511200000 + :type startTime: int + :param endTime: optional - End Time - 1593511200000 + :type endTime: int + :param limit: optional - Number of result sets returned Default:100 Max:1000 - 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_options_api('get', 'userTrades', signed=True, data=params) + + # Fiat Endpoints + + def get_fiat_deposit_withdraw_history(self, **params): + """Get Fiat Deposit/Withdraw History + + https://binance-docs.github.io/apidocs/spot/en/#get-fiat-deposit-withdraw-history-user_data + + :param transactionType: required - 0-deposit,1-withdraw + :type transactionType: str + :param beginTime: optional + :type beginTime: int + :param endTime: optional + :type endTime: int + :param page: optional - default 1 + :type page: int + :param rows: optional - default 100, max 500 + :type rows: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_margin_api('get', 'fiat/orders', signed=True, data=params) + + def get_fiat_payments_history(self, **params): + """Get Fiat Payments History + + https://binance-docs.github.io/apidocs/spot/en/#get-fiat-payments-history-user_data + + :param transactionType: required - 0-buy,1-sell + :type transactionType: str + :param beginTime: optional + :type beginTime: int + :param endTime: optional + :type endTime: int + :param page: optional - default 1 + :type page: int + :param rows: optional - default 100, max 500 + :type rows: int + :param recvWindow: optional + :type recvWindow: int + + """ + return self._request_margin_api('get', 'fiat/payments', signed=True, data=params) + + # C2C Endpoints + + def get_c2c_trade_history(self, **params): + """Get C2C Trade History + + https://binance-docs.github.io/apidocs/spot/en/#get-c2c-trade-history-user_data + + :param tradeType: required - BUY, SELL + :type tradeType: str + :param startTimestamp: optional + :type startTime: int + :param endTimestamp: optional + :type endTimestamp: int + :param page: optional - default 1 + :type page: int + :param rows: optional - default 100, max 100 + :type rows: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + { + "code": "000000", + "message": "success", + "data": [ + { + "orderNumber":"20219644646554779648", + "advNo": "11218246497340923904", + "tradeType": "SELL", + "asset": "BUSD", + "fiat": "CNY", + "fiatSymbol": "¥", + "amount": "5000.00000000", // Quantity (in Crypto) + "totalPrice": "33400.00000000", + "unitPrice": "6.68", // Unit Price (in Fiat) + "orderStatus": "COMPLETED", // PENDING, TRADING, BUYER_PAYED, DISTRIBUTING, COMPLETED, IN_APPEAL, CANCELLED, CANCELLED_BY_SYSTEM + "createTime": 1619361369000, + "commission": "0", // Transaction Fee (in Crypto) + "counterPartNickName": "ab***", + "advertisementRole": "TAKER" + } + ], + "total": 1, + "success": true + } + + """ + return self._request_margin_api('get', 'c2c/orderMatch/listUserOrderHistory', signed=True, data=params) + + # Pay Endpoints + + def get_pay_trade_history(self, **params): + """Get C2C Trade History + + https://binance-docs.github.io/apidocs/spot/en/#pay-endpoints + + :param startTime: optional + :type startTime: int + :param endTime: optional + :type endTime: int + :param limit: optional - default 100, max 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_margin_api('get', 'pay/transactions', signed=True, data=params) + + # Convert Endpoints + + def get_convert_trade_history(self, **params): + """Get C2C Trade History + + https://binance-docs.github.io/apidocs/spot/en/#pay-endpoints + + :param startTime: required - Start Time - 1593511200000 + :type startTime: int + :param endTime: required - End Time - 1593511200000 + :type endTime: int + :param limit: optional - default 100, max 100 + :type limit: int + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_margin_api('get', 'convert/tradeFlow', signed=True, data=params) + + def convert_request_quote(self, **params): + """Request a quote for the requested token pairs + + https://binance-docs.github.io/apidocs/spot/en/#send-quote-request-user_data + + :param fromAsset: required - Asset to convert from - BUSD + :type fromAsset: str + :param toAsset: required - Asset to convert to - BTC + :type toAsset: str + :param fromAmount: EITHER - When specified, it is the amount you will be debited after the conversion + :type fromAmount: decimal + :param toAmount: EITHER - When specified, it is the amount you will be credited after the conversion + :type toAmount: decimal + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_margin_api('post', 'convert/getQuote', signed=True, data=params) + + def convert_accept_quote(self, **params): + """Accept the offered quote by quote ID. + + https://binance-docs.github.io/apidocs/spot/en/#accept-quote-trade + + :param quoteId: required - 457235734584567 + :type quoteId: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_margin_api('post', 'convert/acceptQuote', signed=True, data=params) + + """ + ==================================================================================================================== + PortfolioMargin API + ==================================================================================================================== + """ + + def papi_get_balance(self, **params): + """Query account balance. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'balance', signed=True, data=params) + + def papi_get_account(self, **params): + """Query account information. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Account-Information + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'account', signed=True, data=params) + + def papi_get_margin_max_borrowable(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Margin-Max-Borrow + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/maxBorrowable', signed=True, data=params) + + + def papi_get_margin_max_withdraw(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-Margin-Max-Withdraw + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/maxWithdraw', signed=True, data=params) + + + def papi_get_um_position_risk(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-UM-Position-Information + + :param symbol: required + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/positionRisk', signed=True, data=params) + + + + def papi_get_cm_position_risk(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-CM-Position-Information + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/positionRisk', signed=True, data=params) + + + + def papi_set_um_leverage(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-UM-Initial-Leverage + + :param asset: required + :type asset: str + + :param leverage: required + :type leverage: int + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'um/leverage', signed=True, data=params) + + + def papi_set_cm_leverage(self, **params): + """Query margin max borrow. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-CM-Initial-Leverage + + :param asset: required + :type asset: str + + :param leverage: required + :type leverage: int + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'cm/leverage', signed=True, data=params) + + + def papi_change_um_position_side_dual(self, **params): + """Change user's position mode (Hedge Mode or One-way Mode ) on EVERY symbol in UM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-UM-Position-Mode + + :param dualSidePosition: required + :type dualSidePosition: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'um/positionSide/dual', signed=True, data=params) + + + def papi_get_um_position_side_dual(self, **params): + """Get user's position mode (Hedge Mode or One-way Mode ) on EVERY symbol in UM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Current-Position-Mode + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/positionSide/dual', signed=True, data=params) + + + def papi_get_cm_position_side_dual(self, **params): + """Get user's position mode (Hedge Mode or One-way Mode ) on EVERY symbol in CM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Current-Position-Mode + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/positionSide/dual', signed=True, data=params) + + + def papi_get_um_leverage_bracket(self, **params): + """Query UM notional and leverage brackets. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Margin-Max-Borrow + + :param symbol: optional + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/leverageBracket', signed=True, data=params) + + + def papi_get_cm_leverage_bracket(self, **params): + """Query CM notional and leverage brackets. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/CM-Notional-and-Leverage-Brackets + + :param symbol: optional + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/leverageBracket', signed=True, data=params) + + def papi_get_um_api_trading_status(self, **params): + """Portfolio Margin UM Trading Quantitative Rules Indicators. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Portfolio-Margin-UM-Trading-Quantitative-Rules-Indicators + + :param symbol: optional + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/apiTradingStatus', signed=True, data=params) + + + def papi_get_um_comission_rate(self, **params): + """Get User Commission Rate for UM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-User-Commission-Rate-for-UM + + :param symbol: required + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/commissionRate', signed=True, data=params) + + + def papi_get_cm_comission_rate(self, **params): + """Get User Commission Rate for CM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-User-Commission-Rate-for-CM + + :param symbol: required + :type symbol: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/commissionRate', signed=True, data=params) + + def papi_get_margin_margin_loan(self, **params): + """Query margin loan record. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-Margin-Loan-Record + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/marginLoan', signed=True, data=params) + + def papi_get_margin_repay_loan(self, **params): + """Query margin repay record. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-Margin-repay-Record + + :param asset: required + :type asset: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/repayLoan', signed=True, data=params) + + + def papi_get_repay_futures_switch(self, **params): + """Query Auto-repay-futures Status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Auto-repay-futures-Status + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'repay-futures-switch', signed=True, data=params) + + + def papi_repay_futures_switch(self, **params): + """Change Auto-repay-futures Status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-Auto-repay-futures-Status + + :param autoRepay: required + :type autoRepay: str + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'repay-futures-switch', signed=True, data=params) + + + def papi_get_margin_interest_history(self, **params): + """Get Margin Borrow/Loan Interest History. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Margin-BorrowLoan-Interest-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/marginInterestHistory', signed=True, data=params) + + + def papi_repay_futures_negative_balance(self, **params): + """Repay futures Negative Balance. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Repay-futures-Negative-Balance + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'repay-futures-negative-balance', signed=True, data=params) + + + def papi_get_portfolio_interest_history(self, **params): + """GQuery interest history of negative balance for portfolio margin. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-Portfolio-Margin-Negative-Balance-Interest-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'portfolio/interest-history', signed=True, data=params) + + + def papi_fund_auto_collection(self, **params): + """Fund collection for Portfolio Margin. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Fund-Auto-collection + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'auto-collection', signed=True, data=params) + + + def papi_fund_asset_collection(self, **params): + """Transfers specific asset from Futures Account to Margin account. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Fund-Collection-by-Asset + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'asset-collection', signed=True, data=params) + + + def papi_bnb_transfer(self, **params): + """Transfer BNB in and out of UM. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/BNB-transfer + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('post', 'bnb-transfer', signed=True, data=params) + + + def papi_get_um_income_history(self, **params): + """Get UM Income History. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Income-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/income', signed=True, data=params) + + + def papi_get_cm_income_history(self, **params): + """Get CM Income History. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Income-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/income', signed=True, data=params) + + + def papi_get_um_account(self, **params): + """Get current UM account asset and position information. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Account-Detail + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/account', signed=True, data=params) + + def papi_get_um_account_v2(self, **params): + """Get current UM account asset and position information. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Account-Detail + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/account', version=2, signed=True, data=params) + + + def papi_get_cm_account(self, **params): + """Get current CM account asset and position information. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Account-Detail + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/account', signed=True, data=params) + + + def papi_get_um_account_config(self, **params): + """Query UM Futures account configuration. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Futures-Account-Config + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/accountConfig', signed=True, data=params) + + + def papi_get_um_symbol_config(self, **params): + """Get current UM account symbol configuration. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Futures-Symbol-Config + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/symbolConfig', signed=True, data=params) + + + def papi_get_um_trade_asyn(self, **params): + """Get download id for UM futures trade history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Download-Id-For-UM-Futures-Trade-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/trade/asyn', signed=True, data=params) + + + def papi_get_um_trade_asyn_id(self, **params): + """Get UM futures trade download link by Id. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Download-Id-For-UM-Futures-Trade-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/trade/asyn/id', signed=True, data=params) + + + + def papi_get_um_order_asyn(self, **params): + """Get download id for UM futures order history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Download-Id-For-UM-Futures-Order-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/order/asyn', signed=True, data=params) + + + def papi_get_um_order_asyn_id(self, **params): + """Get UM futures order download link by Id. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Futures-Order-Download-Link-by-Id + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/order/asyn/id', signed=True, data=params) + + + def papi_get_um_income_asyn(self, **params): + """Get download id for UM futures transaction history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Download-Id-For-UM-Futures-Transaction-History + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/income/asyn', signed=True, data=params) + + + def papi_get_um_income_asyn_id(self, **params): + """Get UM futures Transaction download link by Id. + + https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Futures-Transaction-Download-Link-by-Id + + :param recvWindow: optional + :type recvWindow: int + + :returns: API response + + """ + return self._request_papi_api('get', 'um/income/asyn/id', signed=True, data=params) + + # Public papi endpoints + + def papi_ping(self, **params): + """Test connectivity to the Rest API. + + https://developers.binance.com/docs/derivatives/portfolio-margin/market-data + + :returns: API response + + """ + return self._request_papi_api('get', 'ping', signed=False, data=params) + + # Trade papi endpoints + + def papi_create_um_order(self, **params): + """Place new UM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_papi_api('post', 'um/order', signed=True, data=params) + + + def papi_create_um_conditional_order(self, **params): + """Place new UM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Conditional-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_papi_api('post', 'um/conditional/order', signed=True, data=params) + + + def papi_create_cm_order(self, **params): + """Place new CM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_papi_api('post', 'cm/order', signed=True, data=params) + + + def papi_create_cm_conditional_order(self, **params): + """Place new CM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Conditional-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_papi_api('post', 'cm/conditional/order', signed=True, data=params) + + + def papi_create_margin_order(self, **params): + """New Margin Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-Margin-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._request_papi_api('post', 'margin/order', signed=True, data=params) + + + def papi_margin_loan(self, **params): + """Apply for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Borrow + + :returns: API response + + """ + return self._request_papi_api('post', 'marginLoan', signed=True, data=params) + + + def papi_repay_loan(self, **params): + """Repay for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay + + :returns: API response + + """ + return self._request_papi_api('post', 'repayLoan', signed=True, data=params) + + + def papi_margin_order_oco(self, **params): + """Send in a new OCO for a margin account. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-New-OCO + + :returns: API response + + """ + return self._request_papi_api('post', 'margin/order/oco', signed=True, data=params) + + + def papi_cancel_um_order(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Order + + :returns: API response + + """ + return self._request_papi_api('delete', 'um/order', signed=True, data=params) + + def papi_cancel_um_all_open_orders(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Orders + + :returns: API response + + """ + return self._request_papi_api('delete', 'um/allOpenOrders', signed=True, data=params) + + + def papi_cancel_um_conditional_order(self, **params): + """Cancel UM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Conditional-Order + + :returns: API response + + """ + return self._request_papi_api('delete', 'um/conditional/order', signed=True, data=params) + + + def papi_cancel_um_conditional_all_open_orders(self, **params): + """Cancel All UM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('delete', 'um/conditional/allOpenOrders', signed=True, data=params) + + + def papi_cancel_cm_order(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Order + + :returns: API response + + """ + return self._request_papi_api('delete', 'cm/order', signed=True, data=params) + + def papi_cancel_cm_all_open_orders(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Orders + + :returns: API response + + """ + return self._request_papi_api('delete', 'cm/allOpenOrders', signed=True, data=params) + + def papi_cancel_cm_conditional_order(self, **params): + """Cancel CM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Conditional-Order + + :returns: API response + + """ + return self._request_papi_api('delete', 'cm/conditional/order', signed=True, data=params) + + + def papi_cancel_cm_conditional_all_open_orders(self, **params): + """Cancel All CM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('delete', 'cm/conditional/allOpenOrders', signed=True, data=params) + + + def papi_cancel_margin_order(self, **params): + """Cancel Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-Order + + :returns: API response + + """ + return self._request_papi_api('delete', 'margin/order', signed=True, data=params) + + + def papi_cancel_margin_order_list(self, **params): + """Cancel Margin Account OCO Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-OCO-Orders + + :returns: API response + + """ + return self._request_papi_api('delete', 'margin/orderList', signed=True, data=params) + + + def papi_cancel_margin_all_open_orders(self, **params): + """Cancel Margin Account All Open Orders on a Symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-All-Open-Orders-on-a-Symbol + + :returns: API response + + """ + return self._request_papi_api('delete', 'margin/allOpenOrders', signed=True, data=params) + + + def papi_modify_um_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-UM-Order + + :returns: API response + + """ + return self._request_papi_api('put', 'um/order', signed=True, data=params) + + + def papi_modify_cm_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-CM-Order + + :returns: API response + + """ + return self._request_papi_api('put', 'cm/order', signed=True, data=params) + + def papi_get_um_order(self, **params): + """Check an UM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'um/order', signed=True, data=params) + + + def papi_get_um_all_orders(self, **params): + """Get all account UM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'um/allOrders', signed=True, data=params) + + + def papi_get_um_open_order(self, **params): + """Query current UM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'um/openOrder', signed=True, data=params) + + + def papi_get_um_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'um/openOrders', signed=True, data=params) + + + def papi_get_um_conditional_all_orders(self, **params): + """Query All UM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'um/conditional/allOrders', signed=True, data=params) + + + def papi_get_um_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'um/conditional/openOrders', signed=True, data=params) + + + def papi_get_um_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Conditional-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'um/conditional/openOrder', signed=True, data=params) + + + def papi_get_um_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Conditional-Order-History + + :returns: API response + + """ + return self._request_papi_api('get', 'um/conditional/orderHistory', signed=True, data=params) + + + + def papi_get_cm_order(self, **params): + """Check an CM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/order', signed=True, data=params) + + + def papi_get_cm_all_orders(self, **params): + """Get all account CM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/allOrders', signed=True, data=params) + + + def papi_get_cm_open_order(self, **params): + """Query current CM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/openOrder', signed=True, data=params) + + + def papi_get_cm_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/openOrders', signed=True, data=params) + + + def papi_get_cm_conditional_all_orders(self, **params): + """Query All CM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/conditional/allOrders', signed=True, data=params) + + + def papi_get_cm_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Conditional-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/conditional/openOrders', signed=True, data=params) + + + def papi_get_cm_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Conditional-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/conditional/openOrder', signed=True, data=params) + + + def papi_get_cm_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Conditional-Order-History + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/conditional/orderHistory', signed=True, data=params) + + + def papi_get_um_force_orders(self, **params): + """Query User's UM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-UM-Force-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'um/forceOrders', signed=True, data=params) + + + def papi_get_cm_force_orders(self, **params): + """Query User's CM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-CM-Force-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/forceOrders', signed=True, data=params) + + + def papi_get_um_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Modify-Order-History + + :returns: API response + + """ + return self._request_papi_api('get', 'um/orderAmendment', signed=True, data=params) + + + def papi_get_cm_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Modify-Order-History + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/orderAmendment', signed=True, data=params) + + + def papi_get_margin_force_orders(self, **params): + """Query user's margin force orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-Margin-Force-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/forceOrders', signed=True, data=params) + + def papi_get_um_user_trades(self, **params): + """Get trades for a specific account and UM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Account-Trade-List + + :returns: API response + + """ + return self._request_papi_api('get', 'um/userTrades', signed=True, data=params) + + + def papi_get_cm_user_trades(self, **params): + """Get trades for a specific account and CM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Account-Trade-List + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/userTrades', signed=True, data=params) + + + def papi_get_um_adl_quantile(self, **params): + """Query UM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return self._request_papi_api('get', 'um/adlQuantile', signed=True, data=params) + + + def papi_get_cm_adl_quantile(self, **params): + """Query CM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return self._request_papi_api('get', 'cm/adlQuantile', signed=True, data=params) + + + def papi_set_um_fee_burn(self, **params): + """Change user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off ) on EVERY symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Toggle-BNB-Burn-On-UM-Futures-Trade + + :returns: API response + + """ + return self._request_papi_api('post', 'um/feeBurn', signed=True, data=params) + + + def papi_get_um_fee_burn(self, **params): + """Get user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off). + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Get-UM-Futures-BNB-Burn-Status + + :returns: API response + + """ + return self._request_papi_api('get', 'um/feeBurn', signed=True, data=params) + + + def papi_get_margin_order(self, **params): + """Query Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/order', signed=True, data=params) + + + def papi_get_margin_open_orders(self, **params): + """Query Current Margin Open Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/openOrders', signed=True, data=params) + + + def papi_get_margin_all_orders(self, **params): + """Query All Margin Account Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Margin-Account-Orders + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/allOrders', signed=True, data=params) + + + def papi_get_margin_order_list(self, **params): + """Retrieves a specific OCO based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-OCO + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/orderList', signed=True, data=params) + + + def papi_get_margin_all_order_list(self, **params): + """Query all OCO for a specific margin account based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-all-OCO + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/allOrderList', signed=True, data=params) + + + def papi_get_margin_open_order_list(self, **params): + """Query Margin Account's Open OCO. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Open-OCO + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/openOrderList', signed=True, data=params) + + + def papi_get_margin_my_trades(self, **params): + """Margin Account Trade List. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return self._request_papi_api('get', 'margin/myTrades', signed=True, data=params) + + + def papi_get_margin_repay_debt(self, **params): + """Repay debt for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return self._request_papi_api('post', 'margin/repay-debt', signed=True, data=params) + + + def close_connection(self): + if self.session: + self.session.close() + + def __del__(self): + self.close_connection() + + +class AsyncClient(BaseClient): + + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', + base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, + testnet: bool = False, loop=None, session_params: Optional[Dict[str, Any]] = None, + private_key: Optional[Union[str, Path]] = None, private_key_pass: Optional[str] = None, + https_proxy: Optional[str] = None + ): + self.https_proxy = https_proxy + self.loop = loop or get_loop() + self._session_params: Dict[str, Any] = session_params or {} + super().__init__(api_key, api_secret, requests_params, tld, base_endpoint, testnet, private_key, private_key_pass) + + @classmethod + async def create( + cls, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', + base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, + testnet: bool = False, loop=None, session_params: Optional[Dict[str, Any]] = None, + https_proxy: Optional[str] = None + ): + + self = cls(api_key, api_secret, requests_params, tld, base_endpoint, testnet, loop, session_params) + self.https_proxy = https_proxy # move this to the constructor + + try: + await self.ping() + + # calculate timestamp offset between local and binance server + res = await self.get_server_time() + self.timestamp_offset = res['serverTime'] - int(time.time() * 1000) + + return self + except Exception: + # If ping throw an exception, the current self must be cleaned + # else, we can receive a "asyncio:Unclosed client session" + await self.close_connection() + raise + + def _init_session(self) -> aiohttp.ClientSession: + + session = aiohttp.ClientSession( + loop=self.loop, + headers=self._get_headers(), + **self._session_params + ) + return session + + async def close_connection(self): + if self.session: + assert self.session + await self.session.close() + + async def _request(self, method, uri: str, signed: bool, force_params: bool = False, **kwargs): + + kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) + + async with getattr(self.session, method)(uri, proxy=self.https_proxy, **kwargs) as response: + self.response = response + return await self._handle_response(response) + + async def _handle_response(self, response: aiohttp.ClientResponse): + """Internal helper for handling API responses from the Binance server. + Raises the appropriate exceptions when necessary; otherwise, returns the + response. + """ + if not str(response.status).startswith('2'): + raise BinanceAPIException(response, response.status, await response.text()) + try: + return await response.json() + except ValueError: + txt = await response.text() + raise BinanceRequestException(f'Invalid Response: {txt}') + + async def _request_api(self, method, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs): + uri = self._create_api_uri(path, signed, version) + return await self._request(method, uri, signed, **kwargs) + + async def _request_futures_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_api_uri(path, version=version) + + return await self._request(method, uri, signed, False, **kwargs) + + async def _request_futures_data_api(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_futures_data_api_uri(path) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_api_url(path, version=version) + + return await self._request(method, uri, signed, False, **kwargs) + + async def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_data_api_url(path, version=version) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_options_api_uri(path) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_margin_api_uri(path, version) + + return await self._request(method, uri, signed, **kwargs) + + async def _request_papi_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_papi_api_uri(path, version) + + return await self._request(method, uri, signed, **kwargs) + + async def _request_website(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_website_uri(path) + return await self._request(method, uri, signed, **kwargs) + + async def _get(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs): + return await self._request_api('get', path, signed, version, **kwargs) + + async def _post(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: + return await self._request_api('post', path, signed, version, **kwargs) + + async def _put(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: + return await self._request_api('put', path, signed, version, **kwargs) + + async def _delete(self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs) -> Dict: + return await self._request_api('delete', path, signed, version, **kwargs) + + # Exchange Endpoints + + async def get_products(self) -> Dict: + products = await self._request_website('get', 'bapi/asset/v2/public/asset-service/product/get-products?includeEtf=true') + return products + get_products.__doc__ = Client.get_products.__doc__ + + async def get_exchange_info(self) -> Dict: + return await self._get('exchangeInfo', version=self.PRIVATE_API_VERSION) + get_exchange_info.__doc__ = Client.get_exchange_info.__doc__ + + async def get_symbol_info(self, symbol) -> Optional[Dict]: + res = await self.get_exchange_info() + + for item in res['symbols']: + if item['symbol'] == symbol.upper(): + return item + + return None + get_symbol_info.__doc__ = Client.get_symbol_info.__doc__ + + # General Endpoints + + async def ping(self) -> Dict: + return await self._get('ping', version=self.PRIVATE_API_VERSION) + ping.__doc__ = Client.ping.__doc__ + + async def get_server_time(self) -> Dict: + return await self._get('time', version=self.PRIVATE_API_VERSION) + get_server_time.__doc__ = Client.get_server_time.__doc__ + + # Market Data Endpoints + + async def get_all_tickers(self, symbol: Optional[str] = None) -> List[Dict[str, str]]: + params = {} + if symbol: + params['symbol'] = symbol + return await self._get('ticker/price', version=self.PRIVATE_API_VERSION, data=params) + get_all_tickers.__doc__ = Client.get_all_tickers.__doc__ + + async def get_orderbook_tickers(self, **params) -> Dict: + data = {} + if "symbol" in params: + data["symbol"] = params["symbol"] + elif "symbols" in params: + data["symbols"] = params["symbols"] + return await self._get('ticker/bookTicker', data=data, version=self.PRIVATE_API_VERSION) + get_orderbook_tickers.__doc__ = Client.get_orderbook_tickers.__doc__ + + async def get_order_book(self, **params) -> Dict: + return await self._get('depth', data=params, version=self.PRIVATE_API_VERSION) + get_order_book.__doc__ = Client.get_order_book.__doc__ + + async def get_recent_trades(self, **params) -> Dict: + return await self._get('trades', data=params) + get_recent_trades.__doc__ = Client.get_recent_trades.__doc__ + + async def get_historical_trades(self, **params) -> Dict: + return await self._get('historicalTrades', data=params, version=self.PRIVATE_API_VERSION) + get_historical_trades.__doc__ = Client.get_historical_trades.__doc__ + + async def get_aggregate_trades(self, **params) -> Dict: + return await self._get('aggTrades', data=params, version=self.PRIVATE_API_VERSION) + get_aggregate_trades.__doc__ = Client.get_aggregate_trades.__doc__ + + async def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): + if start_str is not None and last_id is not None: + raise ValueError( + 'start_time and last_id may not be simultaneously specified.') + + # If there's no last_id, get one. + if last_id is None: + # Without a last_id, we actually need the first trade. Normally, + # we'd get rid of it. See the next loop. + if start_str is None: + trades = await self.get_aggregate_trades(symbol=symbol, fromId=0) + else: + # The difference between startTime and endTime should be less + # or equal than an hour and the result set should contain at + # least one trade. + start_ts = convert_ts_str(start_str) + # If the resulting set is empty (i.e. no trades in that interval) + # then we just move forward hour by hour until we find at least one + # trade or reach present moment + while True: + end_ts = start_ts + (60 * 60 * 1000) + trades = await self.get_aggregate_trades( + symbol=symbol, + startTime=start_ts, + endTime=end_ts) + if len(trades) > 0: + break + # If we reach present moment and find no trades then there is + # nothing to iterate, so we're done + if end_ts > int(time.time() * 1000): + return + start_ts = end_ts + for t in trades: + yield t + last_id = trades[-1][self.AGG_ID] + + while True: + # There is no need to wait between queries, to avoid hitting the + # rate limit. We're using blocking IO, and as long as we're the + # only thread running calls like this, Binance will automatically + # add the right delay time on their end, forcing us to wait for + # data. That really simplifies this function's job. Binance is + # fucking awesome. + trades = await self.get_aggregate_trades(symbol=symbol, fromId=last_id) + # fromId=n returns a set starting with id n, but we already have + # that one. So get rid of the first item in the result set. + trades = trades[1:] + if len(trades) == 0: + return + for t in trades: + yield t + last_id = trades[-1][self.AGG_ID] + aggregate_trade_iter.__doc__ = Client.aggregate_trade_iter.__doc__ + + async def get_klines(self, **params) -> Dict: + return await self._get('klines', data=params, version=self.PRIVATE_API_VERSION) + get_klines.__doc__ = Client.get_klines.__doc__ + + async def _klines(self, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, **params) -> Dict: + if 'endTime' in params and not params['endTime']: + del params['endTime'] + if HistoricalKlinesType.SPOT == klines_type: + return await self.get_klines(**params) + elif HistoricalKlinesType.FUTURES == klines_type: + return await self.futures_klines(**params) + elif HistoricalKlinesType.FUTURES_COIN == klines_type: + return await self.futures_coin_klines(**params) + else: + raise NotImplementedException(klines_type) + _klines.__doc__ = Client._klines.__doc__ + + async def _get_earliest_valid_timestamp(self, symbol, interval, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + kline = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=1, + startTime=0, + endTime=int(time.time() * 1000) + ) + return kline[0][0] + _get_earliest_valid_timestamp.__doc__ = Client._get_earliest_valid_timestamp.__doc__ + + async def get_historical_klines(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + return await self._historical_klines(symbol, interval, start_str, end_str=end_str, limit=limit, klines_type=klines_type) + get_historical_klines.__doc__ = Client.get_historical_klines.__doc__ + + async def _historical_klines(self, symbol, interval, start_str=None, end_str=None, limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + + initial_limit_set = True + if limit is None: + limit = 1000 + initial_limit_set = False + + # init our list + output_data = [] + + # convert interval to useful value in seconds + timeframe = interval_to_milliseconds(interval) + + # establish first available start timestamp + start_ts = convert_ts_str(start_str) + if start_ts is not None: + first_valid_ts = await self._get_earliest_valid_timestamp(symbol, interval, klines_type) + start_ts = max(start_ts, first_valid_ts) + + # if an end time was passed convert it + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return output_data + + idx = 0 + while True: + # fetch the klines from start_ts up to max 500 entries or the end_ts if set + temp_data = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=limit, + startTime=start_ts, + endTime=end_ts + ) + + # append this loops data to our output data + if temp_data: + output_data += temp_data + + # check if output_data is greater than limit and truncate if needed and break loop + if initial_limit_set and len(output_data) > limit: + output_data = output_data[:limit] + break + + # handle the case where exactly the limit amount of data was returned last loop + # or check if we received less than the required limit and exit the loop + if not len(temp_data) or len(temp_data) < limit: + # exit the while loop + break + + # set our start timestamp using the last value in the array + # and increment next call by our timeframe + start_ts = temp_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break + + # sleep after every 3rd call to be kind to the API + idx += 1 + if idx % 3 == 0: + await asyncio.sleep(1) + + return output_data + _historical_klines.__doc__ = Client._historical_klines.__doc__ + + async def get_historical_klines_generator(self, symbol, interval, start_str=None, end_str=None, limit=1000, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + return self._historical_klines_generator( + symbol, interval, start_str, end_str=end_str, limit=limit, klines_type=klines_type + ) + get_historical_klines_generator.__doc__ = Client.get_historical_klines_generator.__doc__ + + async def _historical_klines_generator(self, symbol, interval, start_str=None, end_str=None, limit=1000, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT): + + # convert interval to useful value in seconds + timeframe = interval_to_milliseconds(interval) + + # if a start time was passed convert it + start_ts = convert_ts_str(start_str) + + # establish first available start timestamp + if start_ts is not None: + first_valid_ts = await self._get_earliest_valid_timestamp(symbol, interval, klines_type) + start_ts = max(start_ts, first_valid_ts) + + # if an end time was passed convert it + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return + + idx = 0 + while True: + # fetch the klines from start_ts up to max 500 entries or the end_ts if set + output_data = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=limit, + startTime=start_ts, + endTime=end_ts + ) + + # yield data + if output_data: + for o in output_data: + yield o + + # handle the case where exactly the limit amount of data was returned last loop + # check if we received less than the required limit and exit the loop + if not len(output_data) or len(output_data) < limit: + # exit the while loop + break + + # increment next call by our timeframe + start_ts = output_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break + + # sleep after every 3rd call to be kind to the API + idx += 1 + if idx % 3 == 0: + await asyncio.sleep(1) + _historical_klines_generator.__doc__ = Client._historical_klines_generator.__doc__ + + async def get_avg_price(self, **params): + return await self._get('avgPrice', data=params, version=self.PRIVATE_API_VERSION) + get_avg_price.__doc__ = Client.get_avg_price.__doc__ + + async def get_ticker(self, **params): + return await self._get('ticker/24hr', data=params, version=self.PRIVATE_API_VERSION) + get_ticker.__doc__ = Client.get_ticker.__doc__ + + async def get_symbol_ticker(self, **params): + return await self._get('ticker/price', data=params, version=self.PRIVATE_API_VERSION) + get_symbol_ticker.__doc__ = Client.get_symbol_ticker.__doc__ + + async def get_symbol_ticker_window(self, **params): + return await self._get('ticker', data=params, version=self.PRIVATE_API_VERSION) + get_symbol_ticker_window.__doc__ = Client.get_symbol_ticker_window.__doc__ + + async def get_orderbook_ticker(self, **params): + return await self._get('ticker/bookTicker', data=params, version=self.PRIVATE_API_VERSION) + get_orderbook_ticker.__doc__ = Client.get_orderbook_ticker.__doc__ + + # Account Endpoints + + async def create_order(self, **params): + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._post('order', True, data=params) + create_order.__doc__ = Client.create_order.__doc__ + + async def order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + params.update({ + 'type': self.ORDER_TYPE_LIMIT, + 'timeInForce': timeInForce + }) + return await self.create_order(**params) + order_limit.__doc__ = Client.order_limit.__doc__ + + async def order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + params.update({ + 'side': self.SIDE_BUY, + }) + return await self.order_limit(timeInForce=timeInForce, **params) + order_limit_buy.__doc__ = Client.order_limit_buy.__doc__ + + async def order_limit_sell(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + params.update({ + 'side': self.SIDE_SELL + }) + return await self.order_limit(timeInForce=timeInForce, **params) + order_limit_sell.__doc__ = Client.order_limit_sell.__doc__ + + async def order_market(self, **params): + params.update({ + 'type': self.ORDER_TYPE_MARKET + }) + return await self.create_order(**params) + order_market.__doc__ = Client.order_market.__doc__ + + async def order_market_buy(self, **params): + params.update({ + 'side': self.SIDE_BUY + }) + return await self.order_market(**params) + order_market_buy.__doc__ = Client.order_market_buy.__doc__ + + async def order_market_sell(self, **params): + params.update({ + 'side': self.SIDE_SELL + }) + return await self.order_market(**params) + order_market_sell.__doc__ = Client.order_market_sell.__doc__ + + async def create_oco_order(self, **params): + return await self._post('order/oco', True, data=params) + create_oco_order.__doc__ = Client.create_oco_order.__doc__ + + async def order_oco_buy(self, **params): + params.update({ + 'side': self.SIDE_BUY + }) + return await self.create_oco_order(**params) + order_oco_buy.__doc__ = Client.order_oco_buy.__doc__ + + async def order_oco_sell(self, **params): + params.update({ + 'side': self.SIDE_SELL + }) + return await self.create_oco_order(**params) + order_oco_sell.__doc__ = Client.order_oco_sell.__doc__ + + async def create_test_order(self, **params): + return await self._post('order/test', True, data=params) + create_test_order.__doc__ = Client.create_test_order.__doc__ + + async def get_order(self, **params): + return await self._get('order', True, data=params) + get_order.__doc__ = Client.get_order.__doc__ + + async def get_all_orders(self, **params): + return await self._get('allOrders', True, data=params) + get_all_orders.__doc__ = Client.get_all_orders.__doc__ + + async def cancel_order(self, **params): + return await self._delete('order', True, data=params) + cancel_order.__doc__ = Client.cancel_order.__doc__ + + async def get_open_orders(self, **params): + return await self._get('openOrders', True, data=params) + get_open_orders.__doc__ = Client.get_open_orders.__doc__ + + async def get_open_oco_orders(self, **params): + return await self._get('openOrderList', True, data=params) + get_open_oco_orders.__doc__ = Client.get_open_oco_orders.__doc__ + + # User Stream Endpoints + async def get_account(self, **params): + return await self._get('account', True, data=params) + get_account.__doc__ = Client.get_account.__doc__ + + async def get_asset_balance(self, asset, **params): + res = await self.get_account(**params) + # find asset balance in list of balances + if "balances" in res: + for bal in res['balances']: + if bal['asset'].lower() == asset.lower(): + return bal + return None + get_asset_balance.__doc__ = Client.get_asset_balance.__doc__ + + async def get_my_trades(self, **params): + return await self._get('myTrades', True, data=params) + get_my_trades.__doc__ = Client.get_my_trades.__doc__ + + async def get_current_order_count(self): + return await self._get('rateLimit/order', True) + get_current_order_count.__doc__ = Client.get_current_order_count.__doc__ + + async def get_prevented_matches(self, **params): + return await self._get('myPreventedMatches', True, data=params) + get_prevented_matches.__doc__ = Client.get_prevented_matches.__doc__ + + async def get_allocations(self, **params): + return await self._get('myAllocations', True, data=params) + get_allocations.__doc__ = Client.get_allocations.__doc__ + + async def get_system_status(self): + return await self._request_margin_api('get', 'system/status') + get_system_status.__doc__ = Client.get_system_status.__doc__ + + async def get_account_status(self, **params): + return await self._request_margin_api('get', 'account/status', True, data=params) + get_account_status.__doc__ = Client.get_account_status.__doc__ + + async def get_account_api_trading_status(self, **params): + return await self._request_margin_api('get', 'account/apiTradingStatus', True, data=params) + get_account_api_trading_status.__doc__ = Client.get_account_api_trading_status.__doc__ + + async def get_account_api_permissions(self, **params): + return await self._request_margin_api('get', 'account/apiRestrictions', True, data=params) + get_account_api_permissions.__doc__ = Client.get_account_api_permissions.__doc__ + + async def get_dust_assets(self, **params): + return await self._request_margin_api('post', 'asset/dust-btc', True, data=params) + get_dust_assets.__doc__ = Client.get_dust_assets.__doc__ + + async def get_dust_log(self, **params): + return await self._request_margin_api('get', 'asset/dribblet', True, data=params) + get_dust_log.__doc__ = Client.get_dust_log.__doc__ + + async def transfer_dust(self, **params): + return await self._request_margin_api('post', 'asset/dust', True, data=params) + transfer_dust.__doc__ = Client.transfer_dust.__doc__ + + async def get_asset_dividend_history(self, **params): + return await self._request_margin_api('get', 'asset/assetDividend', True, data=params) + get_asset_dividend_history.__doc__ = Client.get_asset_dividend_history.__doc__ + + async def make_universal_transfer(self, **params): + return await self._request_margin_api('post', 'asset/transfer', signed=True, data=params) + make_universal_transfer.__doc__ = Client.make_universal_transfer.__doc__ + + async def query_universal_transfer_history(self, **params): + return await self._request_margin_api('get', 'asset/transfer', signed=True, data=params) + query_universal_transfer_history.__doc__ = Client.query_universal_transfer_history.__doc__ + + async def get_trade_fee(self, **params): + if self.tld == 'us': + endpoint = 'asset/query/trading-fee' + else: + endpoint = 'asset/tradeFee' + return await self._request_margin_api('get', endpoint, True, data=params) + get_trade_fee.__doc__ = Client.get_trade_fee.__doc__ + + async def get_asset_details(self, **params): + return await self._request_margin_api('get', 'asset/assetDetail', True, data=params) + get_asset_details.__doc__ = Client.get_asset_details.__doc__ + + async def get_spot_delist_schedule(self, **params): + return await self._request_margin_api('get', '/spot/delist-schedule', signed=True, data=params) + + # Withdraw Endpoints + + async def withdraw(self, **params): + # force a name for the withdrawal if one not set + if 'coin' in params and 'name' not in params: + params['name'] = params['coin'] + return await self._request_margin_api('post', 'capital/withdraw/apply', True, data=params) + withdraw.__doc__ = Client.withdraw.__doc__ + + async def get_deposit_history(self, **params): + return await self._request_margin_api('get', 'capital/deposit/hisrec', True, data=params) + get_deposit_history.__doc__ = Client.get_deposit_history.__doc__ + + async def get_withdraw_history(self, **params): + return await self._request_margin_api('get', 'capital/withdraw/history', True, data=params) + get_withdraw_history.__doc__ = Client.get_withdraw_history.__doc__ + + async def get_withdraw_history_id(self, withdraw_id, **params): + result = await self.get_withdraw_history(**params) + + for entry in result: + if 'id' in entry and entry['id'] == withdraw_id: + return entry + + raise Exception("There is no entry with withdraw id", result) + get_withdraw_history_id.__doc__ = Client.get_withdraw_history_id.__doc__ + + async def get_deposit_address(self, coin: str, network: Optional[str] = None, **params): + params['coin'] = coin + if network: + params['network'] = network + return await self._request_margin_api('get', 'capital/deposit/address', True, data=params) + get_deposit_address.__doc__ = Client.get_deposit_address.__doc__ + + # User Stream Endpoints + + async def stream_get_listen_key(self): + res = await self._post('userDataStream', False, data={}) + return res['listenKey'] + stream_get_listen_key.__doc__ = Client.stream_get_listen_key.__doc__ + + async def stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._put('userDataStream', False, data=params) + stream_keepalive.__doc__ = Client.stream_keepalive.__doc__ + + async def stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._delete('userDataStream', False, data=params) + stream_close.__doc__ = Client.stream_close.__doc__ + + # Margin Trading Endpoints + async def get_margin_account(self, **params): + return await self._request_margin_api('get', 'margin/account', True, data=params) + get_margin_account.__doc__ = Client.get_margin_account.__doc__ + + async def get_isolated_margin_account(self, **params): + return await self._request_margin_api('get', 'margin/isolated/account', True, data=params) + get_isolated_margin_account.__doc__ = Client.get_isolated_margin_account.__doc__ + + async def enable_isolated_margin_account(self, **params): + return await self._request_margin_api('post', 'margin/isolated/account', True, data=params) + enable_isolated_margin_account.__doc__ = Client.enable_isolated_margin_account.__doc__ + + async def disable_isolated_margin_account(self, **params): + return await self._request_margin_api('delete', 'margin/isolated/account', True, data=params) + disable_isolated_margin_account.__doc__ = Client.disable_isolated_margin_account.__doc__ + + async def get_enabled_isolated_margin_account_limit(self, **params): + return await self._request_margin_api('get', 'margin/isolated/accountLimit', True, data=params) + get_enabled_isolated_margin_account_limit.__doc__ = Client.get_enabled_isolated_margin_account_limit.__doc__ + + async def get_margin_dustlog(self, **params): + return await self._request_margin_api('get', 'margin/dribblet', True, data=params) + get_margin_dustlog.__doc__ = Client.get_margin_dustlog.__doc__ + + async def get_margin_dust_assets(self, **params): + return await self._request_margin_api('get', 'margin/dust', True, data=params) + get_margin_dust_assets.__doc__ = Client.get_margin_dust_assets.__doc__ + + async def transfer_margin_dust(self, **params): + return await self._request_margin_api('post', 'margin/dust', True, data=params) + transfer_margin_dust.__doc__ = Client.transfer_margin_dust.__doc__ + + async def get_cross_margin_collateral_ratio(self, **params): + return await self._request_margin_api('get', 'margin/crossMarginCollateralRatio', True, data=params) + get_cross_margin_collateral_ratio.__doc__ = Client.get_cross_margin_collateral_ratio.__doc__ + + async def get_small_liability_exchange_assets(self, **params): + return await self._request_margin_api('get', 'margin/exchange-small-liability', True, data=params) + get_small_liability_exchange_assets.__doc__ = Client.get_small_liability_exchange_assets.__doc__ + + async def exchange_small_liability_assets(self, **params): + return await self._request_margin_api('post', 'margin/exchange-small-liability', True, data=params) + exchange_small_liability_assets.__doc__ = Client.exchange_small_liability_assets.__doc__ + + async def get_small_liability_exchange_history(self, **params): + return await self._request_margin_api('get', 'margin/exchange-small-liability-history', True, data=params) + get_small_liability_exchange_history.__doc__ = Client.get_small_liability_exchange_history.__doc__ + + async def get_future_hourly_interest_rate(self, **params): + return await self._request_margin_api('get', 'margin/next-hourly-interest-rate', True, data=params) + get_future_hourly_interest_rate.__doc__ = Client.get_future_hourly_interest_rate.__doc__ + + async def get_margin_capital_flow(self, **params): + return await self._request_margin_api('get', 'margin/capital-flow', True, data=params) + get_margin_capital_flow.__doc__ = Client.get_margin_capital_flow.__doc__ + + async def get_margin_delist_schedule(self, **params): + return await self._request_margin_api('get', 'margin/delist-schedule', True, data=params) + get_margin_delist_schedule.__doc__ = Client.get_margin_delist_schedule.__doc__ + + async def get_margin_asset(self, **params): + return await self._request_margin_api('get', 'margin/asset', data=params) + get_margin_asset.__doc__ = Client.get_margin_asset.__doc__ + + async def get_margin_symbol(self, **params): + return await self._request_margin_api('get', 'margin/pair', data=params) + get_margin_symbol.__doc__ = Client.get_margin_symbol.__doc__ + + async def get_margin_all_assets(self, **params): + return await self._request_margin_api('get', 'margin/allAssets', data=params) + get_margin_all_assets.__doc__ = Client.get_margin_all_assets.__doc__ + + async def get_margin_all_pairs(self, **params): + return await self._request_margin_api('get', 'margin/allPairs', data=params) + get_margin_all_pairs.__doc__ = Client.get_margin_all_pairs.__doc__ + + async def create_isolated_margin_account(self, **params): + return await self._request_margin_api('post', 'margin/isolated/create', signed=True, data=params) + create_isolated_margin_account.__doc__ = Client.create_isolated_margin_account.__doc__ + + async def get_isolated_margin_symbol(self, **params): + return await self._request_margin_api('get', 'margin/isolated/pair', signed=True, data=params) + get_isolated_margin_symbol.__doc__ = Client.get_isolated_margin_symbol.__doc__ + + async def get_all_isolated_margin_symbols(self, **params): + return await self._request_margin_api('get', 'margin/isolated/allPairs', signed=True, data=params) + get_all_isolated_margin_symbols.__doc__ = Client.get_all_isolated_margin_symbols.__doc__ + + async def get_isolated_margin_fee_data(self, **params): + return await self._request_margin_api('get', 'margin/isolatedMarginData', True, data=params) + get_isolated_margin_fee_data.__doc__ = Client.get_isolated_margin_fee_data.__doc__ + + async def get_isolated_margin_tier_data(self, **params): + return await self._request_margin_api('get', 'margin/isolatedMarginTier', True, data=params) + get_isolated_margin_tier_data.__doc__ = Client.get_isolated_margin_tier_data.__doc__ + + async def margin_manual_liquidation(self, **params): + return await self._request_margin_api('get', 'margin/manual-liquidation', True, data=params) + margin_manual_liquidation.__doc__ = Client.margin_manual_liquidation.__doc__ + + async def toggle_bnb_burn_spot_margin(self, **params): + return await self._request_margin_api('post', 'bnbBurn', signed=True, data=params) + toggle_bnb_burn_spot_margin.__doc__ = Client.toggle_bnb_burn_spot_margin.__doc__ + + async def get_bnb_burn_spot_margin(self, **params): + return await self._request_margin_api('get', 'bnbBurn', signed=True, data=params) + get_bnb_burn_spot_margin.__doc__ = Client.get_bnb_burn_spot_margin.__doc__ + + async def get_margin_price_index(self, **params): + return await self._request_margin_api('get', 'margin/priceIndex', data=params) + get_margin_price_index.__doc__ = Client.get_margin_price_index.__doc__ + + async def transfer_margin_to_spot(self, **params): + params['type'] = 2 + return await self._request_margin_api('post', 'margin/transfer', signed=True, data=params) + transfer_margin_to_spot.__doc__ = Client.transfer_margin_to_spot.__doc__ + + async def transfer_spot_to_margin(self, **params): + params['type'] = 1 + return await self._request_margin_api('post', 'margin/transfer', signed=True, data=params) + transfer_spot_to_margin.__doc__ = Client.transfer_spot_to_margin.__doc__ + + async def transfer_isolated_margin_to_spot(self, **params): + params['transFrom'] = "ISOLATED_MARGIN" + params['transTo'] = "SPOT" + return await self._request_margin_api('post', 'margin/isolated/transfer', signed=True, data=params) + transfer_isolated_margin_to_spot.__doc__ = Client.transfer_isolated_margin_to_spot.__doc__ + + async def transfer_spot_to_isolated_margin(self, **params): + params['transFrom'] = "SPOT" + params['transTo'] = "ISOLATED_MARGIN" + return await self._request_margin_api('post', 'margin/isolated/transfer', signed=True, data=params) + transfer_spot_to_isolated_margin.__doc__ = Client.transfer_spot_to_isolated_margin.__doc__ + + async def create_margin_loan(self, **params): + return await self._request_margin_api('post', 'margin/loan', signed=True, data=params) + create_margin_loan.__doc__ = Client.create_margin_loan.__doc__ + + async def repay_margin_loan(self, **params): + return await self._request_margin_api('post', 'margin/repay', signed=True, data=params) + repay_margin_loan.__doc__ = Client.repay_margin_loan.__doc__ + + async def create_margin_order(self, **params): + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._request_margin_api('post', 'margin/order', signed=True, data=params) + create_margin_order.__doc__ = Client.create_margin_order.__doc__ + + async def cancel_margin_order(self, **params): + return await self._request_margin_api('delete', 'margin/order', signed=True, data=params) + cancel_margin_order.__doc__ = Client.cancel_margin_order.__doc__ + + async def set_margin_max_leverage(self, **params): + return await self._request_margin_api('post', 'margin/max-leverage', signed=True, data=params) + set_margin_max_leverage.__doc__ = Client.set_margin_max_leverage.__doc__ + + async def get_margin_transfer_history(self, **params): + return await self._request_margin_api('get', 'margin/transfer', signed=True, data=params) + get_margin_transfer_history.__doc__ = Client.get_margin_transfer_history.__doc__ + + async def get_margin_loan_details(self, **params): + return await self._request_margin_api('get', 'margin/loan', signed=True, data=params) + get_margin_loan_details.__doc__ = Client.get_margin_loan_details.__doc__ + + async def get_margin_repay_details(self, **params): + return await self._request_margin_api('get', 'margin/repay', signed=True, data=params) + + async def get_cross_margin_data(self, **params): + return await self._request_margin_api('get', 'margin/crossMarginData', signed=True, data=params) + + async def get_margin_interest_history(self, **params): + return await self._request_margin_api('get', 'margin/interestHistory', signed=True, data=params) + + async def get_margin_force_liquidation_rec(self, **params): + return await self._request_margin_api('get', 'margin/forceLiquidationRec', signed=True, data=params) + + async def get_margin_order(self, **params): + return await self._request_margin_api('get', 'margin/order', signed=True, data=params) + + async def get_open_margin_orders(self, **params): + return await self._request_margin_api('get', 'margin/openOrders', signed=True, data=params) + + async def get_all_margin_orders(self, **params): + return await self._request_margin_api('get', 'margin/allOrders', signed=True, data=params) + + async def get_margin_trades(self, **params): + return await self._request_margin_api('get', 'margin/myTrades', signed=True, data=params) + + async def get_max_margin_loan(self, **params): + return await self._request_margin_api('get', 'margin/maxBorrowable', signed=True, data=params) + + async def get_max_margin_transfer(self, **params): + return await self._request_margin_api('get', 'margin/maxTransferable', signed=True, data=params) + + # Margin OCO + + async def create_margin_oco_order(self, **params): + return await self._request_margin_api('post', 'margin/order/oco', signed=True, data=params) + + async def cancel_margin_oco_order(self, **params): + return await self._request_margin_api('delete', 'margin/orderList', signed=True, data=params) + + async def get_margin_oco_order(self, **params): + return await self._request_margin_api('get', 'margin/orderList', signed=True, data=params) + + async def get_open_margin_oco_orders(self, **params): + return await self._request_margin_api('get', 'margin/openOrderList', signed=True, data=params) + + # Cross-margin + + async def margin_stream_get_listen_key(self): + res = await self._request_margin_api('post', 'userDataStream', signed=False, data={}) + return res['listenKey'] + + async def margin_stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_margin_api('put', 'userDataStream', signed=False, data=params) + + async def margin_stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_margin_api('delete', 'userDataStream', signed=False, data=params) + + # Isolated margin + + async def isolated_margin_stream_get_listen_key(self, symbol): + params = { + 'symbol': symbol + } + res = await self._request_margin_api('post', 'userDataStream/isolated', signed=False, data=params) + return res['listenKey'] + + async def isolated_margin_stream_keepalive(self, symbol, listenKey): + params = { + 'symbol': symbol, + 'listenKey': listenKey + } + return await self._request_margin_api('put', 'userDataStream/isolated', signed=False, data=params) + + async def isolated_margin_stream_close(self, symbol, listenKey): + params = { + 'symbol': symbol, + 'listenKey': listenKey + } + return await self._request_margin_api('delete', 'userDataStream/isolated', signed=False, data=params) + + # Simple Earn Endpoints + + async def get_simple_earn_flexible_product_list(self, **params): + return await self._request_margin_api('get', 'simple-earn/flexible/list', signed=True, data=params) + get_simple_earn_flexible_product_list.__doc__ = Client.get_simple_earn_flexible_product_list.__doc__ + + async def get_simple_earn_locked_product_list(self, **params): + return await self._request_margin_api('get', 'simple-earn/locked/list', signed=True, data=params) + get_simple_earn_locked_product_list.__doc__ = Client.get_simple_earn_locked_product_list.__doc__ + + async def subscribe_simple_earn_flexible_product(self, **params): + return await self._request_margin_api('post', 'simple-earn/flexible/subscribe', signed=True, data=params) + subscribe_simple_earn_flexible_product.__doc__ = Client.subscribe_simple_earn_flexible_product.__doc__ + + async def subscribe_simple_earn_locked_product(self, **params): + return await self._request_margin_api('post', 'simple-earn/locked/subscribe', signed=True, data=params) + subscribe_simple_earn_locked_product.__doc__ = Client.subscribe_simple_earn_locked_product.__doc__ + + async def redeem_simple_earn_flexible_product(self, **params): + return await self._request_margin_api('post', 'simple-earn/flexible/redeem', signed=True, data=params) + redeem_simple_earn_flexible_product.__doc__ = Client.redeem_simple_earn_flexible_product.__doc__ + + async def redeem_simple_earn_locked_product(self, **params): + return await self._request_margin_api('post', 'simple-earn/locked/redeem', signed=True, data=params) + redeem_simple_earn_locked_product.__doc__ = Client.redeem_simple_earn_locked_product.__doc__ + + async def get_simple_earn_flexible_product_position(self, **params): + return await self._request_margin_api('get', 'simple-earn/flexible/position', signed=True, data=params) + get_simple_earn_flexible_product_position.__doc__ = Client.get_simple_earn_flexible_product_position.__doc__ + + async def get_simple_earn_locked_product_position(self, **params): + return await self._request_margin_api('get', 'simple-earn/locked/position', signed=True, data=params) + get_simple_earn_locked_product_position.__doc__ = Client.get_simple_earn_locked_product_position.__doc__ + + async def get_simple_earn_account(self, **params): + return await self._request_margin_api('get', 'simple-earn/account', signed=True, data=params) + get_simple_earn_account.__doc__ = Client.get_simple_earn_account.__doc__ + + # Lending Endpoints + + async def get_fixed_activity_project_list(self, **params): + return await self._request_margin_api('get', 'lending/project/list', signed=True, data=params) + + async def change_fixed_activity_to_daily_position(self, **params): + return await self._request_margin_api('post', 'lending/positionChanged', signed=True, data=params) + + # Staking Endpoints + + async def get_staking_product_list(self, **params): + return await self._request_margin_api('get', 'staking/productList', signed=True, data=params) + + async def purchase_staking_product(self, **params): + return await self._request_margin_api('post', 'staking/purchase', signed=True, data=params) + + async def redeem_staking_product(self, **params): + return await self._request_margin_api('post', 'staking/redeem', signed=True, data=params) + + async def get_staking_position(self, **params): + return await self._request_margin_api('get', 'staking/position', signed=True, data=params) + + async def get_staking_purchase_history(self, **params): + return await self._request_margin_api('get', 'staking/purchaseRecord', signed=True, data=params) + + async def set_auto_staking(self, **params): + return await self._request_margin_api('post', 'staking/setAutoStaking', signed=True, data=params) + + async def get_personal_left_quota(self, **params): + return await self._request_margin_api('get', 'staking/personalLeftQuota', signed=True, data=params) + + # US Staking Endpoints + + async def get_staking_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("get", "staking/asset", True, data=params) + get_staking_asset_us.__doc__ = Client.get_staking_asset_us.__doc__ + + async def stake_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("post", "staking/stake", True, data=params) + stake_asset_us.__doc__ = Client.stake_asset_us.__doc__ + + async def unstake_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("post", "staking/unstake", True, data=params) + unstake_asset_us.__doc__ = Client.unstake_asset_us.__doc__ + + async def get_staking_balance_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("get", "staking/stakingBalance", True, data=params) + get_staking_balance_us.__doc__ = Client.get_staking_balance_us.__doc__ + + async def get_staking_history_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("get", "staking/history", True, data=params) + get_staking_history_us.__doc__ = Client.get_staking_history_us.__doc__ + + async def get_staking_rewards_history_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("get", "staking/stakingRewardsHistory", True, data=params) + get_staking_rewards_history_us.__doc__ = Client.get_staking_rewards_history_us.__doc__ + + # Sub Accounts + + async def get_sub_account_list(self, **params): + return await self._request_margin_api('get', 'sub-account/list', True, data=params) + + async def get_sub_account_transfer_history(self, **params): + return await self._request_margin_api('get', 'sub-account/sub/transfer/history', True, data=params) + + async def get_sub_account_futures_transfer_history(self, **params): + return await self._request_margin_api('get', 'sub-account/futures/internalTransfer', True, data=params) + + async def create_sub_account_futures_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/futures/internalTransfer', True, data=params) + + async def get_sub_account_assets(self, **params): + return await self._request_margin_api('get', 'sub-account/assets', True, data=params, version=4) + + async def query_subaccount_spot_summary(self, **params): + return await self._request_margin_api('get', 'sub-account/spotSummary', True, data=params) + + async def get_subaccount_deposit_address(self, **params): + return await self._request_margin_api('get', 'capital/deposit/subAddress', True, data=params) + + async def get_subaccount_deposit_history(self, **params): + return await self._request_margin_api('get', 'capital/deposit/subHisrec', True, data=params) + + async def get_subaccount_futures_margin_status(self, **params): + return await self._request_margin_api('get', 'sub-account/status', True, data=params) + + async def enable_subaccount_margin(self, **params): + return await self._request_margin_api('post', 'sub-account/margin/enable', True, data=params) + + async def get_subaccount_margin_details(self, **params): + return await self._request_margin_api('get', 'sub-account/margin/account', True, data=params) + + async def get_subaccount_margin_summary(self, **params): + return await self._request_margin_api('get', 'sub-account/margin/accountSummary', True, data=params) + + async def enable_subaccount_futures(self, **params): + return await self._request_margin_api('post', 'sub-account/futures/enable', True, data=params) + + async def get_subaccount_futures_details(self, **params): + return await self._request_margin_api('get', 'sub-account/futures/account', True, data=params, version=2) + + async def get_subaccount_futures_summary(self, **params): + return await self._request_margin_api('get', 'sub-account/futures/accountSummary', True, data=params, version=2) + + async def get_subaccount_futures_positionrisk(self, **params): + return await self._request_margin_api('get', 'sub-account/futures/positionRisk', True, data=params, version=2) + + async def make_subaccount_futures_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/futures/transfer', True, data=params) + + async def make_subaccount_margin_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/margin/transfer', True, data=params) + + async def make_subaccount_to_subaccount_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/transfer/subToSub', True, data=params) + + async def make_subaccount_to_master_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/transfer/subToMaster', True, data=params) + + async def get_subaccount_transfer_history(self, **params): + return await self._request_margin_api('get', 'sub-account/transfer/subUserHistory', True, data=params) + + async def make_subaccount_universal_transfer(self, **params): + return await self._request_margin_api('post', 'sub-account/universalTransfer', True, data=params) + + async def get_universal_transfer_history(self, **params): + return await self._request_margin_api('get', 'sub-account/universalTransfer', True, data=params) + + # Futures API + + async def futures_ping(self): + return await self._request_futures_api('get', 'ping') + + async def futures_time(self): + return await self._request_futures_api('get', 'time') + + async def futures_exchange_info(self): + return await self._request_futures_api('get', 'exchangeInfo') + + async def futures_order_book(self, **params): + return await self._request_futures_api('get', 'depth', data=params) + + async def futures_recent_trades(self, **params): + return await self._request_futures_api('get', 'trades', data=params) + + async def futures_historical_trades(self, **params): + return await self._request_futures_api('get', 'historicalTrades', data=params) + + async def futures_aggregate_trades(self, **params): + return await self._request_futures_api('get', 'aggTrades', data=params) + + async def futures_klines(self, **params): + return await self._request_futures_api('get', 'klines', data=params) + + async def futures_continous_klines(self, **params): + return await self._request_futures_api('get', 'continuousKlines', data=params) + + async def futures_historical_klines(self, symbol, interval, start_str, end_str=None, limit=500): + return await self._historical_klines(symbol, interval, start_str, end_str=end_str, limit=limit, klines_type=HistoricalKlinesType.FUTURES) + + async def futures_historical_klines_generator(self, symbol, interval, start_str, end_str=None): + return self._historical_klines_generator(symbol, interval, start_str, end_str=end_str, klines_type=HistoricalKlinesType.FUTURES) + + async def futures_mark_price(self, **params): + return await self._request_futures_api('get', 'premiumIndex', data=params) + + async def futures_funding_rate(self, **params): + return await self._request_futures_api('get', 'fundingRate', data=params) + + async def futures_top_longshort_account_ratio(self, **params): + return await self._request_futures_data_api('get', 'topLongShortAccountRatio', data=params) + + async def futures_top_longshort_position_ratio(self, **params): + return await self._request_futures_data_api('get', 'topLongShortPositionRatio', data=params) + + async def futures_global_longshort_ratio(self, **params): + return await self._request_futures_data_api('get', 'globalLongShortAccountRatio', data=params) + + async def futures_ticker(self, **params): + return await self._request_futures_api('get', 'ticker/24hr', data=params) + + async def futures_symbol_ticker(self, **params): + return await self._request_futures_api('get', 'ticker/price', data=params) + + async def futures_orderbook_ticker(self, **params): + return await self._request_futures_api('get', 'ticker/bookTicker', data=params) + + async def futures_liquidation_orders(self, **params): + return await self._request_futures_api('get', 'forceOrders', signed=True, data=params) + + async def futures_api_trading_status(self, **params): + return await self._request_futures_api('get', 'apiTradingStatus', signed=True, data=params) + + async def futures_commission_rate(self, **params): + return await self._request_futures_api('get', 'commissionRate', signed=True, data=params) + + async def futures_adl_quantile_estimate(self, **params): + return await self._request_futures_api('get', 'adlQuantile', signed=True, data=params) + + async def futures_open_interest(self, **params): + return await self._request_futures_api('get', 'openInterest', data=params) + + async def futures_index_info(self, **params): + return await self._request_futures_api('get', 'indexInfo', data=params) + + async def futures_open_interest_hist(self, **params): + return await self._request_futures_data_api('get', 'openInterestHist', data=params) + + async def futures_leverage_bracket(self, **params): + return await self._request_futures_api('get', 'leverageBracket', True, data=params) + + async def futures_account_transfer(self, **params): + return await self._request_margin_api('post', 'futures/transfer', True, data=params) + + async def transfer_history(self, **params): + return await self._request_margin_api('get', 'futures/transfer', True, data=params) + + async def futures_loan_borrow_history(self, **params): + return await self._request_margin_api('get', 'futures/loan/borrow/history', True, data=params) + + async def futures_loan_repay_history(self, **params): + return await self._request_margin_api('get', 'futures/loan/repay/history', True, data=params) + + async def futures_loan_wallet(self, **params): + return await self._request_margin_api('get', 'futures/loan/wallet', True, data=params, version=2) + + async def futures_cross_collateral_adjust_history(self, **params): + return await self._request_margin_api('get', 'futures/loan/adjustCollateral/history', True, data=params) + + async def futures_cross_collateral_liquidation_history(self, **params): + return await self._request_margin_api('get', 'futures/loan/liquidationHistory', True, data=params) + + async def futures_loan_interest_history(self, **params): + return await self._request_margin_api('get', 'futures/loan/interestHistory', True, data=params) + + async def futures_create_order(self, **params): + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_futures_api('post', 'order', True, data=params) + + async def futures_create_test_order(self, **params): + return await self._request_futures_api('post', 'order/test', True, data=params) + + async def futures_place_batch_order(self, **params): + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace('%27', '%22') + params['batchOrders'] = query_string[12:] + return await self._request_futures_api('post', 'batchOrders', True, data=params) + + async def futures_get_order(self, **params): + return await self._request_futures_api('get', 'order', True, data=params) + + async def futures_get_open_orders(self, **params): + return await self._request_futures_api('get', 'openOrders', True, data=params) + + async def futures_get_all_orders(self, **params): + return await self._request_futures_api('get', 'allOrders', True, data=params) + + async def futures_cancel_order(self, **params): + return await self._request_futures_api('delete', 'order', True, data=params) + + async def futures_cancel_all_open_orders(self, **params): + return await self._request_futures_api('delete', 'allOpenOrders', True, data=params) + + async def futures_cancel_orders(self, **params): + return await self._request_futures_api('delete', 'batchOrders', True, data=params) + + async def futures_countdown_cancel_all(self, **params): + return await self._request_futures_api('post', 'countdownCancelAll', True, data=params) + + async def futures_account_balance(self, **params): + return await self._request_futures_api('get', 'balance', True, version=3, data=params) + + async def futures_account(self, **params): + return await self._request_futures_api('get', 'account', True, version=2, data=params) + + async def futures_change_leverage(self, **params): + return await self._request_futures_api('post', 'leverage', True, data=params) + + async def futures_change_margin_type(self, **params): + return await self._request_futures_api('post', 'marginType', True, data=params) + + async def futures_change_position_margin(self, **params): + return await self._request_futures_api('post', 'positionMargin', True, data=params) + + async def futures_position_margin_history(self, **params): + return await self._request_futures_api('get', 'positionMargin/history', True, data=params) + + async def futures_position_information(self, **params): + return await self._request_futures_api('get', 'positionRisk', True, version=3, data=params) + + async def futures_account_trades(self, **params): + return await self._request_futures_api('get', 'userTrades', True, data=params) + + async def futures_income_history(self, **params): + return await self._request_futures_api('get', 'income', True, data=params) + + async def futures_change_position_mode(self, **params): + return await self._request_futures_api('post', 'positionSide/dual', True, data=params) + + async def futures_get_position_mode(self, **params): + return await self._request_futures_api('get', 'positionSide/dual', True, data=params) + + async def futures_change_multi_assets_mode(self, multiAssetsMargin: bool): + params = { + 'multiAssetsMargin': 'true' if multiAssetsMargin else 'false' + } + return await self._request_futures_api('post', 'multiAssetsMargin', True, data=params) + + async def futures_get_multi_assets_mode(self): + return await self._request_futures_api('get', 'multiAssetsMargin', True, data={}) + + async def futures_stream_get_listen_key(self): + res = await self._request_futures_api('post', 'listenKey', signed=False, data={}) + return res['listenKey'] + + async def futures_stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_futures_api('put', 'listenKey', signed=False, data=params) + + async def futures_stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_futures_api('delete', 'listenKey', signed=False, data=params) + + # new methods + async def futures_account_config(self, **params): + return await self._request_futures_api('get', 'accountConfig', signed=True, version=1, data=params) + + async def futures_symbol_config(self, **params): + return await self._request_futures_api('get', 'symbolConfig', signed=True, version=1, data=params) + + # COIN Futures API + + async def futures_coin_ping(self): + return await self._request_futures_coin_api("get", "ping") + + async def futures_coin_time(self): + return await self._request_futures_coin_api("get", "time") + + async def futures_coin_exchange_info(self): + return await self._request_futures_coin_api("get", "exchangeInfo") + + async def futures_coin_order_book(self, **params): + return await self._request_futures_coin_api("get", "depth", data=params) + + async def futures_coin_recent_trades(self, **params): + return await self._request_futures_coin_api("get", "trades", data=params) + + async def futures_coin_historical_trades(self, **params): + return await self._request_futures_coin_api("get", "historicalTrades", data=params) + + async def futures_coin_aggregate_trades(self, **params): + return await self._request_futures_coin_api("get", "aggTrades", data=params) + + async def futures_coin_klines(self, **params): + return await self._request_futures_coin_api("get", "klines", data=params) + + async def futures_coin_continous_klines(self, **params): + return await self._request_futures_coin_api("get", "continuousKlines", data=params) + + async def futures_coin_index_price_klines(self, **params): + return await self._request_futures_coin_api("get", "indexPriceKlines", data=params) + + async def futures_coin_mark_price_klines(self, **params): + return await self._request_futures_coin_api("get", "markPriceKlines", data=params) + + async def futures_coin_mark_price(self, **params): + return await self._request_futures_coin_api("get", "premiumIndex", data=params) + + async def futures_coin_funding_rate(self, **params): + return await self._request_futures_coin_api("get", "fundingRate", data=params) + + async def futures_coin_ticker(self, **params): + return await self._request_futures_coin_api("get", "ticker/24hr", data=params) + + async def futures_coin_symbol_ticker(self, **params): + return await self._request_futures_coin_api("get", "ticker/price", data=params) + + async def futures_coin_orderbook_ticker(self, **params): + return await self._request_futures_coin_api("get", "ticker/bookTicker", data=params) + + async def futures_coin_liquidation_orders(self, **params): + return await self._request_futures_coin_api("get", "forceOrders", signed=True, data=params) + + async def futures_coin_open_interest(self, **params): + return await self._request_futures_coin_api("get", "openInterest", data=params) + + async def futures_coin_open_interest_hist(self, **params): + return await self._request_futures_coin_data_api("get", "openInterestHist", data=params) + + async def futures_coin_leverage_bracket(self, **params): + return await self._request_futures_coin_api( + "get", "leverageBracket", version=2, signed=True, data=params + ) + + async def new_transfer_history(self, **params): + return await self._request_margin_api("get", "asset/transfer", True, data=params) + + async def funding_wallet(self, **params): + return await self._request_margin_api("post", "asset/get-funding-asset", True, data=params) + + async def get_user_asset(self, **params): + return await self._request_margin_api("post", "asset/getUserAsset", True, data=params, version=3) + + async def universal_transfer(self, **params): + return await self._request_margin_api( + "post", "asset/transfer", signed=True, data=params + ) + + async def futures_coin_create_order(self, **params): + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_futures_coin_api("post", "order", True, data=params) + + async def futures_coin_place_batch_order(self, **params): + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace('%27', '%22') + params['batchOrders'] = query_string[12:] + + return await self._request_futures_coin_api('post', 'batchOrders', True, data=params) + + async def futures_coin_get_order(self, **params): + return await self._request_futures_coin_api("get", "order", True, data=params) + + async def futures_coin_get_open_orders(self, **params): + return await self._request_futures_coin_api("get", "openOrders", True, data=params) + + async def futures_coin_get_all_orders(self, **params): + return await self._request_futures_coin_api( + "get", "allOrders", signed=True, data=params + ) + + async def futures_coin_cancel_order(self, **params): + return await self._request_futures_coin_api( + "delete", "order", signed=True, data=params + ) + + async def futures_coin_cancel_all_open_orders(self, **params): + return await self._request_futures_coin_api( + "delete", "allOpenOrders", signed=True, data=params + ) + + async def futures_coin_cancel_orders(self, **params): + return await self._request_futures_coin_api( + "delete", "batchOrders", True, data=params + ) + + async def futures_coin_account_balance(self, **params): + return await self._request_futures_coin_api( + "get", "balance", signed=True, data=params + ) + + async def futures_coin_account(self, **params): + return await self._request_futures_coin_api( + "get", "account", signed=True, data=params + ) + + async def futures_coin_change_leverage(self, **params): + return await self._request_futures_coin_api( + "post", "leverage", signed=True, data=params + ) + + async def futures_coin_change_margin_type(self, **params): + return await self._request_futures_coin_api( + "post", "marginType", signed=True, data=params + ) + + async def futures_coin_change_position_margin(self, **params): + return await self._request_futures_coin_api( + "post", "positionMargin", True, data=params + ) + + async def futures_coin_position_margin_history(self, **params): + return await self._request_futures_coin_api( + "get", "positionMargin/history", True, data=params + ) + + async def futures_coin_position_information(self, **params): + return await self._request_futures_coin_api("get", "positionRisk", True, data=params) + + async def futures_coin_account_trades(self, **params): + return await self._request_futures_coin_api("get", "userTrades", True, data=params) + + async def futures_coin_income_history(self, **params): + return await self._request_futures_coin_api("get", "income", True, data=params) + + async def futures_coin_change_position_mode(self, **params): + return await self._request_futures_coin_api("post", "positionSide/dual", True, data=params) + + async def futures_coin_get_position_mode(self, **params): + return await self._request_futures_coin_api("get", "positionSide/dual", True, data=params) + + async def futures_coin_stream_get_listen_key(self): + res = await self._request_futures_coin_api('post', 'listenKey', signed=False, data={}) + return res['listenKey'] + + async def futures_coin_stream_keepalive(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_futures_coin_api('put', 'listenKey', signed=False, data=params) + + async def futures_coin_stream_close(self, listenKey): + params = { + 'listenKey': listenKey + } + return await self._request_futures_coin_api('delete', 'listenKey', signed=False, data=params) + + async def get_all_coins_info(self, **params): + return await self._request_margin_api('get', 'capital/config/getall', True, data=params) + + async def get_account_snapshot(self, **params): + return await self._request_margin_api('get', 'accountSnapshot', True, data=params) + + async def disable_fast_withdraw_switch(self, **params): + return await self._request_margin_api('post', 'disableFastWithdrawSwitch', True, data=params) + + async def enable_fast_withdraw_switch(self, **params): + return await self._request_margin_api('post', 'enableFastWithdrawSwitch', True, data=params) + + """ + ==================================================================================================================== + Options API + ==================================================================================================================== + """ + + # Quoting interface endpoints + + async def options_ping(self): + return await self._request_options_api('get', 'ping') + + async def options_time(self): + return await self._request_options_api('get', 'time') + + async def options_info(self): + return await self._request_options_api('get', 'optionInfo') + + async def options_exchange_info(self): + return await self._request_options_api('get', 'exchangeInfo') + + async def options_index_price(self, **params): + return await self._request_options_api('get', 'index', data=params) + + async def options_price(self, **params): + return await self._request_options_api('get', 'ticker', data=params) + + async def options_mark_price(self, **params): + return await self._request_options_api('get', 'mark', data=params) + + async def options_order_book(self, **params): + return await self._request_options_api('get', 'depth', data=params) + + async def options_klines(self, **params): + return await self._request_options_api('get', 'klines', data=params) + + async def options_recent_trades(self, **params): + return await self._request_options_api('get', 'trades', data=params) + + async def options_historical_trades(self, **params): + return await self._request_options_api('get', 'historicalTrades', data=params) + + # Account and trading interface endpoints + + async def options_account_info(self, **params): + return await self._request_options_api('get', 'account', signed=True, data=params) + + async def options_funds_transfer(self, **params): + return await self._request_options_api('post', 'transfer', signed=True, data=params) + + async def options_positions(self, **params): + return await self._request_options_api('get', 'position', signed=True, data=params) + + async def options_bill(self, **params): + return await self._request_options_api('post', 'bill', signed=True, data=params) + + async def options_place_order(self, **params): + if 'clientOrderId' not in params: + params['clientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_options_api('post', 'order', signed=True, data=params) + + async def options_place_batch_order(self, **params): + for order in params['batchOrders']: + if 'newClientOrderId' not in order: + order['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_options_api('post', 'batchOrders', signed=True, data=params) + + async def options_cancel_order(self, **params): + return await self._request_options_api('delete', 'order', signed=True, data=params) + + async def options_cancel_batch_order(self, **params): + return await self._request_options_api('delete', 'batchOrders', signed=True, data=params) + + async def options_cancel_all_orders(self, **params): + return await self._request_options_api('delete', 'allOpenOrders', signed=True, data=params) + + async def options_query_order(self, **params): + return await self._request_options_api('get', 'order', signed=True, data=params) + + async def options_query_pending_orders(self, **params): + return await self._request_options_api('get', 'openOrders', signed=True, data=params) + + async def options_query_order_history(self, **params): + return await self._request_options_api('get', 'historyOrders', signed=True, data=params) + + async def options_user_trades(self, **params): + return await self._request_options_api('get', 'userTrades', signed=True, data=params) + + # Fiat Endpoints + + async def get_fiat_deposit_withdraw_history(self, **params): + return await self._request_margin_api('get', 'fiat/orders', signed=True, data=params) + + async def get_fiat_payments_history(self, **params): + return await self._request_margin_api('get', 'fiat/payments', signed=True, data=params) + + # C2C Endpoints + + async def get_c2c_trade_history(self, **params): + return await self._request_margin_api('get', 'c2c/orderMatch/listUserOrderHistory', signed=True, data=params) + + # Pay Endpoints + + async def get_pay_trade_history(self, **params): + return await self._request_margin_api('get', 'pay/transactions', signed=True, data=params) + get_pay_trade_history.__doc__ = Client.get_pay_trade_history.__doc__ + + # Convert Endpoints + + async def get_convert_trade_history(self, **params): + return await self._request_margin_api('get', 'convert/tradeFlow', signed=True, data=params) + get_convert_trade_history.__doc__ = Client.get_convert_trade_history.__doc__ + + async def convert_request_quote(self, **params): + return await self._request_margin_api('post', 'convert/getQuote', signed=True, data=params) + convert_request_quote.__doc__ = Client.convert_request_quote.__doc__ + + async def convert_accept_quote(self, **params): + return await self._request_margin_api('post', 'convert/acceptQuote', signed=True, data=params) + convert_accept_quote.__doc__ = Client.convert_accept_quote.__doc__ + + """ + ==================================================================================================================== + PortfolioMargin API + ==================================================================================================================== + """ + + async def papi_get_balance(self, **params): + return await self._request_papi_api('get', 'balance', signed=True, data=params) + + async def papi_get_account(self, **params): + return await self._request_papi_api('get', 'account', signed=True, data=params) + + async def papi_get_margin_max_borrowable(self, **params): + return await self._request_papi_api('get', 'margin/maxBorrowable', signed=True, data=params) + + async def papi_get_margin_max_withdraw(self, **params): + return await self._request_papi_api('get', 'margin/maxWithdraw', signed=True, data=params) + + async def papi_get_um_position_risk(self, **params): + return await self._request_papi_api('get', 'um/positionRisk', signed=True, data=params) + + async def papi_get_cm_position_risk(self, **params): + return await self._request_papi_api('get', 'cm/positionRisk', signed=True, data=params) + + async def papi_set_um_leverage(self, **params): + return await self._request_papi_api('post', 'um/leverage', signed=True, data=params) + + + async def papi_set_cm_leverage(self, **params): + return await self._request_papi_api('post', 'cm/leverage', signed=True, data=params) + + + async def papi_change_um_position_side_dual(self, **params): + return await self._request_papi_api('post', 'um/positionSide/dual', signed=True, data=params) + + + async def papi_get_um_position_side_dual(self, **params): + return await self._request_papi_api('get', 'um/positionSide/dual', signed=True, data=params) + + + async def papi_get_cm_position_side_dual(self, **params): + return await self._request_papi_api('get', 'cm/positionSide/dual', signed=True, data=params) + + + async def papi_get_um_leverage_bracket(self, **params): + return await self._request_papi_api('get', 'um/leverageBracket', signed=True, data=params) + + + async def papi_get_cm_leverage_bracket(self, **params): + return await self._request_papi_api('get', 'cm/leverageBracket', signed=True, data=params) + + async def papi_get_um_api_trading_status(self, **params): + return await self._request_papi_api('get', 'um/apiTradingStatus', signed=True, data=params) + + + async def papi_get_um_comission_rate(self, **params): + return await self._request_papi_api('get', 'um/commissionRate', signed=True, data=params) + + + async def papi_get_cm_comission_rate(self, **params): + return await self._request_papi_api('get', 'cm/commissionRate', signed=True, data=params) + + async def papi_get_margin_margin_loan(self, **params): + return await self._request_papi_api('get', 'margin/marginLoan', signed=True, data=params) + + async def papi_get_margin_repay_loan(self, **params): + return await self._request_papi_api('get', 'margin/repayLoan', signed=True, data=params) + + + async def papi_get_repay_futures_switch(self, **params): + return await self._request_papi_api('get', 'repay-futures-switch', signed=True, data=params) + + + async def papi_repay_futures_switch(self, **params): + return await self._request_papi_api('post', 'repay-futures-switch', signed=True, data=params) + + + async def papi_get_margin_interest_history(self, **params): + return await self._request_papi_api('get', 'margin/marginInterestHistory', signed=True, data=params) + + + async def papi_repay_futures_negative_balance(self, **params): + return await self._request_papi_api('post', 'repay-futures-negative-balance', signed=True, data=params) + + + async def papi_get_portfolio_interest_history(self, **params): + return await self._request_papi_api('get', 'portfolio/interest-history', signed=True, data=params) + + + async def papi_fund_auto_collection(self, **params): + return await self._request_papi_api('post', 'auto-collection', signed=True, data=params) + + + async def papi_fund_asset_collection(self, **params): + return await self._request_papi_api('post', 'asset-collection', signed=True, data=params) + + + async def papi_bnb_transfer(self, **params): + return await self._request_papi_api('post', 'bnb-transfer', signed=True, data=params) + + + async def papi_get_um_income_history(self, **params): + return await self._request_papi_api('get', 'um/income', signed=True, data=params) + + + async def papi_get_cm_income_history(self, **params): + return await self._request_papi_api('get', 'cm/income', signed=True, data=params) + + + async def papi_get_um_account(self, **params): + return await self._request_papi_api('get', 'um/account', signed=True, data=params) + + async def papi_get_um_account_v2(self, **params): + return await self._request_papi_api('get', 'um/account', version=2, signed=True, data=params) + + + async def papi_get_cm_account(self, **params): + return await self._request_papi_api('get', 'cm/account', signed=True, data=params) + + + async def papi_get_um_account_config(self, **params): + return await self._request_papi_api('get', 'um/accountConfig', signed=True, data=params) + + + async def papi_get_um_symbol_config(self, **params): + return await self._request_papi_api('get', 'um/symbolConfig', signed=True, data=params) + + + async def papi_get_um_trade_asyn(self, **params): + return await self._request_papi_api('get', 'um/trade/asyn', signed=True, data=params) + + + async def papi_get_um_trade_asyn_id(self, **params): + return await self._request_papi_api('get', 'um/trade/asyn/id', signed=True, data=params) + + async def papi_get_um_order_asyn(self, **params): + return await self._request_papi_api('get', 'um/order/asyn', signed=True, data=params) + + + async def papi_get_um_order_asyn_id(self, **params): + return await self._request_papi_api('get', 'um/order/asyn/id', signed=True, data=params) + + + async def papi_get_um_income_asyn(self, **params): + return await self._request_papi_api('get', 'um/income/asyn', signed=True, data=params) + + + async def papi_get_um_income_asyn_id(self, **params): + return await self._request_papi_api('get', 'um/income/asyn/id', signed=True, data=params) + + + async def papi_ping(self, **params): + return await self._request_papi_api('get', 'ping', signed=False, data=params) + + # papi trading endpoints + + async def papi_create_um_order(self, **params): + """Place new UM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api('post', 'um/order', signed=True, data=params) + + + async def papi_create_um_conditional_order(self, **params): + """Place new UM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Conditional-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api('post', 'um/conditional/order', signed=True, data=params) + + + async def papi_create_cm_order(self, **params): + """Place new CM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api('post', 'cm/order', signed=True, data=params) + + + async def papi_create_cm_conditional_order(self, **params): + """Place new CM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Conditional-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api('post', 'cm/conditional/order', signed=True, data=params) + + + async def papi_create_margin_order(self, **params): + """New Margin Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-Margin-Order + + :returns: API response + + """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api('post', 'margin/order', signed=True, data=params) + + + async def papi_margin_loan(self, **params): + """Apply for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Borrow + + :returns: API response + + """ + return await self._request_papi_api('post', 'marginLoan', signed=True, data=params) + + + async def papi_repay_loan(self, **params): + """Repay for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay + + :returns: API response + + """ + return await self._request_papi_api('post', 'repayLoan', signed=True, data=params) + + + async def papi_margin_order_oco(self, **params): + """Send in a new OCO for a margin account. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-New-OCO + + :returns: API response + + """ + return await self._request_papi_api('post', 'margin/order/oco', signed=True, data=params) + + + async def papi_cancel_um_order(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Order + + :returns: API response + + """ + return await self._request_papi_api('delete', 'um/order', signed=True, data=params) + + async def papi_cancel_um_all_open_orders(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api('delete', 'um/allOpenOrders', signed=True, data=params) + + + async def papi_cancel_um_conditional_order(self, **params): + """Cancel UM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api('delete', 'um/conditional/order', signed=True, data=params) + + + async def papi_cancel_um_conditional_all_open_orders(self, **params): + """Cancel All UM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('delete', 'um/conditional/allOpenOrders', signed=True, data=params) + + + async def papi_cancel_cm_order(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Order + + :returns: API response + + """ + return await self._request_papi_api('delete', 'cm/order', signed=True, data=params) + + async def papi_cancel_cm_all_open_orders(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api('delete', 'cm/allOpenOrders', signed=True, data=params) + + async def papi_cancel_cm_conditional_order(self, **params): + """Cancel CM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api('delete', 'cm/conditional/order', signed=True, data=params) + + + async def papi_cancel_cm_conditional_all_open_orders(self, **params): + """Cancel All CM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('delete', 'cm/conditional/allOpenOrders', signed=True, data=params) + + + async def papi_cancel_margin_order(self, **params): + """Cancel Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api('delete', 'margin/order', signed=True, data=params) + + + async def papi_cancel_margin_order_list(self, **params): + """Cancel Margin Account OCO Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-OCO-Orders + + :returns: API response + + """ + return await self._request_papi_api('delete', 'margin/orderList', signed=True, data=params) + + + async def papi_cancel_margin_all_open_orders(self, **params): + """Cancel Margin Account All Open Orders on a Symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-All-Open-Orders-on-a-Symbol + + :returns: API response + + """ + return await self._request_papi_api('delete', 'margin/allOpenOrders', signed=True, data=params) + + + async def papi_modify_um_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-UM-Order + + :returns: API response + + """ + return await self._request_papi_api('put', 'um/order', signed=True, data=params) + + + async def papi_modify_cm_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-CM-Order + + :returns: API response + + """ + return await self._request_papi_api('put', 'cm/order', signed=True, data=params) + + async def papi_get_um_order(self, **params): + """Check an UM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/order', signed=True, data=params) + + + async def papi_get_um_all_orders(self, **params): + """Get all account UM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/allOrders', signed=True, data=params) + + + async def papi_get_um_open_order(self, **params): + """Query current UM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/openOrder', signed=True, data=params) + + + async def papi_get_um_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/openOrders', signed=True, data=params) + + + async def papi_get_um_conditional_all_orders(self, **params): + """Query All UM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/conditional/allOrders', signed=True, data=params) + + + async def papi_get_um_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/conditional/openOrders', signed=True, data=params) + + + async def papi_get_um_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/conditional/openOrder', signed=True, data=params) + + + async def papi_get_um_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Conditional-Order-History + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/conditional/orderHistory', signed=True, data=params) + + + + async def papi_get_cm_order(self, **params): + """Check an CM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/order', signed=True, data=params) + + + async def papi_get_cm_all_orders(self, **params): + """Get all account CM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/allOrders', signed=True, data=params) + + + async def papi_get_cm_open_order(self, **params): + """Query current CM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/openOrder', signed=True, data=params) + + + async def papi_get_cm_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/openOrders', signed=True, data=params) + + + async def papi_get_cm_conditional_all_orders(self, **params): + """Query All CM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/conditional/allOrders', signed=True, data=params) + + + async def papi_get_cm_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/conditional/openOrders', signed=True, data=params) + + + async def papi_get_cm_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/conditional/openOrder', signed=True, data=params) + + + async def papi_get_cm_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Conditional-Order-History + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/conditional/orderHistory', signed=True, data=params) + + + async def papi_get_um_force_orders(self, **params): + """Query User's UM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-UM-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/forceOrders', signed=True, data=params) + + + async def papi_get_cm_force_orders(self, **params): + """Query User's CM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-CM-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/forceOrders', signed=True, data=params) + + + async def papi_get_um_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Modify-Order-History + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/orderAmendment', signed=True, data=params) + + + async def papi_get_cm_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Modify-Order-History + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/orderAmendment', signed=True, data=params) + + + async def papi_get_margin_force_orders(self, **params): + """Query user's margin force orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-Margin-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/forceOrders', signed=True, data=params) + + async def papi_get_um_user_trades(self, **params): + """Get trades for a specific account and UM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/userTrades', signed=True, data=params) + + + async def papi_get_cm_user_trades(self, **params): + """Get trades for a specific account and CM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/userTrades', signed=True, data=params) + + + async def papi_get_um_adl_quantile(self, **params): + """Query UM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/adlQuantile', signed=True, data=params) + + + async def papi_get_cm_adl_quantile(self, **params): + """Query CM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return await self._request_papi_api('get', 'cm/adlQuantile', signed=True, data=params) + + + async def papi_set_um_fee_burn(self, **params): + """Change user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off ) on EVERY symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Toggle-BNB-Burn-On-UM-Futures-Trade + + :returns: API response + + """ + return await self._request_papi_api('post', 'um/feeBurn', signed=True, data=params) + + + async def papi_get_um_fee_burn(self, **params): + """Get user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off). + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Get-UM-Futures-BNB-Burn-Status + + :returns: API response + + """ + return await self._request_papi_api('get', 'um/feeBurn', signed=True, data=params) + + + async def papi_get_margin_order(self, **params): + """Query Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/order', signed=True, data=params) + + + async def papi_get_margin_open_orders(self, **params): + """Query Current Margin Open Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/openOrders', signed=True, data=params) + + + async def papi_get_margin_all_orders(self, **params): + """Query All Margin Account Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Margin-Account-Orders + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/allOrders', signed=True, data=params) + + + async def papi_get_margin_order_list(self, **params): + """Retrieves a specific OCO based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-OCO + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/orderList', signed=True, data=params) + + + async def papi_get_margin_all_order_list(self, **params): + """Query all OCO for a specific margin account based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-all-OCO + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/allOrderList', signed=True, data=params) + + + async def papi_get_margin_open_order_list(self, **params): + """Query Margin Account's Open OCO. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Open-OCO + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/openOrderList', signed=True, data=params) + + + async def papi_get_margin_my_trades(self, **params): + """Margin Account Trade List. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api('get', 'margin/myTrades', signed=True, data=params) + + + async def papi_get_margin_repay_debt(self, **params): + """Repay debt for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api('post', 'margin/repay-debt', signed=True, data=params) diff --git a/binance/depthcache.py b/binance/depthcache.py index 28f0da396..e36e146f5 100644 --- a/binance/depthcache.py +++ b/binance/depthcache.py @@ -1,24 +1,31 @@ -# coding=utf-8 - +import logging from operator import itemgetter +import asyncio import time +from typing import Optional, Dict, Callable -from .websockets import BinanceSocketManager +from .helpers import get_loop +from .streams import BinanceSocketManager +from .threaded_stream import ThreadedApiManager class DepthCache(object): - def __init__(self, symbol): + def __init__(self, symbol, conv_type: Callable = float): """Initialise the DepthCache :param symbol: Symbol to create depth cache for :type symbol: string + :param conv_type: Optional type to represent price, and amount, default is float. + :type conv_type: function. """ self.symbol = symbol self._bids = {} self._asks = {} self.update_time = None + self.conv_type: Callable = conv_type + self._log = logging.getLogger(__name__) def add_bid(self, bid): """Add a bid to the cache @@ -27,7 +34,7 @@ def add_bid(self, bid): :return: """ - self._bids[bid[0]] = float(bid[1]) + self._bids[bid[0]] = self.conv_type(bid[1]) if bid[1] == "0.00000000": del self._bids[bid[0]] @@ -38,14 +45,14 @@ def add_ask(self, ask): :return: """ - self._asks[ask[0]] = float(ask[1]) + self._asks[ask[0]] = self.conv_type(ask[1]) if ask[1] == "0.00000000": del self._asks[ask[0]] def get_bids(self): """Get the current bids - :return: list of bids with price and quantity as floats + :return: list of bids with price and quantity as conv_type .. code-block:: python @@ -73,12 +80,12 @@ def get_bids(self): ] """ - return DepthCache.sort_depth(self._bids, reverse=True) + return DepthCache.sort_depth(self._bids, reverse=True, conv_type=self.conv_type) def get_asks(self): """Get the current asks - :return: list of asks with price and quantity as floats + :return: list of asks with price and quantity as conv_type. .. code-block:: python @@ -106,51 +113,202 @@ def get_asks(self): ] """ - return DepthCache.sort_depth(self._asks, reverse=False) + return DepthCache.sort_depth(self._asks, reverse=False, conv_type=self.conv_type) @staticmethod - def sort_depth(vals, reverse=False): + def sort_depth(vals, reverse=False, conv_type: Callable = float): """Sort bids or asks by price """ - lst = [[float(price), quantity] for price, quantity in vals.items()] + if isinstance(vals, dict): + lst = [[conv_type(price), conv_type(quantity)] for price, quantity in vals.items()] + elif isinstance(vals, list): + lst = [[conv_type(price), conv_type(quantity)] for price, quantity in vals] + else: + raise ValueError(f'Unknown order book depth data type: {type(vals)}') lst = sorted(lst, key=itemgetter(0), reverse=reverse) return lst +DEFAULT_REFRESH = 60 * 30 # 30 minutes -class DepthCacheManager(object): +class BaseDepthCacheManager: + TIMEOUT = 60 - _default_refresh = 60 * 30 # 30 minutes - - def __init__(self, client, symbol, callback=None, refresh_interval=_default_refresh, bm=None, limit=500): - """Initialise the DepthCacheManager + def __init__(self, client, symbol, loop=None, refresh_interval: Optional[int]=DEFAULT_REFRESH, bm=None, limit=10, conv_type=float): + """Create a DepthCacheManager instance :param client: Binance API client :type client: binance.Client + :param loop: + :type loop: :param symbol: Symbol to create depth cache for :type symbol: string - :param callback: Optional function to receive depth cache updates - :type callback: function :param refresh_interval: Optional number of seconds between cache refresh, use 0 or None to disable :type refresh_interval: int + :param bm: Optional BinanceSocketManager + :type bm: BinanceSocketManager :param limit: Optional number of orders to get from orderbook :type limit: int + :param conv_type: Optional type to represent price, and amount, default is float. + :type conv_type: function. """ + self._client = client + self._depth_cache = None + self._loop = loop or get_loop() self._symbol = symbol self._limit = limit - self._callback = callback self._last_update_id = None - self._depth_message_buffer = [] - self._bm = bm - self._depth_cache = DepthCache(self._symbol) + self._bm = bm or BinanceSocketManager(self._client) self._refresh_interval = refresh_interval self._conn_key = None + self._conv_type = conv_type + self._log = logging.getLogger(__name__) + + async def __aenter__(self): + await asyncio.gather( + self._init_cache(), + self._start_socket() + ) + await self._socket.__aenter__() + return self + + async def __aexit__(self, *args, **kwargs): + await self._socket.__aexit__(*args, **kwargs) + + async def recv(self): + dc = None + while not dc: + try: + res = await asyncio.wait_for(self._socket.recv(), timeout=self.TIMEOUT) + except Exception as e: + self._log.warning(e) + else: + dc = await self._depth_event(res) + return dc + + async def _init_cache(self): + """Initialise the depth cache calling REST endpoint + + :return: + """ + + # initialise or clear depth cache + self._depth_cache = DepthCache(self._symbol, conv_type=self._conv_type) + + # set a time to refresh the depth cache + if self._refresh_interval: + self._refresh_time = int(time.time()) + self._refresh_interval + + async def _start_socket(self): + """Start the depth cache socket + + :return: + """ + self._socket = self._get_socket() + + def _get_socket(self): + raise NotImplementedError + + async def _depth_event(self, msg): + """Handle a depth event + + :param msg: + :return: + + """ + + if not msg: + return None + + if 'e' in msg and msg['e'] == 'error': + # close the socket + await self.close() - self._start_socket() - self._init_cache() + # notify the user by returning a None value + return None + + return await self._process_depth_message(msg) + + async def _process_depth_message(self, msg): + """Process a depth event message. + + :param msg: Depth event message. + :return: + + """ + + # add any bid or ask values + self._apply_orders(msg) + + # call the callback with the updated depth cache + res = self._depth_cache + + # after processing event see if we need to refresh the depth cache + if self._refresh_interval and int(time.time()) > self._refresh_time: + await self._init_cache() + + return res + + def _apply_orders(self, msg): + assert self._depth_cache + for bid in msg.get('b', []) + msg.get('bids', []): + self._depth_cache.add_bid(bid) + for ask in msg.get('a', []) + msg.get('asks', []): + self._depth_cache.add_ask(ask) + + # keeping update time + self._depth_cache.update_time = msg.get('E') or msg.get('lastUpdateId') + + def get_depth_cache(self): + """Get the current depth cache + + :return: DepthCache object + + """ + return self._depth_cache + + async def close(self): + """Close the open socket for this manager - def _init_cache(self): + :return: + """ + self._depth_cache = None + + def get_symbol(self): + """Get the symbol + + :return: symbol + """ + return self._symbol + + +class DepthCacheManager(BaseDepthCacheManager): + + def __init__( + self, client, symbol, loop=None, refresh_interval: Optional[int]=None, bm=None, limit=500, conv_type=float, ws_interval=None + ): + """Initialise the DepthCacheManager + + :param client: Binance API client + :type client: binance.Client + :param loop: asyncio loop + :param symbol: Symbol to create depth cache for + :type symbol: string + :param refresh_interval: Optional number of seconds between cache refresh, use 0 or None to disable + :type refresh_interval: int + :param limit: Optional number of orders to get from orderbook + :type limit: int + :param conv_type: Optional type to represent price, and amount, default is float. + :type conv_type: function. + :param ws_interval: Optional interval for updates on websocket, default None. If not set, updates happen every second. Must be 0, None (1s) or 100 (100ms). + :type ws_interval: int + + """ + super().__init__(client, symbol, loop, refresh_interval, bm, limit, conv_type) + self._ws_interval = ws_interval + + async def _init_cache(self): """Initialise the depth cache calling REST endpoint :return: @@ -158,9 +316,14 @@ def _init_cache(self): self._last_update_id = None self._depth_message_buffer = [] - res = self._client.get_order_book(symbol=self._symbol, limit=self._limit) + res = await self._client.get_order_book(symbol=self._symbol, limit=self._limit) + + # initialise or clear depth cache + await super()._init_cache() # process bid and asks from the order book + self._apply_orders(res) + assert self._depth_cache for bid in res['bids']: self._depth_cache.add_bid(bid) for ask in res['asks']: @@ -169,105 +332,160 @@ def _init_cache(self): # set first update id self._last_update_id = res['lastUpdateId'] - # set a time to refresh the depth cache - if self._refresh_interval: - self._refresh_time = int(time.time()) + self._refresh_interval - # Apply any updates from the websocket for msg in self._depth_message_buffer: - self._process_depth_message(msg, buffer=True) + await self._process_depth_message(msg) # clear the depth buffer - del self._depth_message_buffer + self._depth_message_buffer = [] - def _start_socket(self): + async def _start_socket(self): """Start the depth cache socket :return: """ - if self._bm is None: - self._bm = BinanceSocketManager(self._client) + if not getattr(self, '_depth_message_buffer', None): + self._depth_message_buffer = [] - self._conn_key = self._bm.start_depth_socket(self._symbol, self._depth_event) - if not self._bm.is_alive(): - self._bm.start() + await super()._start_socket() - # wait for some socket responses - while not len(self._depth_message_buffer): - time.sleep(1) + def _get_socket(self): + return self._bm.depth_socket(self._symbol, interval=self._ws_interval) - def _depth_event(self, msg): - """Handle a depth event + async def _process_depth_message(self, msg): + """Process a depth event message. - :param msg: + :param msg: Depth event message. :return: """ - if 'e' in msg and msg['e'] == 'error': - # close the socket - self.close() - - # notify the user by returning a None value - if self._callback: - self._callback(None) - if self._last_update_id is None: # Initial depth snapshot fetch not yet performed, buffer messages self._depth_message_buffer.append(msg) - else: - self._process_depth_message(msg) - - def _process_depth_message(self, msg, buffer=False): - """Process a depth event message. - - :param msg: Depth event message. - :return: - - """ + return - if buffer and msg['u'] <= self._last_update_id: + if msg['u'] <= self._last_update_id: # ignore any updates before the initial update id return elif msg['U'] != self._last_update_id + 1: # if not buffered check we get sequential updates # otherwise init cache again - self._init_cache() + await self._init_cache() # add any bid or ask values - for bid in msg['b']: - self._depth_cache.add_bid(bid) - for ask in msg['a']: - self._depth_cache.add_ask(ask) - - # keeping update time - self._depth_cache.update_time = msg['E'] + self._apply_orders(msg) # call the callback with the updated depth cache - if self._callback: - self._callback(self._depth_cache) + res = self._depth_cache self._last_update_id = msg['u'] # after processing event see if we need to refresh the depth cache if self._refresh_interval and int(time.time()) > self._refresh_time: - self._init_cache() - - def get_depth_cache(self): - """Get the current depth cache + await self._init_cache() - :return: DepthCache object + return res - """ - return self._depth_cache - def close(self, close_socket=False): - """Close the open socket for this manager +class FuturesDepthCacheManager(BaseDepthCacheManager): + async def _process_depth_message(self, msg): + """Process a depth event message. + :param msg: Depth event message. :return: + """ - self._bm.stop_socket(self._conn_key) - if close_socket: - self._bm.close() - time.sleep(1) - self._depth_cache = None + msg = msg.get('data') + return await super()._process_depth_message(msg) + + def _apply_orders(self, msg): + assert self._depth_cache + self._depth_cache._bids = msg.get('b', []) + self._depth_cache._asks = msg.get('a', []) + + # keeping update time + self._depth_cache.update_time = msg.get('E') or msg.get('lastUpdateId') + + def _get_socket(self): + sock = self._bm.futures_depth_socket(self._symbol) + return sock + + +class OptionsDepthCacheManager(BaseDepthCacheManager): + + def _get_socket(self): + return self._bm.options_depth_socket(self._symbol) + + +class ThreadedDepthCacheManager(ThreadedApiManager): + + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, str]] = None, tld: str = 'com', + testnet: bool = False + ): + super().__init__(api_key, api_secret, requests_params, tld, testnet) + + def _start_depth_cache( + self, dcm_class, callback: Callable, symbol: str, + refresh_interval=None, bm=None, limit=10, conv_type=float, **kwargs + ) -> str: + + while not self._client: + time.sleep(0.01) + + dcm = dcm_class( + client=self._client, + symbol=symbol, + loop=self._loop, + refresh_interval=refresh_interval, + bm=bm, + limit=limit, + conv_type=conv_type, + **kwargs + ) + path = symbol.lower() + '@depth' + str(limit) + self._socket_running[path] = True + self._loop.call_soon(asyncio.create_task, self.start_listener(dcm, path, callback)) + return path + + def start_depth_cache( + self, callback: Callable, symbol: str, refresh_interval=None, bm=None, limit=10, conv_type=float, ws_interval=0 + ) -> str: + return self._start_depth_cache( + dcm_class=DepthCacheManager, + callback=callback, + symbol=symbol, + refresh_interval=refresh_interval, + bm=bm, + limit=limit, + conv_type=conv_type, + ws_interval=ws_interval + ) + + def start_futures_depth_socket( + self, callback: Callable, symbol: str, refresh_interval=None, bm=None, limit=10, conv_type=float + ) -> str: + return self._start_depth_cache( + dcm_class=FuturesDepthCacheManager, + callback=callback, + symbol=symbol, + refresh_interval=refresh_interval, + bm=bm, + limit=limit, + conv_type=conv_type + ) + + def start_options_depth_socket( + self, callback: Callable, symbol: str, refresh_interval=None, bm=None, limit=10, conv_type=float + ) -> str: + return self._start_depth_cache( + dcm_class=OptionsDepthCacheManager, + callback=callback, + symbol=symbol, + refresh_interval=refresh_interval, + bm=bm, + limit=limit, + conv_type=conv_type + ) diff --git a/binance/enums.py b/binance/enums.py index 4d37b9089..18981b8eb 100644 --- a/binance/enums.py +++ b/binance/enums.py @@ -1,4 +1,4 @@ -# coding=utf-8 +from enum import Enum SYMBOL_TYPE_SPOT = 'SPOT' @@ -10,6 +10,7 @@ ORDER_STATUS_REJECTED = 'REJECTED' ORDER_STATUS_EXPIRED = 'EXPIRED' +KLINE_INTERVAL_1SECOND = '1s' KLINE_INTERVAL_1MINUTE = '1m' KLINE_INTERVAL_3MINUTE = '3m' KLINE_INTERVAL_5MINUTE = '5m' @@ -37,9 +38,19 @@ ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' +FUTURE_ORDER_TYPE_LIMIT = 'LIMIT' +FUTURE_ORDER_TYPE_MARKET = 'MARKET' +FUTURE_ORDER_TYPE_STOP = 'STOP' +FUTURE_ORDER_TYPE_STOP_MARKET = 'STOP_MARKET' +FUTURE_ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' +FUTURE_ORDER_TYPE_TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET' +FUTURE_ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' +FUTURE_ORDER_TYPE_TRAILING_STOP_MARKET = 'TRAILING_STOP_MARKET' + TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel TIME_IN_FORCE_FOK = 'FOK' # Fill or kill +TIME_IN_FORCE_GTX = 'GTX' # Post only order ORDER_RESP_TYPE_ACK = 'ACK' ORDER_RESP_TYPE_RESULT = 'RESULT' @@ -48,3 +59,24 @@ WEBSOCKET_DEPTH_5 = '5' WEBSOCKET_DEPTH_10 = '10' WEBSOCKET_DEPTH_20 = '20' + +NO_SIDE_EFFECT_TYPE = 'NO_SIDE_EFFECT' +MARGIN_BUY_TYPE = 'MARGIN_BUY' +AUTO_REPAY_TYPE = 'AUTO_REPAY' + + +class HistoricalKlinesType(Enum): + SPOT = 1 + FUTURES = 2 + FUTURES_COIN = 3 + + +class FuturesType(Enum): + USD_M = 1 + COIN_M = 2 + + +class ContractType(Enum): + PERPETUAL = "perpetual" + CURRENT_QUARTER = "current_quarter" + NEXT_QUARTER = "next_quarter" diff --git a/binance/exceptions.py b/binance/exceptions.py index 39c450a7c..9a3fd313c 100644 --- a/binance/exceptions.py +++ b/binance/exceptions.py @@ -1,18 +1,19 @@ # coding=utf-8 +import json class BinanceAPIException(Exception): - def __init__(self, response): + def __init__(self, response, status_code, text): self.code = 0 try: - json_res = response.json() + json_res = json.loads(text) except ValueError: self.message = 'Invalid JSON error message from Binance: {}'.format(response.text) else: - self.code = json_res['code'] - self.message = json_res['msg'] - self.status_code = response.status_code + self.code = json_res.get('code') + self.message = json_res.get('msg') + self.status_code = status_code self.response = response self.request = getattr(response, 'request', None) @@ -42,42 +43,46 @@ class BinanceOrderMinAmountException(BinanceOrderException): def __init__(self, value): message = "Amount must be a multiple of %s" % value - super(BinanceOrderMinAmountException, self).__init__(-1013, message) + super().__init__(-1013, message) class BinanceOrderMinPriceException(BinanceOrderException): def __init__(self, value): message = "Price must be at least %s" % value - super(BinanceOrderMinPriceException, self).__init__(-1013, message) + super().__init__(-1013, message) class BinanceOrderMinTotalException(BinanceOrderException): def __init__(self, value): message = "Total must be at least %s" % value - super(BinanceOrderMinTotalException, self).__init__(-1013, message) + super().__init__(-1013, message) class BinanceOrderUnknownSymbolException(BinanceOrderException): def __init__(self, value): message = "Unknown symbol %s" % value - super(BinanceOrderUnknownSymbolException, self).__init__(-1013, message) + super().__init__(-1013, message) class BinanceOrderInactiveSymbolException(BinanceOrderException): def __init__(self, value): message = "Attempting to trade an inactive symbol %s" % value - super(BinanceOrderInactiveSymbolException, self).__init__(-1013, message) + super().__init__(-1013, message) -class BinanceWithdrawException(Exception): - def __init__(self, message): - if message == u'参数异常': - message = 'Withdraw to this address through the website first' - self.message = message +class BinanceWebsocketUnableToConnect(Exception): + pass - def __str__(self): - return 'BinanceWithdrawException: %s' % self.message + +class NotImplementedException(Exception): + def __init__(self, value): + message = f'Not implemented: {value}' + super().__init__(message) + + +class UnknownDateFormat(Exception): + ... diff --git a/binance/helpers.py b/binance/helpers.py index fe91df3f4..5c6515ea1 100644 --- a/binance/helpers.py +++ b/binance/helpers.py @@ -1,12 +1,16 @@ -# coding=utf-8 +import asyncio +from decimal import Decimal +from typing import Union, Optional, Dict import dateparser import pytz from datetime import datetime +from binance.exceptions import UnknownDateFormat -def date_to_milliseconds(date_str): + +def date_to_milliseconds(date_str: str) -> int: """Convert UTC date to milliseconds If using offset strings add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" @@ -14,12 +18,14 @@ def date_to_milliseconds(date_str): See dateparse docs for formats http://dateparser.readthedocs.io/en/latest/ :param date_str: date in readable format, i.e. "January 01, 2018", "11 hours ago UTC", "now UTC" - :type date_str: str """ # get epoch value in UTC - epoch = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) + epoch: datetime = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) # parse our date string - d = dateparser.parse(date_str) + d: Optional[datetime] = dateparser.parse(date_str, settings={"TIMEZONE": "UTC"}) + if not d: + raise UnknownDateFormat(date_str) + # if the date is not timezone aware apply UTC timezone if d.tzinfo is None or d.tzinfo.utcoffset(d) is None: d = d.replace(tzinfo=pytz.utc) @@ -28,11 +34,10 @@ def date_to_milliseconds(date_str): return int((d - epoch).total_seconds() * 1000.0) -def interval_to_milliseconds(interval): +def interval_to_milliseconds(interval: str) -> Optional[int]: """Convert a Binance interval string to milliseconds :param interval: Binance interval string, e.g.: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w - :type interval: str :return: int value of interval in milliseconds @@ -40,7 +45,8 @@ def interval_to_milliseconds(interval): None if interval suffix is not one of m, h, d, w """ - seconds_per_unit = { + seconds_per_unit: Dict[str, int] = { + "s": 1, "m": 60, "h": 60 * 60, "d": 24 * 60 * 60, @@ -50,3 +56,39 @@ def interval_to_milliseconds(interval): return int(interval[:-1]) * seconds_per_unit[interval[-1]] * 1000 except (ValueError, KeyError): return None + + +def round_step_size(quantity: Union[float, Decimal], step_size: Union[float, Decimal]) -> float: + """Rounds a given quantity to a specific step size + + :param quantity: required + :param step_size: required + + :return: decimal + """ + quantity = Decimal(str(quantity)) + return float(quantity - quantity % Decimal(str(step_size))) + + +def convert_ts_str(ts_str): + if ts_str is None: + return ts_str + if type(ts_str) == int: + return ts_str + return date_to_milliseconds(ts_str) + + +def get_loop(): + """check if there is an event loop in the current thread, if not create one + inspired by https://stackoverflow.com/questions/46727787/runtimeerror-there-is-no-current-event-loop-in-thread-in-async-apscheduler + """ + try: + loop = asyncio.get_event_loop() + return loop + except RuntimeError as e: + if str(e).startswith("There is no current event loop in thread"): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop + else: + raise diff --git a/binance/streams.py b/binance/streams.py new file mode 100755 index 000000000..a2bcd13ee --- /dev/null +++ b/binance/streams.py @@ -0,0 +1,1572 @@ +import asyncio +import gzip +import json +import logging +import time +from asyncio import sleep +from enum import Enum +from random import random +from socket import gaierror +from typing import Optional, List, Dict, Callable, Any + +import websockets as ws +from websockets.exceptions import ConnectionClosedError + +from .client import AsyncClient +from .enums import FuturesType +from .exceptions import BinanceWebsocketUnableToConnect +from .enums import ContractType +from .helpers import get_loop +from .threaded_stream import ThreadedApiManager + +KEEPALIVE_TIMEOUT = 5 * 60 # 5 minutes + + +class WSListenerState(Enum): + INITIALISING = 'Initialising' + STREAMING = 'Streaming' + RECONNECTING = 'Reconnecting' + EXITING = 'Exiting' + + +class BinanceSocketType(str, Enum): + SPOT = 'Spot' + USD_M_FUTURES = 'USD_M_Futures' + COIN_M_FUTURES = 'Coin_M_Futures' + OPTIONS = 'Vanilla_Options' + ACCOUNT = 'Account' + + +class ReconnectingWebsocket: + MAX_RECONNECTS = 5 + MAX_RECONNECT_SECONDS = 60 + MIN_RECONNECT_WAIT = 0.1 + TIMEOUT = 10 + NO_MESSAGE_RECONNECT_TIMEOUT = 60 + MAX_QUEUE_SIZE = 100 + + def __init__( + self, url: str, path: Optional[str] = None, prefix: str = 'ws/', is_binary: bool = False, exit_coro=None, **kwargs + ): + self._loop = get_loop() + self._log = logging.getLogger(__name__) + self._path = path + self._url = url + self._exit_coro = exit_coro + self._prefix = prefix + self._reconnects = 0 + self._is_binary = is_binary + self._conn = None + self._socket = None + self.ws: Optional[ws.WebSocketClientProtocol] = None # type: ignore + self.ws_state = WSListenerState.INITIALISING + self._queue = asyncio.Queue() + self._handle_read_loop = None + self._ws_kwargs = kwargs + + async def __aenter__(self): + await self.connect() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._exit_coro: + await self._exit_coro(self._path) + self.ws_state = WSListenerState.EXITING + if self.ws: + self.ws.fail_connection() + if self._conn and hasattr(self._conn, 'protocol'): + await self._conn.__aexit__(exc_type, exc_val, exc_tb) + self.ws = None + if not self._handle_read_loop: + self._log.error("CANCEL read_loop") + await self._kill_read_loop() + + async def connect(self): + await self._before_connect() + assert self._path + ws_url = self._url + self._prefix + self._path + self._conn = ws.connect(ws_url, close_timeout=0.1, **self._ws_kwargs) # type: ignore + try: + self.ws = await self._conn.__aenter__() + except: # noqa + await self._reconnect() + return + self.ws_state = WSListenerState.STREAMING + self._reconnects = 0 + await self._after_connect() + # To manage the "cannot call recv while another coroutine is already waiting for the next message" + if not self._handle_read_loop: + self._handle_read_loop = self._loop.call_soon_threadsafe(asyncio.create_task, self._read_loop()) + + async def _kill_read_loop(self): + self.ws_state = WSListenerState.EXITING + while self._handle_read_loop: + await sleep(0.1) + + async def _before_connect(self): + pass + + async def _after_connect(self): + pass + + def _handle_message(self, evt): + if self._is_binary: + try: + evt = gzip.decompress(evt) + except (ValueError, OSError): + return None + try: + return json.loads(evt) + except ValueError: + self._log.debug(f'error parsing evt json:{evt}') + return None + + async def _read_loop(self): + try: + while True: + try: + while self.ws_state == WSListenerState.RECONNECTING: + await self._run_reconnect() + + if self.ws_state == WSListenerState.EXITING: + self._log.debug(f"_read_loop {self._path} break for {self.ws_state}") + break + elif self.ws.state == ws.protocol.State.CLOSING: # type: ignore + await asyncio.sleep(0.1) + continue + elif self.ws.state == ws.protocol.State.CLOSED: # type: ignore + await self._reconnect() + elif self.ws_state == WSListenerState.STREAMING: + assert self.ws + res = await asyncio.wait_for(self.ws.recv(), timeout=self.TIMEOUT) + res = self._handle_message(res) + if res: + if self._queue.qsize() < self.MAX_QUEUE_SIZE: + await self._queue.put(res) + else: + self._log.debug(f"Queue overflow {self.MAX_QUEUE_SIZE}. Message not filled") + await self._queue.put({ + 'e': 'error', + 'm': 'Queue overflow. Message not filled' + }) + raise BinanceWebsocketUnableToConnect + except asyncio.TimeoutError: + self._log.debug(f"no message in {self.TIMEOUT} seconds") + # _no_message_received_reconnect + except asyncio.CancelledError as e: + self._log.debug(f"cancelled error {e}") + break + except asyncio.IncompleteReadError as e: + self._log.debug(f"incomplete read error ({e})") + except ConnectionClosedError as e: + self._log.debug(f"connection close error ({e})") + except gaierror as e: + self._log.debug(f"DNS Error ({e})") + except BinanceWebsocketUnableToConnect as e: + self._log.debug(f"BinanceWebsocketUnableToConnect ({e})") + break + except Exception as e: + self._log.debug(f"Unknown exception ({e})") + continue + finally: + self._handle_read_loop = None # Signal the coro is stopped + self._reconnects = 0 + + async def _run_reconnect(self): + await self.before_reconnect() + if self._reconnects < self.MAX_RECONNECTS: + reconnect_wait = self._get_reconnect_wait(self._reconnects) + self._log.debug( + f"websocket reconnecting. {self.MAX_RECONNECTS - self._reconnects} reconnects left - " + f"waiting {reconnect_wait}" + ) + await asyncio.sleep(reconnect_wait) + await self.connect() + else: + self._log.error(f'Max reconnections {self.MAX_RECONNECTS} reached:') + # Signal the error + await self._queue.put({ + 'e': 'error', + 'm': 'Max reconnect retries reached' + }) + raise BinanceWebsocketUnableToConnect + + async def recv(self): + res = None + while not res: + try: + res = await asyncio.wait_for(self._queue.get(), timeout=self.TIMEOUT) + except asyncio.TimeoutError: + self._log.debug(f"no message in {self.TIMEOUT} seconds") + return res + + async def _wait_for_reconnect(self): + while self.ws_state != WSListenerState.STREAMING and self.ws_state != WSListenerState.EXITING: + await sleep(0.1) + + def _get_reconnect_wait(self, attempts: int) -> int: + expo = 2 ** attempts + return round(random() * min(self.MAX_RECONNECT_SECONDS, expo - 1) + 1) + + async def before_reconnect(self): + if self.ws: + self.ws = None + + if self._conn and hasattr(self._conn, 'protocol'): + await self._conn.__aexit__(None, None, None) + + self._reconnects += 1 + + def _no_message_received_reconnect(self): + self._log.debug('No message received, reconnecting') + self.ws_state = WSListenerState.RECONNECTING + + async def _reconnect(self): + self.ws_state = WSListenerState.RECONNECTING + + +class KeepAliveWebsocket(ReconnectingWebsocket): + + def __init__( + self, client: AsyncClient, url, keepalive_type, prefix='ws/', is_binary=False, exit_coro=None, + user_timeout=None, **kwargs + ): + super().__init__(path=None, url=url, prefix=prefix, is_binary=is_binary, exit_coro=exit_coro, **kwargs) + self._keepalive_type = keepalive_type + self._client = client + self._user_timeout = user_timeout or KEEPALIVE_TIMEOUT + self._timer = None + + async def __aexit__(self, *args, **kwargs): + if not self._path: + return + if self._timer: + self._timer.cancel() + self._timer = None + await super().__aexit__(*args, **kwargs) + + async def _before_connect(self): + if not self._path: + self._path = await self._get_listen_key() + + async def _after_connect(self): + self._start_socket_timer() + + def _start_socket_timer(self): + self._timer = self._loop.call_later( + self._user_timeout, + lambda: asyncio.create_task(self._keepalive_socket()) + ) + + async def _get_listen_key(self): + if self._keepalive_type == 'user': + listen_key = await self._client.stream_get_listen_key() + elif self._keepalive_type == 'margin': # cross-margin + listen_key = await self._client.margin_stream_get_listen_key() + elif self._keepalive_type == 'futures': + listen_key = await self._client.futures_stream_get_listen_key() + elif self._keepalive_type == 'coin_futures': + listen_key = await self._client.futures_coin_stream_get_listen_key() + else: # isolated margin + # Passing symbol for isolated margin + listen_key = await self._client.isolated_margin_stream_get_listen_key(self._keepalive_type) + return listen_key + + async def _keepalive_socket(self): + try: + listen_key = await self._get_listen_key() + if listen_key != self._path: + self._log.debug("listen key changed: reconnect") + self._path = listen_key + await self._reconnect() + else: + self._log.debug("listen key same: keepalive") + if self._keepalive_type == 'user': + await self._client.stream_keepalive(self._path) + elif self._keepalive_type == 'margin': # cross-margin + await self._client.margin_stream_keepalive(self._path) + elif self._keepalive_type == 'futures': + await self._client.futures_stream_keepalive(self._path) + elif self._keepalive_type == 'coin_futures': + await self._client.futures_coin_stream_keepalive(self._path) + else: # isolated margin + # Passing symbol for isolated margin + await self._client.isolated_margin_stream_keepalive(self._keepalive_type, self._path) + except Exception: + pass # Ignore + finally: + self._start_socket_timer() + + +class BinanceSocketManager: + STREAM_URL = 'wss://stream.binance.{}:9443/' + STREAM_TESTNET_URL = 'wss://testnet.binance.vision/' + FSTREAM_URL = 'wss://fstream.binance.{}/' + FSTREAM_TESTNET_URL = 'wss://stream.binancefuture.com/' + DSTREAM_URL = 'wss://dstream.binance.{}/' + DSTREAM_TESTNET_URL = 'wss://dstream.binancefuture.com/' + VSTREAM_URL = 'wss://vstream.binance.{}/' + VSTREAM_TESTNET_URL = 'wss://testnetws.binanceops.{}/' + + WEBSOCKET_DEPTH_5 = '5' + WEBSOCKET_DEPTH_10 = '10' + WEBSOCKET_DEPTH_20 = '20' + + def __init__(self, client: AsyncClient, user_timeout=KEEPALIVE_TIMEOUT): + """Initialise the BinanceSocketManager + + :param client: Binance API client + :type client: binance.AsyncClient + + """ + self.STREAM_URL = self.STREAM_URL.format(client.tld) + self.FSTREAM_URL = self.FSTREAM_URL.format(client.tld) + self.DSTREAM_URL = self.DSTREAM_URL.format(client.tld) + self.VSTREAM_URL = self.VSTREAM_URL.format(client.tld) + self.VSTREAM_TESTNET_URL = self.VSTREAM_TESTNET_URL.format(client.tld) + + self._conns = {} + self._loop = get_loop() + self._client = client + self._user_timeout = user_timeout + + self.testnet = self._client.testnet + self.ws_kwargs = {} + + def _get_stream_url(self, stream_url: Optional[str] = None): + if stream_url: + return stream_url + stream_url = self.STREAM_URL + if self.testnet: + stream_url = self.STREAM_TESTNET_URL + return stream_url + + def _get_socket( + self, path: str, stream_url: Optional[str] = None, prefix: str = 'ws/', is_binary: bool = False, + socket_type: BinanceSocketType = BinanceSocketType.SPOT + ) -> ReconnectingWebsocket: + conn_id = f'{socket_type}_{path}' + if conn_id not in self._conns: + self._conns[conn_id] = ReconnectingWebsocket( + path=path, + url=self._get_stream_url(stream_url), + prefix=prefix, + exit_coro=lambda p: self._exit_socket(f'{socket_type}_{p}'), + is_binary=is_binary, + **self.ws_kwargs + ) + + return self._conns[conn_id] + + def _get_account_socket( + self, path: str, stream_url: Optional[str] = None, prefix: str = 'ws/', is_binary: bool = False + ): + conn_id = f'{BinanceSocketType.ACCOUNT}_{path}' + if conn_id not in self._conns: + self._conns[conn_id] = KeepAliveWebsocket( + client=self._client, + url=self._get_stream_url(stream_url), + keepalive_type=path, + prefix=prefix, + exit_coro=self._exit_socket, + is_binary=is_binary, + user_timeout=self._user_timeout, + **self.ws_kwargs + ) + + return self._conns[conn_id] + + def _get_futures_socket(self, path: str, futures_type: FuturesType, prefix: str = 'stream?streams='): + socket_type: BinanceSocketType = BinanceSocketType.USD_M_FUTURES + if futures_type == FuturesType.USD_M: + stream_url = self.FSTREAM_URL + if self.testnet: + stream_url = self.FSTREAM_TESTNET_URL + else: + stream_url = self.DSTREAM_URL + if self.testnet: + stream_url = self.DSTREAM_TESTNET_URL + return self._get_socket(path, stream_url, prefix, socket_type=socket_type) + + def _get_options_socket(self, path: str, prefix: str = 'ws/'): + stream_url = self.VSTREAM_URL + if self.testnet: + stream_url = self.VSTREAM_TESTNET_URL + return self._get_socket(path, stream_url, prefix, is_binary=True, socket_type=BinanceSocketType.OPTIONS) + + async def _exit_socket(self, path: str): + await self._stop_socket(path) + + def depth_socket(self, symbol: str, depth: Optional[str] = None, interval: Optional[int] = None): + """Start a websocket for symbol market depth returning either a diff or a partial book + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams + + :param symbol: required + :type symbol: str + :param depth: optional Number of depth entries to return, default None. If passed returns a partial book instead of a diff + :type depth: str + :param interval: optional interval for updates, default None. If not set, updates happen every second. Must be 0, None (1s) or 100 (100ms) + :type interval: int + + :returns: connection key string if successful, False otherwise + + Partial Message Format + + .. code-block:: python + + { + "lastUpdateId": 160, # Last update ID + "bids": [ # Bids to be updated + [ + "0.0024", # price level to be updated + "10", # quantity + [] # ignore + ] + ], + "asks": [ # Asks to be updated + [ + "0.0026", # price level to be updated + "100", # quantity + [] # ignore + ] + ] + } + + + Diff Message Format + + .. code-block:: python + + { + "e": "depthUpdate", # Event type + "E": 123456789, # Event time + "s": "BNBBTC", # Symbol + "U": 157, # First update ID in event + "u": 160, # Final update ID in event + "b": [ # Bids to be updated + [ + "0.0024", # price level to be updated + "10", # quantity + [] # ignore + ] + ], + "a": [ # Asks to be updated + [ + "0.0026", # price level to be updated + "100", # quantity + [] # ignore + ] + ] + } + + """ + socket_name = symbol.lower() + '@depth' + if depth and depth != '1': + socket_name = f'{socket_name}{depth}' + if interval: + if interval in [0, 100]: + socket_name = f'{socket_name}@{interval}ms' + else: + raise ValueError("Websocket interval value not allowed. Allowed values are [0, 100]") + return self._get_socket(socket_name) + + def kline_socket(self, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE): + """Start a websocket for symbol kline data + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#klinecandlestick-streams + + :param symbol: required + :type symbol: str + :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE + :type interval: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "kline", # event type + "E": 1499404907056, # event time + "s": "ETHBTC", # symbol + "k": { + "t": 1499404860000, # start time of this bar + "T": 1499404919999, # end time of this bar + "s": "ETHBTC", # symbol + "i": "1m", # interval + "f": 77462, # first trade id + "L": 77465, # last trade id + "o": "0.10278577", # open + "c": "0.10278645", # close + "h": "0.10278712", # high + "l": "0.10278518", # low + "v": "17.47929838", # volume + "n": 4, # number of trades + "x": false, # whether this bar is final + "q": "1.79662878", # quote volume + "V": "2.34879839", # volume of active buy + "Q": "0.24142166", # quote volume of active buy + "B": "13279784.01349473" # can be ignored + } + } + """ + path = f'{symbol.lower()}@kline_{interval}' + return self._get_socket(path) + + def kline_futures_socket(self, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE, + futures_type: FuturesType = FuturesType.USD_M, + contract_type: ContractType = ContractType.PERPETUAL): + """Start a websocket for symbol kline data for the perpeual futures stream + + https://binance-docs.github.io/apidocs/futures/en/#continuous-contract-kline-candlestick-streams + + :param symbol: required + :type symbol: str + :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE + :type interval: str + :param futures_type: use USD-M or COIN-M futures default USD-M + :param contract_type: use PERPETUAL or CURRENT_QUARTER or NEXT_QUARTER default PERPETUAL + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e":"continuous_kline", // Event type + "E":1607443058651, // Event time + "ps":"BTCUSDT", // Pair + "ct":"PERPETUAL" // Contract type + "k":{ + "t":1607443020000, // Kline start time + "T":1607443079999, // Kline close time + "i":"1m", // Interval + "f":116467658886, // First trade ID + "L":116468012423, // Last trade ID + "o":"18787.00", // Open price + "c":"18804.04", // Close price + "h":"18804.04", // High price + "l":"18786.54", // Low price + "v":"197.664", // volume + "n": 543, // Number of trades + "x":false, // Is this kline closed? + "q":"3715253.19494", // Quote asset volume + "V":"184.769", // Taker buy volume + "Q":"3472925.84746", //Taker buy quote asset volume + "B":"0" // Ignore + } + } + _@continuousKline_ + """ + + path = f'{symbol.lower()}_{contract_type.value}@continuousKline_{interval}' + return self._get_futures_socket(path, prefix='ws/', futures_type=futures_type) + + def miniticker_socket(self, update_time: int = 1000): + """Start a miniticker websocket for all trades + + This is not in the official Binance api docs, but this is what + feeds the right column on a ticker page on Binance. + + :param update_time: time between callbacks in milliseconds, must be 1000 or greater + :type update_time: int + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + [ + { + 'e': '24hrMiniTicker', # Event type + 'E': 1515906156273, # Event time + 's': 'QTUMETH', # Symbol + 'c': '0.03836900', # close + 'o': '0.03953500', # open + 'h': '0.04400000', # high + 'l': '0.03756000', # low + 'v': '147435.80000000', # volume + 'q': '5903.84338533' # quote volume + } + ] + """ + + return self._get_socket(f'!miniTicker@arr@{update_time}ms') + + def trade_socket(self, symbol: str): + """Start a websocket for symbol trade data + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#trade-streams + + :param symbol: required + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "trade", # Event type + "E": 123456789, # Event time + "s": "BNBBTC", # Symbol + "t": 12345, # Trade ID + "p": "0.001", # Price + "q": "100", # Quantity + "b": 88, # Buyer order Id + "a": 50, # Seller order Id + "T": 123456785, # Trade time + "m": true, # Is the buyer the market maker? + "M": true # Ignore. + } + + """ + + return self._get_socket(symbol.lower() + '@trade') + + def aggtrade_socket(self, symbol: str): + """Start a websocket for symbol trade data + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#aggregate-trade-streams + + :param symbol: required + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "aggTrade", # event type + "E": 1499405254326, # event time + "s": "ETHBTC", # symbol + "a": 70232, # aggregated tradeid + "p": "0.10281118", # price + "q": "8.15632997", # quantity + "f": 77489, # first breakdown trade id + "l": 77489, # last breakdown trade id + "T": 1499405254324, # trade time + "m": false, # whether buyer is a maker + "M": true # can be ignored + } + + """ + return self._get_socket(symbol.lower() + '@aggTrade') + + def aggtrade_futures_socket(self, symbol: str, futures_type: FuturesType = FuturesType.USD_M): + """Start a websocket for aggregate symbol trade data for the futures stream + + :param symbol: required + :param futures_type: use USD-M or COIN-M futures default USD-M + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "aggTrade", // Event type + "E": 123456789, // Event time + "s": "BTCUSDT", // Symbol + "a": 5933014, // Aggregate trade ID + "p": "0.001", // Price + "q": "100", // Quantity + "f": 100, // First trade ID + "l": 105, // Last trade ID + "T": 123456785, // Trade time + "m": true, // Is the buyer the market maker? + } + + """ + return self._get_futures_socket(symbol.lower() + '@aggTrade', futures_type=futures_type) + + def symbol_miniticker_socket(self, symbol: str): + """Start a websocket for a symbol's miniTicker data + + https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-mini-ticker-stream + + :param symbol: required + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "24hrMiniTicker", // Event type + "E": 123456789, // Event time + "s": "BNBBTC", // Symbol + "c": "0.0025", // Close price + "o": "0.0010", // Open price + "h": "0.0025", // High price + "l": "0.0010", // Low price + "v": "10000", // Total traded base asset volume + "q": "18" // Total traded quote asset volume + } + + """ + return self._get_socket(symbol.lower() + '@miniTicker') + + def symbol_ticker_socket(self, symbol: str): + """Start a websocket for a symbol's ticker data + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#individual-symbol-ticker-streams + + :param symbol: required + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "e": "24hrTicker", # Event type + "E": 123456789, # Event time + "s": "BNBBTC", # Symbol + "p": "0.0015", # Price change + "P": "250.00", # Price change percent + "w": "0.0018", # Weighted average price + "x": "0.0009", # Previous day's close price + "c": "0.0025", # Current day's close price + "Q": "10", # Close trade's quantity + "b": "0.0024", # Best bid price + "B": "10", # Bid bid quantity + "a": "0.0026", # Best ask price + "A": "100", # Best ask quantity + "o": "0.0010", # Open price + "h": "0.0025", # High price + "l": "0.0010", # Low price + "v": "10000", # Total traded base asset volume + "q": "18", # Total traded quote asset volume + "O": 0, # Statistics open time + "C": 86400000, # Statistics close time + "F": 0, # First trade ID + "L": 18150, # Last trade Id + "n": 18151 # Total number of trades + } + + """ + return self._get_socket(symbol.lower() + '@ticker') + + def ticker_socket(self): + """Start a websocket for all ticker data + + By default all markets are included in an array. + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#all-market-tickers-stream + + :param coro: callback function to handle messages + :type coro: function + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + [ + { + 'F': 278610, + 'o': '0.07393000', + 's': 'BCCBTC', + 'C': 1509622420916, + 'b': '0.07800800', + 'l': '0.07160300', + 'h': '0.08199900', + 'L': 287722, + 'P': '6.694', + 'Q': '0.10000000', + 'q': '1202.67106335', + 'p': '0.00494900', + 'O': 1509536020916, + 'a': '0.07887800', + 'n': 9113, + 'B': '1.00000000', + 'c': '0.07887900', + 'x': '0.07399600', + 'w': '0.07639068', + 'A': '2.41900000', + 'v': '15743.68900000' + } + ] + """ + return self._get_socket('!ticker@arr') + + def index_price_socket(self, symbol: str, fast: bool = True): + """Start a websocket for a symbol's futures mark price + https://binance-docs.github.io/apidocs/delivery/en/#index-price-stream + :param symbol: required + :param fast: use faster or 1s default + :returns: connection key string if successful, False otherwise + + Message Format + .. code-block:: python + { + "e": "indexPriceUpdate", // Event type + "E": 1591261236000, // Event time + "i": "BTCUSD", // Pair + "p": "9636.57860000", // Index Price + } + """ + stream_name = '@indexPrice@1s' if fast else '@indexPrice' + return self._get_futures_socket(symbol.lower() + stream_name, futures_type=FuturesType.COIN_M) + + def futures_depth_socket(self, symbol: str, depth: str = '10', futures_type=FuturesType.USD_M): + """Subscribe to a futures depth data stream + + https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams + + :param symbol: required + :type symbol: str + :param depth: optional Number of depth entries to return, default 10. + :type depth: str + :param futures_type: use USD-M or COIN-M futures default USD-M + """ + return self._get_futures_socket(symbol.lower() + '@depth' + str(depth), futures_type=futures_type) + + def symbol_mark_price_socket(self, symbol: str, fast: bool = True, futures_type: FuturesType = FuturesType.USD_M): + """Start a websocket for a symbol's futures mark price + https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream + :param symbol: required + :param fast: use faster or 1s default + :param futures_type: use USD-M or COIN-M futures default USD-M + :returns: connection key string if successful, False otherwise + Message Format + .. code-block:: python + { + "e": "markPriceUpdate", // Event type + "E": 1562305380000, // Event time + "s": "BTCUSDT", // Symbol + "p": "11185.87786614", // Mark price + "r": "0.00030000", // Funding rate + "T": 1562306400000 // Next funding time + } + """ + stream_name = '@markPrice@1s' if fast else '@markPrice' + return self._get_futures_socket(symbol.lower() + stream_name, futures_type=futures_type) + + def all_mark_price_socket(self, fast: bool = True, futures_type: FuturesType = FuturesType.USD_M): + """Start a websocket for all futures mark price data + By default all symbols are included in an array. + https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream-for-all-market + :param fast: use faster or 1s default + :param futures_type: use USD-M or COIN-M futures default USD-M + :returns: connection key string if successful, False otherwise + Message Format + .. code-block:: python + + [ + { + "e": "markPriceUpdate", // Event type + "E": 1562305380000, // Event time + "s": "BTCUSDT", // Symbol + "p": "11185.87786614", // Mark price + "r": "0.00030000", // Funding rate + "T": 1562306400000 // Next funding time + } + ] + """ + stream_name = '!markPrice@arr@1s' if fast else '!markPrice@arr' + return self._get_futures_socket(stream_name, futures_type=futures_type) + + def symbol_ticker_futures_socket(self, symbol: str, futures_type: FuturesType = FuturesType.USD_M): + """Start a websocket for a symbol's ticker data + By default all markets are included in an array. + https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-book-ticker-streams + :param symbol: required + :param futures_type: use USD-M or COIN-M futures default USD-M + :returns: connection key string if successful, False otherwise + .. code-block:: python + [ + { + "u":400900217, // order book updateId + "s":"BNBUSDT", // symbol + "b":"25.35190000", // best bid price + "B":"31.21000000", // best bid qty + "a":"25.36520000", // best ask price + "A":"40.66000000" // best ask qty + } + ] + """ + return self._get_futures_socket(symbol.lower() + '@bookTicker', futures_type=futures_type) + + def individual_symbol_ticker_futures_socket(self, symbol: str, futures_type: FuturesType = FuturesType.USD_M): + """Start a futures websocket for a single symbol's ticker data + https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-ticker-streams + :param symbol: required + :type symbol: str + :param futures_type: use USD-M or COIN-M futures default USD-M + :returns: connection key string if successful, False otherwise + .. code-block:: python + { + "e": "24hrTicker", // Event type + "E": 123456789, // Event time + "s": "BTCUSDT", // Symbol + "p": "0.0015", // Price change + } + """ + return self._get_futures_socket(symbol.lower() + '@ticker', futures_type=futures_type) + + def all_ticker_futures_socket(self, futures_type: FuturesType = FuturesType.USD_M): + """Start a websocket for all ticker data + By default all markets are included in an array. + https://binance-docs.github.io/apidocs/futures/en/#all-book-tickers-stream + :param futures_type: use USD-M or COIN-M futures default USD-M + :returns: connection key string if successful, False otherwise + Message Format + .. code-block:: python + [ + { + "u":400900217, // order book updateId + "s":"BNBUSDT", // symbol + "b":"25.35190000", // best bid price + "B":"31.21000000", // best bid qty + "a":"25.36520000", // best ask price + "A":"40.66000000" // best ask qty + } + ] + """ + + return self._get_futures_socket('!bookTicker', futures_type=futures_type) + + def symbol_book_ticker_socket(self, symbol: str): + """Start a websocket for the best bid or ask's price or quantity for a specified symbol. + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#individual-symbol-book-ticker-streams + + :param symbol: required + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + "u":400900217, // order book updateId + "s":"BNBUSDT", // symbol + "b":"25.35190000", // best bid price + "B":"31.21000000", // best bid qty + "a":"25.36520000", // best ask price + "A":"40.66000000" // best ask qty + } + + """ + return self._get_socket(symbol.lower() + '@bookTicker') + + def book_ticker_socket(self): + """Start a websocket for the best bid or ask's price or quantity for all symbols. + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#all-book-tickers-stream + + :returns: connection key string if successful, False otherwise + + Message Format + + .. code-block:: python + + { + // Same as @bookTicker payload + } + + """ + return self._get_socket('!bookTicker') + + def multiplex_socket(self, streams: List[str]): + """Start a multiplexed socket using a list of socket names. + User stream sockets can not be included. + + Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker + + Combined stream events are wrapped as follows: {"stream":"","data":} + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md + + :param streams: list of stream names in lower case + :type streams: list + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + + """ + path = f'streams={"/".join(streams)}' + return self._get_socket(path, prefix='stream?') + + def options_multiplex_socket(self, streams: List[str]): + """Start a multiplexed socket using a list of socket names. + User stream sockets can not be included. + + Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker + + Combined stream events are wrapped as follows: {"stream":"","data":} + + https://binance-docs.github.io/apidocs/voptions/en/#account-and-trading-interface + + :param streams: list of stream names in lower case + :type streams: list + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + + """ + stream_name = '/'.join([s.lower() for s in streams]) + stream_path = f'streams={stream_name}' + return self._get_options_socket(stream_path, prefix='stream?') + + def futures_multiplex_socket(self, streams: List[str], futures_type: FuturesType = FuturesType.USD_M): + """Start a multiplexed socket using a list of socket names. + User stream sockets can not be included. + + Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker + + Combined stream events are wrapped as follows: {"stream":"","data":} + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md + + :param streams: list of stream names in lower case + :param futures_type: use USD-M or COIN-M futures default USD-M + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + + """ + path = f'streams={"/".join(streams)}' + return self._get_futures_socket(path, prefix='stream?', futures_type=futures_type) + + def user_socket(self): + """Start a websocket for user data + + https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md + https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + """ + stream_url = self.STREAM_URL + if self.testnet: + stream_url = self.STREAM_TESTNET_URL + return self._get_account_socket('user', stream_url=stream_url) + + def futures_user_socket(self): + """Start a websocket for futures user data + + https://binance-docs.github.io/apidocs/futures/en/#user-data-streams + + :returns: connection key string if successful, False otherwise + + Message Format - see Binanace API docs for all types + """ + + stream_url = self.FSTREAM_URL + if self.testnet: + stream_url = self.FSTREAM_TESTNET_URL + return self._get_account_socket('futures', stream_url=stream_url) + + def coin_futures_user_socket(self): + """Start a websocket for coin futures user data + + https://binance-docs.github.io/apidocs/delivery/en/#user-data-streams + + :returns: connection key string if successful, False otherwise + + Message Format - see Binanace API docs for all types + """ + + return self._get_account_socket('coin_futures', stream_url=self.DSTREAM_URL) + + def margin_socket(self): + """Start a websocket for cross-margin data + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + """ + stream_url = self.STREAM_URL + if self.testnet: + stream_url = self.STREAM_TESTNET_URL + return self._get_account_socket('margin', stream_url=stream_url) + + def futures_socket(self): + """Start a websocket for futures data + + https://binance-docs.github.io/apidocs/futures/en/#websocket-market-streams + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + """ + stream_url = self.FSTREAM_URL + if self.testnet: + stream_url = self.FSTREAM_TESTNET_URL + return self._get_account_socket('futures', stream_url=stream_url) + + def coin_futures_socket(self): + """Start a websocket for coin futures data + + https://binance-docs.github.io/apidocs/delivery/en/#websocket-market-streams + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + """ + stream_url = self.DSTREAM_URL + if self.testnet: + stream_url = self.DSTREAM_TESTNET_URL + return self._get_account_socket('coin_futures', stream_url=stream_url) + + def isolated_margin_socket(self, symbol: str): + """Start a websocket for isolated margin data + + https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin + + :param symbol: required - symbol for the isolated margin account + :type symbol: str + + :returns: connection key string if successful, False otherwise + + Message Format - see Binance API docs for all types + """ + stream_url = self.STREAM_URL + if self.testnet: + stream_url = self.STREAM_TESTNET_URL + return self._get_account_socket(symbol, stream_url=stream_url) + + def options_ticker_socket(self, symbol: str): + """Subscribe to a 24 hour ticker info stream + + https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-24-hour-ticker + + :param symbol: required + :type symbol: str + """ + return self._get_options_socket(symbol.lower() + '@ticker') + + def options_ticker_by_expiration_socket(self, symbol: str, expiration_date: str): + """Subscribe to a 24 hour ticker info stream + https://binance-docs.github.io/apidocs/voptions/en/#24-hour-ticker-by-underlying-asset-and-expiration-data + :param symbol: required + :type symbol: str + :param expiration_date : required + :type expiration_date: str + """ + return self._get_options_socket(symbol.lower() + '@ticker@' + expiration_date) + + def options_recent_trades_socket(self, symbol: str): + """Subscribe to a latest completed trades stream + + https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-latest-completed-trades + + :param symbol: required + :type symbol: str + """ + return self._get_options_socket(symbol.lower() + '@trade') + + def options_kline_socket(self, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE): + """Subscribe to a candlestick data stream + + https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-candle + + :param symbol: required + :type symbol: str + :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE + :type interval: str + """ + return self._get_options_socket(symbol.lower() + '@kline_' + interval) + + def options_depth_socket(self, symbol: str, depth: str = '10'): + """Subscribe to a depth data stream + + https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-depth + + :param symbol: required + :type symbol: str + :param depth: optional Number of depth entries to return, default 10. + :type depth: str + """ + return self._get_options_socket(symbol.lower() + '@depth' + str(depth)) + + async def _stop_socket(self, conn_key): + """Stop a websocket given the connection key + + :param conn_key: Socket connection key + :type conn_key: string + + :returns: None + """ + if conn_key not in self._conns: + return + + del (self._conns[conn_key]) + + +class ThreadedWebsocketManager(ThreadedApiManager): + + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', + testnet: bool = False, session_params: Optional[Dict[str, Any]] = None + ): + super().__init__(api_key, api_secret, requests_params, tld, testnet, session_params) + self._bsm: Optional[BinanceSocketManager] = None + + async def _before_socket_listener_start(self): + assert self._client + self._bsm = BinanceSocketManager(client=self._client) + + def _start_async_socket( + self, callback: Callable, socket_name: str, params: Dict[str, Any], path: Optional[str] = None + ) -> str: + while not self._bsm: + time.sleep(0.1) + socket = getattr(self._bsm, socket_name)(**params) + socket_path: str = path or socket._path # noqa + self._socket_running[socket_path] = True + self._loop.call_soon_threadsafe(asyncio.create_task, self.start_listener(socket, socket_path, callback)) + return socket_path + + def start_depth_socket( + self, callback: Callable, symbol: str, depth: Optional[str] = None, interval: Optional[int] = None + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='depth_socket', + params={ + 'symbol': symbol, + 'depth': depth, + 'interval': interval, + } + ) + + def start_kline_socket(self, callback: Callable, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE) -> str: + return self._start_async_socket( + callback=callback, + socket_name='kline_socket', + params={ + 'symbol': symbol, + 'interval': interval, + } + ) + + def start_kline_futures_socket(self, callback: Callable, symbol: str, + interval=AsyncClient.KLINE_INTERVAL_1MINUTE, + futures_type: FuturesType = FuturesType.USD_M, + contract_type: ContractType = ContractType.PERPETUAL) -> str: + return self._start_async_socket( + callback=callback, + socket_name='kline_futures_socket', + params={ + 'symbol': symbol, + 'interval': interval, + 'futures_type': futures_type, + 'contract_type': contract_type + } + ) + + def start_miniticker_socket(self, callback: Callable, update_time: int = 1000) -> str: + return self._start_async_socket( + callback=callback, + socket_name='miniticker_socket', + params={ + 'update_time': update_time, + } + ) + + def start_trade_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='trade_socket', + params={ + 'symbol': symbol, + } + ) + + def start_aggtrade_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='aggtrade_socket', + params={ + 'symbol': symbol, + } + ) + + def start_aggtrade_futures_socket( + self, callback: Callable, symbol: str, futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='aggtrade_futures_socket', + params={ + 'symbol': symbol, + 'futures_type': futures_type, + } + ) + + def start_symbol_miniticker_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='symbol_miniticker_socket', + params={ + 'symbol': symbol, + } + ) + + def start_symbol_ticker_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='symbol_ticker_socket', + params={ + 'symbol': symbol, + } + ) + + def start_ticker_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='ticker_socket', + params={} + ) + + def start_index_price_socket(self, callback: Callable, symbol: str, fast: bool = True) -> str: + return self._start_async_socket( + callback=callback, + socket_name='index_price_socket', + params={ + 'symbol': symbol, + 'fast': fast + } + ) + + def start_symbol_mark_price_socket( + self, callback: Callable, symbol: str, fast: bool = True, futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='symbol_mark_price_socket', + params={ + 'symbol': symbol, + 'fast': fast, + 'futures_type': futures_type + } + ) + + def start_all_mark_price_socket( + self, callback: Callable, fast: bool = True, futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='all_mark_price_socket', + params={ + 'fast': fast, + 'futures_type': futures_type + } + ) + + def start_symbol_ticker_futures_socket( + self, callback: Callable, symbol: str, futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='symbol_ticker_futures_socket', + params={ + 'symbol': symbol, + 'futures_type': futures_type + } + ) + + def start_individual_symbol_ticker_futures_socket( + self, callback: Callable, symbol: str, futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='individual_symbol_ticker_futures_socket', + params={ + 'symbol': symbol, + 'futures_type': futures_type + } + ) + + def start_all_ticker_futures_socket(self, callback: Callable, futures_type: FuturesType = FuturesType.USD_M) -> str: + return self._start_async_socket( + callback=callback, + socket_name='all_ticker_futures_socket', + params={ + 'futures_type': futures_type + } + ) + + def start_symbol_book_ticker_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='symbol_book_ticker_socket', + params={ + 'symbol': symbol + } + ) + + def start_book_ticker_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='book_ticker_socket', + params={} + ) + + def start_multiplex_socket(self, callback: Callable, streams: List[str]) -> str: + return self._start_async_socket( + callback=callback, + socket_name='multiplex_socket', + params={ + 'streams': streams + } + ) + + def start_options_multiplex_socket(self, callback: Callable, streams: List[str]) -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_multiplex_socket', + params={ + 'streams': streams + } + ) + + def start_futures_multiplex_socket( + self, callback: Callable, streams: List[str], futures_type: FuturesType = FuturesType.USD_M + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='futures_multiplex_socket', + params={ + 'streams': streams, + 'futures_type': futures_type + } + ) + + def start_user_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='user_socket', + params={} + ) + + def start_futures_user_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='futures_user_socket', + params={} + ) + + def start_coin_futures_user_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='coin_futures_user_socket', + params={} + ) + + def start_margin_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='margin_socket', + params={} + ) + + def start_futures_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='futures_socket', + params={} + ) + + def start_coin_futures_socket(self, callback: Callable) -> str: + return self._start_async_socket( + callback=callback, + socket_name='coin_futures_socket', + params={} + ) + + def start_isolated_margin_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='isolated_margin_socket', + params={ + 'symbol': symbol + } + ) + + def start_options_ticker_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_ticker_socket', + params={ + 'symbol': symbol + } + ) + + def start_options_ticker_by_expiration_socket(self, callback: Callable, symbol: str, expiration_date: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_ticker_by_expiration_socket', + params={ + 'symbol': symbol, + 'expiration_date': expiration_date + } + ) + + def start_options_recent_trades_socket(self, callback: Callable, symbol: str) -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_recent_trades_socket', + params={ + 'symbol': symbol + } + ) + + def start_options_kline_socket( + self, callback: Callable, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE + ) -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_kline_socket', + params={ + 'symbol': symbol, + 'interval': interval + } + ) + + def start_options_depth_socket(self, callback: Callable, symbol: str, depth: str = '10') -> str: + return self._start_async_socket( + callback=callback, + socket_name='options_depth_socket', + params={ + 'symbol': symbol, + 'depth': depth + } + ) + + def start_futures_depth_socket(self, callback: Callable, symbol: str, depth: str = '10', futures_type=FuturesType.USD_M) -> str: + return self._start_async_socket( + callback=callback, + socket_name='futures_depth_socket', + params={ + 'symbol': symbol, + 'depth': depth, + 'futures_type': futures_type + } + ) diff --git a/binance/threaded_stream.py b/binance/threaded_stream.py new file mode 100755 index 000000000..d0a4bcf49 --- /dev/null +++ b/binance/threaded_stream.py @@ -0,0 +1,76 @@ +import asyncio +import threading +from typing import Optional, Dict, Any + +from .client import AsyncClient +from .helpers import get_loop + + +class ThreadedApiManager(threading.Thread): + + def __init__( + self, api_key: Optional[str] = None, api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, tld: str = 'com', + testnet: bool = False, session_params: Optional[Dict[str, Any]] = None + ): + """Initialise the BinanceSocketManager + + """ + super().__init__() + self._loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if asyncio.get_event_loop().is_running() else asyncio.new_event_loop() + self._client: Optional[AsyncClient] = None + self._running: bool = True + self._socket_running: Dict[str, bool] = {} + self._client_params = { + 'api_key': api_key, + 'api_secret': api_secret, + 'requests_params': requests_params, + 'tld': tld, + 'testnet': testnet, + 'session_params': session_params, + } + + async def _before_socket_listener_start(self): + ... + + async def socket_listener(self): + self._client = await AsyncClient.create(loop=self._loop, **self._client_params) + await self._before_socket_listener_start() + while self._running: + await asyncio.sleep(0.2) + while self._socket_running: + await asyncio.sleep(0.2) + + async def start_listener(self, socket, path: str, callback): + async with socket as s: + while self._socket_running[path]: + try: + msg = await asyncio.wait_for(s.recv(), 3) + except asyncio.TimeoutError: + ... + continue + else: + if not msg: + continue + callback(msg) + del self._socket_running[path] + + def run(self): + self._loop.run_until_complete(self.socket_listener()) + + def stop_socket(self, socket_name): + if socket_name in self._socket_running: + self._socket_running[socket_name] = False + + async def stop_client(self): + if not self._client: + return + await self._client.close_connection() + + def stop(self): + if not self._running: + return + self._running = False + self._loop.call_soon(asyncio.create_task, self.stop_client()) + for socket_name in self._socket_running.keys(): + self._socket_running[socket_name] = False diff --git a/binance/websockets.py b/binance/websockets.py deleted file mode 100644 index f60910ed0..000000000 --- a/binance/websockets.py +++ /dev/null @@ -1,527 +0,0 @@ -# coding=utf-8 - -import json -import threading - -from autobahn.twisted.websocket import WebSocketClientFactory, \ - WebSocketClientProtocol, \ - connectWS -from twisted.internet import reactor, ssl -from twisted.internet.protocol import ReconnectingClientFactory -from twisted.internet.error import ReactorAlreadyRunning - -from binance.client import Client - - -class BinanceClientProtocol(WebSocketClientProtocol): - - def __init__(self): - super(WebSocketClientProtocol, self).__init__() - - def onConnect(self, response): - # reset the delay after reconnecting - self.factory.resetDelay() - - def onMessage(self, payload, isBinary): - if not isBinary: - try: - payload_obj = json.loads(payload.decode('utf8')) - except ValueError: - pass - else: - self.factory.callback(payload_obj) - - -class BinanceReconnectingClientFactory(ReconnectingClientFactory): - - # set initial delay to a short time - initialDelay = 0.1 - - maxDelay = 10 - - maxRetries = 5 - - -class BinanceClientFactory(WebSocketClientFactory, BinanceReconnectingClientFactory): - - protocol = BinanceClientProtocol - _reconnect_error_payload = { - 'e': 'error', - 'm': 'Max reconnect retries reached' - } - - def clientConnectionFailed(self, connector, reason): - self.retry(connector) - if self.retries > self.maxRetries: - self.callback(self._reconnect_error_payload) - - def clientConnectionLost(self, connector, reason): - self.retry(connector) - if self.retries > self.maxRetries: - self.callback(self._reconnect_error_payload) - - -class BinanceSocketManager(threading.Thread): - - STREAM_URL = 'wss://stream.binance.com:9443/' - - WEBSOCKET_DEPTH_5 = '5' - WEBSOCKET_DEPTH_10 = '10' - WEBSOCKET_DEPTH_20 = '20' - - DEFAULT_USER_TIMEOUT = 30 * 60 # 30 minutes - - def __init__(self, client, user_timeout=DEFAULT_USER_TIMEOUT): - """Initialise the BinanceSocketManager - - :param client: Binance API client - :type client: binance.Client - :param user_timeout: Custom websocket timeout - :type user_timeout: int - - """ - threading.Thread.__init__(self) - self._conns = {} - self._user_timer = None - self._user_listen_key = None - self._user_callback = None - self._client = client - self._user_timeout = user_timeout - - def _start_socket(self, path, callback, prefix='ws/'): - if path in self._conns: - return False - - factory_url = self.STREAM_URL + prefix + path - factory = BinanceClientFactory(factory_url) - factory.protocol = BinanceClientProtocol - factory.callback = callback - factory.reconnect = True - context_factory = ssl.ClientContextFactory() - - self._conns[path] = connectWS(factory, context_factory) - return path - - def start_depth_socket(self, symbol, callback, depth=None): - """Start a websocket for symbol market depth returning either a diff or a partial book - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams - - :param symbol: required - :type symbol: str - :param callback: callback function to handle messages - :type callback: function - :param depth: optional Number of depth entries to return, default None. If passed returns a partial book instead of a diff - :type depth: str - - :returns: connection key string if successful, False otherwise - - Partial Message Format - - .. code-block:: python - - { - "lastUpdateId": 160, # Last update ID - "bids": [ # Bids to be updated - [ - "0.0024", # price level to be updated - "10", # quantity - [] # ignore - ] - ], - "asks": [ # Asks to be updated - [ - "0.0026", # price level to be updated - "100", # quantity - [] # ignore - ] - ] - } - - - Diff Message Format - - .. code-block:: python - - { - "e": "depthUpdate", # Event type - "E": 123456789, # Event time - "s": "BNBBTC", # Symbol - "U": 157, # First update ID in event - "u": 160, # Final update ID in event - "b": [ # Bids to be updated - [ - "0.0024", # price level to be updated - "10", # quantity - [] # ignore - ] - ], - "a": [ # Asks to be updated - [ - "0.0026", # price level to be updated - "100", # quantity - [] # ignore - ] - ] - } - - """ - socket_name = symbol.lower() + '@depth' - if depth and depth != '1': - socket_name = '{}{}'.format(socket_name, depth) - return self._start_socket(socket_name, callback) - - def start_kline_socket(self, symbol, callback, interval=Client.KLINE_INTERVAL_1MINUTE): - """Start a websocket for symbol kline data - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#klinecandlestick-streams - - :param symbol: required - :type symbol: str - :param callback: callback function to handle messages - :type callback: function - :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE - :type interval: str - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - { - "e": "kline", # event type - "E": 1499404907056, # event time - "s": "ETHBTC", # symbol - "k": { - "t": 1499404860000, # start time of this bar - "T": 1499404919999, # end time of this bar - "s": "ETHBTC", # symbol - "i": "1m", # interval - "f": 77462, # first trade id - "L": 77465, # last trade id - "o": "0.10278577", # open - "c": "0.10278645", # close - "h": "0.10278712", # high - "l": "0.10278518", # low - "v": "17.47929838", # volume - "n": 4, # number of trades - "x": false, # whether this bar is final - "q": "1.79662878", # quote volume - "V": "2.34879839", # volume of active buy - "Q": "0.24142166", # quote volume of active buy - "B": "13279784.01349473" # can be ignored - } - } - """ - socket_name = '{}@kline_{}'.format(symbol.lower(), interval) - return self._start_socket(socket_name, callback) - - def start_miniticker_socket(self, callback, update_time=1000): - """Start a miniticker websocket for all trades - - This is not in the official Binance api docs, but this is what - feeds the right column on a ticker page on Binance. - - :param callback: callback function to handle messages - :type callback: function - :param update_time: time between callbacks in milliseconds, must be 1000 or greater - :type update_time: int - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - [ - { - 'e': '24hrMiniTicker', # Event type - 'E': 1515906156273, # Event time - 's': 'QTUMETH', # Symbol - 'c': '0.03836900', # close - 'o': '0.03953500', # open - 'h': '0.04400000', # high - 'l': '0.03756000', # low - 'v': '147435.80000000', # volume - 'q': '5903.84338533' # quote volume - } - ] - """ - - return self._start_socket('!miniTicker@arr@{}ms'.format(update_time), callback) - - def start_trade_socket(self, symbol, callback): - """Start a websocket for symbol trade data - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#trade-streams - - :param symbol: required - :type symbol: str - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - { - "e": "trade", # Event type - "E": 123456789, # Event time - "s": "BNBBTC", # Symbol - "t": 12345, # Trade ID - "p": "0.001", # Price - "q": "100", # Quantity - "b": 88, # Buyer order Id - "a": 50, # Seller order Id - "T": 123456785, # Trade time - "m": true, # Is the buyer the market maker? - "M": true # Ignore. - } - - """ - return self._start_socket(symbol.lower() + '@trade', callback) - - def start_aggtrade_socket(self, symbol, callback): - """Start a websocket for symbol trade data - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#aggregate-trade-streams - - :param symbol: required - :type symbol: str - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - { - "e": "aggTrade", # event type - "E": 1499405254326, # event time - "s": "ETHBTC", # symbol - "a": 70232, # aggregated tradeid - "p": "0.10281118", # price - "q": "8.15632997", # quantity - "f": 77489, # first breakdown trade id - "l": 77489, # last breakdown trade id - "T": 1499405254324, # trade time - "m": false, # whether buyer is a maker - "M": true # can be ignored - } - - """ - return self._start_socket(symbol.lower() + '@aggTrade', callback) - - def start_symbol_ticker_socket(self, symbol, callback): - """Start a websocket for a symbol's ticker data - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#individual-symbol-ticker-streams - - :param symbol: required - :type symbol: str - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - { - "e": "24hrTicker", # Event type - "E": 123456789, # Event time - "s": "BNBBTC", # Symbol - "p": "0.0015", # Price change - "P": "250.00", # Price change percent - "w": "0.0018", # Weighted average price - "x": "0.0009", # Previous day's close price - "c": "0.0025", # Current day's close price - "Q": "10", # Close trade's quantity - "b": "0.0024", # Best bid price - "B": "10", # Bid bid quantity - "a": "0.0026", # Best ask price - "A": "100", # Best ask quantity - "o": "0.0010", # Open price - "h": "0.0025", # High price - "l": "0.0010", # Low price - "v": "10000", # Total traded base asset volume - "q": "18", # Total traded quote asset volume - "O": 0, # Statistics open time - "C": 86400000, # Statistics close time - "F": 0, # First trade ID - "L": 18150, # Last trade Id - "n": 18151 # Total number of trades - } - - """ - return self._start_socket(symbol.lower() + '@ticker', callback) - - def start_ticker_socket(self, callback): - """Start a websocket for all ticker data - - By default all markets are included in an array. - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#all-market-tickers-stream - - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - - .. code-block:: python - - [ - { - 'F': 278610, - 'o': '0.07393000', - 's': 'BCCBTC', - 'C': 1509622420916, - 'b': '0.07800800', - 'l': '0.07160300', - 'h': '0.08199900', - 'L': 287722, - 'P': '6.694', - 'Q': '0.10000000', - 'q': '1202.67106335', - 'p': '0.00494900', - 'O': 1509536020916, - 'a': '0.07887800', - 'n': 9113, - 'B': '1.00000000', - 'c': '0.07887900', - 'x': '0.07399600', - 'w': '0.07639068', - 'A': '2.41900000', - 'v': '15743.68900000' - } - ] - """ - return self._start_socket('!ticker@arr', callback) - - def start_multiplex_socket(self, streams, callback): - """Start a multiplexed socket using a list of socket names. - User stream sockets can not be included. - - Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker - - Combined stream events are wrapped as follows: {"stream":"","data":} - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md - - :param streams: list of stream names in lower case - :type streams: list - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - see Binance API docs for all types - - """ - stream_path = 'streams={}'.format('/'.join(streams)) - return self._start_socket(stream_path, callback, 'stream?') - - def start_user_socket(self, callback): - """Start a websocket for user data - - https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md - - :param callback: callback function to handle messages - :type callback: function - - :returns: connection key string if successful, False otherwise - - Message Format - see Binance API docs for all types - """ - # Get the user listen key - user_listen_key = self._client.stream_get_listen_key() - # and start the socket with this specific key - conn_key = self._start_user_socket(user_listen_key, callback) - return conn_key - - def _start_user_socket(self, user_listen_key, callback): - # With this function we can start a user socket with a specific key - if self._user_listen_key: - # cleanup any sockets with this key - for conn_key in self._conns: - if len(conn_key) >= 60 and conn_key[:60] == self._user_listen_key: - self.stop_socket(conn_key) - break - self._user_listen_key = user_listen_key - self._user_callback = callback - conn_key = self._start_socket(self._user_listen_key, callback) - if conn_key: - # start timer to keep socket alive - self._start_user_timer() - - return conn_key - - def _start_user_timer(self): - self._user_timer = threading.Timer(self._user_timeout, self._keepalive_user_socket) - self._user_timer.setDaemon(True) - self._user_timer.start() - - def _keepalive_user_socket(self): - user_listen_key = self._client.stream_get_listen_key() - # check if they key changed and - if user_listen_key != self._user_listen_key: - # Start a new socket with the key received - # `_start_user_socket` automatically cleanup open sockets - # and starts timer to keep socket alive - self._start_user_socket(user_listen_key, self._user_callback) - else: - # Restart timer only if the user listen key is not changed - self._start_user_timer() - - def stop_socket(self, conn_key): - """Stop a websocket given the connection key - - :param conn_key: Socket connection key - :type conn_key: string - - :returns: connection key string if successful, False otherwise - """ - if conn_key not in self._conns: - return - - # disable reconnecting if we are closing - self._conns[conn_key].factory = WebSocketClientFactory(self.STREAM_URL + 'tmp_path') - self._conns[conn_key].disconnect() - del(self._conns[conn_key]) - - # check if we have a user stream socket - if len(conn_key) >= 60 and conn_key[:60] == self._user_listen_key: - self._stop_user_socket() - - def _stop_user_socket(self): - if not self._user_listen_key: - return - # stop the timer - self._user_timer.cancel() - self._user_timer = None - self._user_listen_key = None - - def run(self): - try: - reactor.run(installSignalHandlers=False) - except ReactorAlreadyRunning: - # Ignore error about reactor already running - pass - - def close(self): - """Close all connections - - """ - keys = set(self._conns.keys()) - for key in keys: - self.stop_socket(key) - - self._conns = {} diff --git a/docs/account.rst b/docs/account.rst index cc1fb69e1..17d3cbd9c 100644 --- a/docs/account.rst +++ b/docs/account.rst @@ -9,10 +9,13 @@ Order Validation Binance has a number of rules around symbol pair orders with validation on minimum price, quantity and total order value. -Read more about their specifics in the `Filters `_ +Read more about their specifics in the `Filters `_ section of the official API. -It can be helpful to format the output using the following snippet +Read `Understanding Binance Order Filters `_ +for more information about price and quantity filters on `Binance `_. + +It can be helpful to format the output using formatting .. code:: python @@ -20,6 +23,16 @@ It can be helpful to format the output using the following snippet precision = 5 amt_str = "{:0.0{}f}".format(amount, precision) +Or if you have the tickSize or stepSize then use the helper to round to step size + +.. code:: python + + from binance.helpers import round_step_size + + amount = 0.000234234 + tick_size = 0.00001 + rounded_amount = round_step_size(amount, tick_size) + `Fetch all orders `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -78,6 +91,22 @@ Use the helper functions to easily place a market buy or sell order symbol='BNBBTC', quantity=100) +**Place an OCO order** + +Use the `create_oco_order` function to have full control over creating an OCO order + +.. code:: python + + from binance.enums import * + order = client.create_oco_order( + symbol='BNBBTC', + side=SIDE_SELL, + stopLimitTimeInForce=TIME_IN_FORCE_GTC, + quantity=100, + stopPrice='0.00001', + price='0.00002') + + `Place a test order `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -153,6 +182,13 @@ Account status = client.get_account_status() +`Get account API trading status `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + status = client.get_account_api_trading_status() + `Get trades `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -184,3 +220,34 @@ Account .. code:: python log = client.get_dust_log() + +`Transfer dust `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transfer = client.transfer_dust(asset='BNZ') + + +`Get Asset Dividend History `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + history = client.get_asset_dividend_history() + + +`Disable Fast Withdraw Switch `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + client.disable_fast_withdraw_switch() + + +`Enable Fast Withdraw Switch `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + client.enable_fast_withdraw_switch() diff --git a/docs/binance.rst b/docs/binance.rst index 0bc33386f..c243a5fc3 100644 --- a/docs/binance.rst +++ b/docs/binance.rst @@ -2,7 +2,7 @@ Binance API =========== client module ----------------------- +------------- .. automodule:: binance.client :members: @@ -10,7 +10,7 @@ client module :show-inheritance: depthcache module --------------------------- +----------------- .. automodule:: binance.depthcache :members: @@ -18,7 +18,7 @@ depthcache module :show-inheritance: exceptions module --------------------------- +----------------- .. automodule:: binance.exceptions :members: @@ -26,7 +26,7 @@ exceptions module :show-inheritance: helpers module --------------------------- +-------------- .. automodule:: binance.helpers :members: @@ -34,9 +34,9 @@ helpers module :show-inheritance: websockets module --------------------------- +----------------- -.. automodule:: binance.websockets +.. automodule:: binance.streams :members: :undoc-members: :show-inheritance: diff --git a/docs/changelog.rst b/docs/changelog.rst old mode 100644 new mode 100755 index 6faf03510..180d1b0ac --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,360 @@ Changelog ========= +v1.0.21 - 2024-10-23 +^^^^^^^^^^^^^^^^^^^^ + +**Added** +- Some new endpoints + +**Fixed** +- url encoding for coin-m post endpoints +- batch order endpoint +- Some minor bug fixes + +v1.0.20 - 2024-10-22 +^^^^^^^^^^^^^^^^^^^^ + +**Added** +- EDDSA authentication +- Portfolio Margin endpoints +- Some new futures endpoints +- Proxy support for the AsyncClient +- Version override is possible now through `params` + +**Fixed** +- Migrated `positionRisk` to v3 +- Fixed the error `AttributeError("'Connect' object has no attribute 'protocol'")` +- Some minor bug fixes + +v1.0.19 - 2023-08-11 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- some new futures and margin endpoints +- pass session_params to streams for AsyncClient + +**Fixed** + +- removed debug statements +- options testnet URL +- accessing msg variable before assignment + +v1.0.18 - 2023-08-09 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- TRAILING_STOP_MARKET option for orders + +**Fixed** + +- futures api endpoint versions +- margin endpoint request methods + + +v1.0.17 - 2023-02-21 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- RSA key authentication +- Support for api1, api2, api3, api4 base endpoints +- binance.us staking endpoints +- Options ticker by expiration socket +- Staking endpoints +- Pay and Convert endpoints +- Futures index info endpoint +- Open OCO Orders endpoint +- Param to pass session params to aiohttp.ClientSession + +**Updated** + +- Some margin endpoint versions +- Support testnet for more streams + +**Fixed** + +- Indefinite websocket reconnect loop +- Crash on parsing code from some errors + +v1.0.16 - 2022-04-09 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- pass limit param to all kline functions +- increase default for kline functions from 500 to 1000 +- add HistoricalKlinesType.FUTURES_COIN as option for kline functions +- testnet URL for coin_futures_socket + +**Updated** + +- round_step_size more accurate + +**Fixed** + +- remove deprecated loop param +- websockets unpinned +- hanging websockets in exiting state +- check start_ts after end_ts for klines +- multi assets margin params + + +v1.0.15 - 2021-09-27 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- Enable/disable margin account for symbol endpoints +- Top trader long/short positions endpoint +- Global long/short ratio endpoint + +**Fixed** + +- fix websockets to 9.1 +- websocket reconnect updates +- fix futures kline sockets + + +v1.0.14 - 2021-09-08 +^^^^^^^^^^^^^^^^^^^^ + +**Fixed** + +- websocket reconnecting + +v1.0.13 - 2021-09-08 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- Futures Depth Cache Manager +- Futures kline websocket stream +- Coin Futures User websocket stream +- New Margin endpoints +- Margin OCO order endpoints +- Fiat endpoints +- C2C endpoints +- Account API permissions endpoint + +**Fixed** + +- changed `asset` to `coin` in withdraw endpoint + + +v1.0.12 - 2021-06-03 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- coin futures batch order function + +**Fixed** + +- threaded websockets on python3.9 +- filter out None params in request kwargs +- deconflict streams with same name on different websocket urls +- reduce close timeout on websocket close to short time to reduce waiting + + +v1.0.10 - 2021-05-13 +^^^^^^^^^^^^^^^^^^^^ + +**Added** + +- futures multi-asset margin mode endpoints +- optional symbol param to get_all_tickers + +**Fixed** + +- start_multiplex_socket remove lower case filter on stream names + +v1.0.9 - 2021-05-12 +^^^^^^^^^^^^^^^^^^^ + +**Fixed** + +- start_book_ticker_socket and start_multiplex_socket to call correct async function + +v1.0.8 - 2021-05-11 +^^^^^^^^^^^^^^^^^^^ + +**Added** + +- old style websocket and depth cache managers as option without interacting with asyncio + +**Fixed** + +- fixed issue with get_historical_klines in Client +- remove print debug line + +v1.0.7 +^^^^^^ + +**Fixed** + +- remove version param from get_sub_account_assets + +v1.0.6 +^^^^^^ + +**Fixed** + +- fix time for authenticated stream keepalive + +v1.0.5 +^^^^^^ + +**Fixed** + +- Restored access to last response on client + +v1.0.4 +^^^^^^ + +**Added** + +- Futures Testnet support +- Kline type for fetching historical klines + +**Fixed** + +- Spot Testnet websocket URL + +v1.0.3 +^^^^^^ + +**Added** + +- Spot Testnet support + +v1.0.2 +^^^^^^ + +**Added** + +- start of typing to client and websockets + +**Fixed** + +- end_str, limit, spot params in kline fetching +- drop None values in params passed + +**Updated** + +- more examples in docs + +v1.0.1 +^^^^^^ + +**Fixed** + +- restored params for Client and AsyncClient classes + +v1.0.0 +^^^^^^ + +**Added** + +- Async support for all REST endpoints +- USDⓈ-M and Coin-M Futures websocket streams +- Websockets use same tld as Client +- convert type option for DepthCache + +**Breaking Changes** + +- Supports only py3.6+ +- All wapi calls changed to sapi +- Websockets have changed to use Asynchronous context managers + +**Fixed** + +- get_historical_klines params + +v0.7.11 +^^^^^^^ + +**Added** +- Vanilla Options REST endpoints +- Vanilla Options websockets +- Futures order type enums + +**Updated** + +- websocket keep-alive functions for different socket types +- dependencies + +**Fixed** + +- change to User-Agent to avoid connection issues + +v0.7.5.dev +^^^^^^^^^^ +**Changed** +- Stock json lib to ujson (https://github.com/sammchardy/python-binance/pull/383) + +v0.7.5 - 2020-02-06 +^^^^^^^^^^^^^^^^^^^ + +**Added** + +- Futures REST endpoints +- Lending REST endpoints +- OCO Orders function `create_oco_order`, `order_oco_buy`, `order_oco_sell` +- Average Price function `get_avg_price` +- Support for other domains (.us, .jp, etc) + +**Updated** + +- dependencies + +**Fixed** + +- websocket keepalive callback not found + +v0.7.4 - 2019-09-22 +^^^^^^^^^^^^^^^^^^^ + +**Added** + +- symbol book ticker websocket streams +- margin websocket stream + +**Updated** + +- can call Client without any params +- make response a property of the Client class so you can access response properties after a request + +**Fixed** + +- issue with None value params causing errors + +v0.7.3 - 2019-08-12 +^^^^^^^^^^^^^^^^^^^ + +**Added** + +- sub account endpoints +- dust transfer endpoint +- asset divident history endpoint + +**Removed** + +- deprecated withdraw fee endpoint + +v0.7.2 - 2019-08-01 +^^^^^^^^^^^^^^^^^^^ + +**Added** + +- margin trading endpoints + +**Fixed** + +- depth cache clearing bug + v0.7.1 - 2019-01-23 ^^^^^^^^^^^^^^^^^^^ diff --git a/docs/conf.py b/docs/conf.py index 00a9734de..b6aaed97f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,10 +31,12 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages'] + 'sphinx.ext.githubpages', +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -86,7 +88,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -#html_theme = 'alabaster' +# html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme @@ -182,4 +184,3 @@ def skip(app, what, name, obj, skip, options): def setup(app): app.connect("autodoc-skip-member", skip) - diff --git a/docs/constants.rst b/docs/constants.rst index fcae367af..905c582ba 100644 --- a/docs/constants.rst +++ b/docs/constants.rst @@ -15,6 +15,7 @@ Binance requires specific string constants for Order Types, Order Side, Time in ORDER_STATUS_REJECTED = 'REJECTED' ORDER_STATUS_EXPIRED = 'EXPIRED' + KLINE_INTERVAL_1SECOND = '1s' KLINE_INTERVAL_1MINUTE = '1m' KLINE_INTERVAL_3MINUTE = '3m' KLINE_INTERVAL_5MINUTE = '5m' diff --git a/docs/depth_cache.rst b/docs/depth_cache.rst old mode 100644 new mode 100755 index b6e791c28..6cb97f9e9 --- a/docs/depth_cache.rst +++ b/docs/depth_cache.rst @@ -1,19 +1,136 @@ Depth Cache =========== -To follow the depth cache updates for a symbol use the `DepthCacheManager` +To follow the depth cache updates for a symbol there are 2 options similar to websockets. -Create the manager like so, passing the api client, symbol and an optional callback function. +Use the `DepthCacheManager `_ +(or `OptionsDepthCacheManager `_ for vanilla options) or +use the `ThreadedDepthCacheManager `_ +if you don't want to interact with asyncio. + +ThreadedDepthCacheManager Websocket Usage +----------------------------------------- + +Starting sockets on the ThreadedDepthCacheManager requires a callback parameter, similar to old implementations of +depth cache on python-binance pre v1 + +ThreadedDepthCacheManager takes similar parameters to the `Client `_ class +as it creates an AsyncClient internally. + +As these use threads `start()` is required to be called before starting any depth cache streams. + +To keep the ThreadedDepthCacheManager running using `join()` to join it to the main thread. .. code:: python - from binance.depthcache import DepthCacheManager - dcm = DepthCacheManager(client, 'BNBBTC', callback=process_depth) + from binance import ThreadedDepthCacheManager + + def main(): + + dcm = ThreadedDepthCacheManager() + # start is required to initialise its internal loop + dcm.start() + + def handle_depth_cache(depth_cache): + print(f"symbol {depth_cache.symbol}") + print("top 5 bids") + print(depth_cache.get_bids()[:5]) + print("top 5 asks") + print(depth_cache.get_asks()[:5]) + print("last update time {}".format(depth_cache.update_time)) + + dcm_name = dcm.start_depth_cache(handle_depth_cache, symbol='BNBBTC') + + # multiple depth caches can be started + dcm_name = dcm.start_depth_cache(handle_depth_cache, symbol='ETHBTC') -The callback function receives the current `DepthCache` object which allows access to a pre-sorted -list of bids or asks able to be filtered as required. + dcm.join() -Access the symbol value from the `depth_cache` object in case you have multiple caches using the same callback. + + if __name__ == "__main__": + main() + + +**Stop Individual Depth Cache** + +When starting a stream, a name for that stream will be returned. This can be used to stop that individual stream + +.. code:: python + + from binance import ThreadedDepthCacheManager + + symbol = 'BNBBTC' + + dcm = ThreadedDepthCacheManager() + dcm.start() + + def handle_depth_cache(depth_cache): + print(f"message type: {msg['e']}") + print(msg) + + dcm_name = dcm.start_depth_cache(handle_depth_cache, symbol='BNBBTC') + + # some time later + + dcm.stop_socket(dcm_name) + +**Stop All Depth Cache streams** + +.. code:: python + + from binance import ThreadedDepthCacheManager + + symbol = 'BNBBTC' + + dcm = ThreadedDepthCacheManager() + dcm.start() + + def handle_depth_cache(depth_cache): + print(f"message type: {msg['e']}") + print(msg) + + dcm_name = dcm.start_depth_cache(handle_depth_cache, symbol='BNBBTC') + + # some time later + + dcm.stop() + +Attempting to start a stream after `stop` is called will not work. + + +DepthCacheManager or OptionsDepthCacheManager Usage +--------------------------------------------------- + +Create the manager like so, passing the async api client, symbol and an optional callback function. + +.. code:: python + + import asyncio + + from binance import AsyncClient, DepthCacheManager + + + async def main(): + client = await AsyncClient.create() + dcm = DepthCacheManager(client, 'BNBBTC') + + async with dcm as dcm_socket: + while True: + depth_cache = await dcm_socket.recv() + print("symbol {}".format(depth_cache.symbol)) + print("top 5 bids") + print(depth_cache.get_bids()[:5]) + print("top 5 asks") + print(depth_cache.get_asks()[:5]) + print("last update time {}".format(depth_cache.update_time)) + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +The `DepthCacheManager` returns an Asynchronous Context Manager which can be used with `async for` +or by interacting with the `__aenter__` and `__aexit__` functions By default the depth cache will fetch the order book via REST request every 30 minutes. This duration can be changed by using the `refresh_interval` parameter. To disable the refresh pass 0 or None. @@ -29,18 +146,8 @@ Here dcm1 and dcm2 share the same instance of BinanceSocketManager from binance.websockets import BinanceSocketManager from binance.depthcache import DepthCacheManager bm = BinanceSocketManager(client) - dcm1 = DepthCacheManager(client, 'BNBBTC', callback=process_depth1, bm=bm) - dcm2 = DepthCacheManager(client, 'ETHBTC', callback=process_depth2, bm=bm) - -Because they both share the same BinanceSocketManager calling close can close both message streams. - -.. code:: python - - # close just dcm1 stream - dcm1.close() - - # close the underlying socket manager as well - dcm1.close(close_socket=True) + dcm1 = DepthCacheManager(client, 'BNBBTC', bm=bm) + dcm2 = DepthCacheManager(client, 'ETHBTC', bm=bm) Websocket Errors ---------------- @@ -53,42 +160,37 @@ Examples .. code:: python # 1 hour interval refresh - dcm = DepthCacheManager(client, 'BNBBTC', callback=process_depth, refresh_interval=60*60) + dcm = DepthCacheManager(client, 'BNBBTC', refresh_interval=60*60) # disable refreshing - dcm = DepthCacheManager(client, 'BNBBTC', callback=process_depth, refresh_interval=0) + dcm = DepthCacheManager(client, 'BNBBTC', refresh_interval=0) .. code:: python - def process_depth(depth_cache): - if depth_cache is not None: + async with dcm as dcm_socket: + while True: + depth_cache = await dcm_socket.recv() print("symbol {}".format(depth_cache.symbol)) print("top 5 bids") print(depth_cache.get_bids()[:5]) print("top 5 asks") print(depth_cache.get_asks()[:5]) - print("last update time {}".format(depth_cache.update_time) - else: - # depth cache had an error and needs to be restarted + print("last update time {}".format(depth_cache.update_time)) -At any time the current `DepthCache` object can be retrieved from the `DepthCacheManager` +To use the magic `__aenter__` and `__aexit__` functions to use this class without the `async with` .. code:: python - depth_cache = dcm.get_depth_cache() - if depth_cache is not None: - print("symbol {}".format(depth_cache.symbol)) - print("top 5 bids") - print(depth_cache.get_bids()[:5]) - print("top 5 asks") - print(depth_cache.get_asks()[:5]) - print("last update time {}".format(depth_cache.update_time) - else: - # depth cache had an error and needs to be restarted - -To stop the `DepthCacheManager` from returning messages use the `close` method. -This will close the internal websocket and this instance of the `DepthCacheManager` will not be able to be used again. + dcm = DepthCacheManager(client, 'BNBBTC') -.. code:: python + await dcm.__aenter__() + depth_cache = await dcm.recv() + print("symbol {}".format(depth_cache.symbol)) + print("top 5 bids") + print(depth_cache.get_bids()[:5]) + print("top 5 asks") + print(depth_cache.get_asks()[:5]) + print("last update time {}".format(depth_cache.update_time)) - dcm.close() + # exit the context manager + await dcm.__aexit__(None, None, None) diff --git a/docs/exceptions.rst b/docs/exceptions.rst index beda6c8e9..d4815190d 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -26,8 +26,3 @@ The exception provides access to the except BinanceAPIException as e: print e.status_code print e.message - -BinanceWithdrawException ------------------------- - -Raised if the withdraw fails. diff --git a/docs/faqs.rst b/docs/faqs.rst index 5f1173769..43eb2d3cc 100644 --- a/docs/faqs.rst +++ b/docs/faqs.rst @@ -20,9 +20,3 @@ Check recvWindow is an integer and not a string. *A2*: You may need to regenerate your API Key and Secret *A3*: You may be attempting to access the API from a Chinese IP address, these are now restricted by Binance. - - -*Q: Twisted won't install using pip on Windows* - -*A*:If you see errors building Twisted indication Microsoft Visual C++ is required you may need to install the Visual C++ Build Tools -refer to the `Python Wiki on Widows Compilers `_ for your relevant version. diff --git a/docs/general.rst b/docs/general.rst index a6d023212..f695ba6f3 100644 --- a/docs/general.rst +++ b/docs/general.rst @@ -47,6 +47,24 @@ Get the exchange info for a particular symbol info = client.get_symbol_info('BNBBTC') +`Get All Coins Info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Get information of coins (available for deposit and withdraw) for user + +.. code:: python + + info = client.get_all_tickers() + +`Get Get Daily Account Snapshot `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Get daily account snapshot of specific type. Valid types: SPOT/MARGIN/FUTURES. + +.. code:: python + + info = client.get_account_snapshot(type='SPOT') + `Get Current Products `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/helpers.rst b/docs/helpers.rst index 2820ff65f..cbb5eebd6 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -2,5 +2,5 @@ Helper Functions ================ .. autoclass:: binance.helpers - :members: date_to_milliseconds, interval_to_milliseconds + :members: date_to_milliseconds, interval_to_milliseconds, round_step_size :noindex: diff --git a/docs/index.rst b/docs/index.rst index a4f76c381..a3988722e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,8 @@ Contents general market_data account + sub_accounts + margin websockets depth_cache withdraw diff --git a/docs/margin.rst b/docs/margin.rst new file mode 100644 index 000000000..11cbc1626 --- /dev/null +++ b/docs/margin.rst @@ -0,0 +1,262 @@ +Margin Trading Endpoints +======================== + +.. note :: + + **Cross-margin vs isolated margin trading** + + Binance offers both *cross-margin* trading (where all margin is in one account) and *isolated margin* trading (where each pair is a separate margin account). Make sure you are interacting with the right one. + + Some of the API endpoints apply to the cross-margin or isolated margin accounts only. Other endpoints, such as the trade execution endpoints, are used for the cross-margin account trades by default, but you can use your isolated margin accounts by using the ``isIsolated`` or ``isolatedSymbol`` parameters. See the documentation below. + +Market Data +----------- + +`Get cross-margin asset info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_margin_asset(asset='BNB') + +`Get cross-margin symbol info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_margin_symbol(symbol='BTCUSDT') + +`Get isolated margin symbol info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_isolated_margin_symbol(symbol='BTCUSDT') + +`Get all isolated margin symbols `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_all_isolated_margin_symbols() + +`Get margin price index `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_margin_price_index(symbol='BTCUSDT') + +Orders +------ + +Cross-margin vs isolated margin orders +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, these trade execution endpoints will create an order using the *cross-margin* account. + +To use the *isolated margin* account for the ``symbol`` you have specified, simply add the ``isIsolated='TRUE'`` parameter to the API calls below in this 'Orders' section. + +Order Validation +^^^^^^^^^^^^^^^^ + +Binance has a number of rules around symbol pair orders with validation on minimum price, quantity and total order value. + +Read more about their specifics in the `Filters `_ +section of the official API. + +It can be helpful to format the output using the following snippet + +.. code:: python + + amount = 0.000234234 + precision = 5 + amt_str = "{:0.0{}f}".format(amount, precision) + + +`Fetch all margin_orders `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + orders = client.get_all_margin_orders(symbol='BNBBTC', limit=10) + + +`Place a margin order `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the `create_margin_order` function to have full control over creating an order + +.. code:: python + + from binance.enums import * + order = client.create_margin_order( + symbol='BNBBTC', + side=SIDE_BUY, + type=ORDER_TYPE_LIMIT, + timeInForce=TIME_IN_FORCE_GTC, + quantity=100, + price='0.00001') + + +`Check order status `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + order = client.get_margin_order( + symbol='BNBBTC', + orderId='orderId') + + +`Cancel a margin order `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + result = client.cancel_margin_order( + symbol='BNBBTC', + orderId='orderId') + + +`Get all open margin orders `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + orders = client.get_open_margin_orders(symbol='BNBBTC') + +For isolated margin, add the ``isIsolated='TRUE'`` parameter. + +`Get all margin orders `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + orders = client.get_all_margin_orders(symbol='BNBBTC') + +For isolated margin, add the ``isIsolated='TRUE'`` parameter. + +Account +------- + +`Get cross-margin account info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_margin_account() + +`Create isolated margin account `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + account = client.create_isolated_margin_account(base='BTC', quote='ETH') + +`Get isolated margin account info `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + info = client.get_isolated_margin_account() + +`Transfer spot to cross-margin account `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.transfer_spot_to_margin(asset='BTC', amount='1.1') + +`Transfer cross-margin account to spot `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.transfer_margin_to_spot(asset='BTC', amount='1.1') + +`Transfer spot to isolated margin account `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.transfer_spot_to_isolated_margin(asset='BTC', + symbol='ETHBTC', amount='1.1') + +`Transfer isolated margin account to spot `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.transfer_isolated_margin_to_spot(asset='BTC', + symbol='ETHBTC', amount='1.1') + +`Get max transfer amount `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + details = client.get_max_margin_transfer(asset='BTC') + +This max transfer is for the cross-margin account by default. For isolated margin records, add the ``isolatedSymbol=symbol_name`` parameter. + +Trades +----- + +`Get all margin trades `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + trades = client.get_margin_trades(symbol='BNBBTC') + +For isolated margin trades, add the ``isIsolated='TRUE'`` parameter. + + +Loans +----- + +`Create loan `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.create_margin_loan(asset='BTC', amount='1.1') + +This for the cross-margin account by default. For isolated margin, add the ``isIsolated='TRUE'`` and the ``symbol=symbol_name`` parameters. + +`Repay loan `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + transaction = client.repay_margin_loan(asset='BTC', amount='1.1') + +This for the cross-margin account by default. For isolated margin, add the ``isIsolated='TRUE'`` and the ``symbol=symbol_name`` parameters. + +`Get loan details `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + details = client.get_margin_loan_details(asset='BTC', txId='100001') + +This for the cross-margin account by default. For isolated margin records, add the ``isolatedSymbol=symbol_name`` parameter. + +`Get repay details `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + details = client.get_margin_repay_details(asset='BTC', txId='100001') + +This for the cross-margin account by default. For isolated margin records, add the ``isolatedSymbol=symbol_name`` parameter. + +`Get max loan amount `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + details = client.get_max_margin_loan(asset='BTC') + +The max loan is for the cross-margin account by default. For isolated margin records, add the ``isolatedSymbol=symbol_name`` parameter. diff --git a/docs/market_data.rst b/docs/market_data.rst index 4884f7240..2499459db 100644 --- a/docs/market_data.rst +++ b/docs/market_data.rst @@ -75,7 +75,7 @@ Fetch klines for any date range and interval klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") # fetch weekly klines since it listed - klines = client.get_historical_klines("NEOBTC", KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") `Get Historical Kline/Candlesticks using a generator `_ @@ -85,10 +85,17 @@ Fetch klines using a generator .. code:: python - for kline in client.get_historical_klines_generator("BNBBTC", Client.KLINE_INTERVAL_1MINUTE, "1 day ago UTC") + for kline in client.get_historical_klines_generator("BNBBTC", Client.KLINE_INTERVAL_1MINUTE, "1 day ago UTC"): print(kline) # do something with the kline +`Get average price for a symbol `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + avg_price = client.get_avg_price(symbol='BNBBTC') + `Get 24hr Ticker `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/overview.rst b/docs/overview.rst index a88bbd08b..ad812ab7e 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -11,20 +11,15 @@ Install with ``pip``: pip install python-binance -**Windows** - -If you see errors building Twisted indication Microsoft Visual C++ is required you may need to install the Visual C++ Build Tools -refer to the `Python Wiki on Widows Compilers `_ for your relevant version. - Register on Binance ------------------- -Firstly `register an account with Binance `_. +Firstly `register an account with Binance `_. Generate an API Key ------------------- -To use signed account methods you are required to `create an API Key `_. +To use signed account methods you are required to `create an API Key `_. Initialise the client --------------------- @@ -36,10 +31,65 @@ Pass your API Key and Secret from binance.client import Client client = Client(api_key, api_secret) +or for Asynchronous client + +.. code:: python + + async def main(): + + # initialise the client + client = await AsyncClient.create(api_key, api_secret) + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +Using the Spot, Futures or Vanilla Options Testnet +-------------------------------------------------- + +Binance offers a `Spot `_, +`Futures `_ +and `Vanilla Options `_ Testnet, +to test interacting with the exchange. + +To enable this set the `testnet` parameter passed to the Client to True. + +The testnet parameter will also be used by any websocket streams when the client is passed to the BinanceSocketManager. + +.. code:: python + + client = Client(api_key, api_secret, testnet=True) + +or for Asynchronous client + +.. code:: python + + client = await AsyncClient.create(api_key, api_secret, testnet=True) + +Using a different TLD +--------------------- + +If you are interacting with a regional version of Binance which has a different TLD such as `.us` or `.jp' then you +will need to pass this when creating the client, see examples below. + +This tld will also be used by any websocket streams when the client is passed to the BinanceSocketManager. + +.. code:: python + + client = Client(api_key, api_secret, tld='us') + +or for Asynchronous client + +.. code:: python + + client = await AsyncClient.create(api_key, api_secret, tld='us') + + Making API Calls ---------------- -Every method supports the passing of arbitrary parameters via keyword matching those in the`Binance API documentation `_. +Every method supports the passing of arbitrary parameters via keyword matching those in the `Binance API documentation `_. These keyword arguments will be sent directly to the relevant endpoint. Each API method returns a dictionary of the JSON response as per the `Binance API documentation `_. @@ -51,6 +101,37 @@ Some methods have a `recvWindow` parameter for `timing security, see Binance doc API Endpoints are rate limited by Binance at 20 requests per second, ask them if you require more. +Async API Calls +--------------- + +aiohttp is used to handle asyncio REST requests. + +Each function available in the normal client is available in the AsyncClient class. + +The only difference is to run within an asyncio event loop and await the function like below. + +.. code:: python + + import asyncio + from binance import AsyncClient + + async def main(): + client = await AsyncClient.create() + + # fetch exchange info + res = await client.get_exchange_info() + print(json.dumps(res, indent=2)) + + await client.close_connection() + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +Read `Async basics for Binance `_ +for more information about asynchronous patterns. + API Rate Limit -------------- @@ -58,15 +139,59 @@ Check the `get_exchange_info() `_ for specific information. -.. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance/docs/overview?pixel +On each request Binance returns `X-MBX-USED-WEIGHT-(intervalNum)(intervalLetter)` and `X-MBX-ORDER-COUNT-(intervalNum)` +headers. + +Here are examples to access these + +Asynchronous example + +.. code:: python + + import asyncio + from binance import AsyncClient + api_key = '' + api_secret = '' + + async def main(): + client = await AsyncClient.create(api_key, api_secret) + + res = await client.get_exchange_info() + print(client.response.headers) + + await client.close_connection() + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +Synchronous example + + +.. code:: python + + from binance import Client + + api_key = '' + api_secret = '' + + def main(): + client = Client(api_key, api_secret) + + res = client.get_exchange_info() + print(client.response.headers) + + if __name__ == "__main__": + main() Requests Settings ----------------- @@ -120,3 +245,5 @@ For Windows environments C:\>set HTTP_PROXY=http://10.10.1.10:3128 C:\>set HTTPS_PROXY=http://10.10.1.10:1080 + +.. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance/docs/overview?pixel diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..cdada1d35 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx==8.1.3 +sphinx_rtd_theme==3.0.1 \ No newline at end of file diff --git a/docs/sub_accounts.rst b/docs/sub_accounts.rst new file mode 100644 index 000000000..5b27d10e1 --- /dev/null +++ b/docs/sub_accounts.rst @@ -0,0 +1,24 @@ +Sub Account Endpoints +===================== + + +`Get Sub Account list `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + accounts = client.get_sub_account_list() + +`Get Sub Account Transfer History `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + history = client.get_sub_account_transfer_history(fromEmail='blah@gmail.com', toEmail='foo@gmail.com') + +`Get Sub Account Assets `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + assets = client.get_sub_account_assets(email='blah@gmail.com') diff --git a/docs/websockets.rst b/docs/websockets.rst old mode 100644 new mode 100755 index 8bb364658..19f9676bf --- a/docs/websockets.rst +++ b/docs/websockets.rst @@ -1,54 +1,201 @@ Websockets ========== -Sockets are handled through a Socket Manager `BinanceSocketManager `_. +There are 2 ways to interact with websockets. -Multiple socket connections can be made through the manager. +with `ThreadedWebsocketManager `_ or `BinanceSocketManager `_. + +ThreadedWebsocketManager does not require asyncio programming, while BinanceSocketManager does. + +ThreadedWebsocketManager function begin with `start_`, e.g `start_ticker_socket` while BinanceSocketManager is simply `ticker_socket`. + +Multiple socket connections can be made through either manager. Only one instance of each socket type will be created, i.e. only one BNBBTC Depth socket can be created and there can be both a BNBBTC Depth and a BNBBTC Trade socket open at once. -When creating socket connections a callback function is passed which receives the messages. +Messages are received as dictionary objects relating to the message formats defined in the `Binance WebSocket API documentation `_. + +Websockets are setup to reconnect with a maximum of 5 retries with an exponential backoff strategy. + +ThreadedWebsocketManager Websocket Usage +---------------------------------------- -Messages are received as dictionary objects relating to the message formats defined in the `Binance WebSocket API documentation `_. +Starting sockets on the ThreadedWebsocketManager requires a callback parameter, similar to the old implementations of websockets on python-binance. -Websockets are setup to reconnect with a maximum of 5 retries. +ThreadedWebsocketManager takes similar parameters to the `Client `_ class as it +creates an AsyncClient internally. -Websocket Usage ---------------- +For authenticated streams `api_key` and `api_stream` are required. -Create the manager like so, passing the API client. +As these use threads `start()` is required to be called before starting any sockets. + +To keep the ThreadedWebsocketManager running, use `join()` to join it to the main thread. .. code:: python - from binance.websockets import BinanceSocketManager - bm = BinanceSocketManager(client) - # start any sockets here, i.e a trade socket - conn_key = bm.start_trade_socket('BNBBTC', process_message) - # then start the socket manager - bm.start() + import time + + from binance import ThreadedWebsocketManager + + api_key = '' + api_secret = '' + + def main(): + + symbol = 'BNBBTC' -A callback to process messages would take the format + twm = ThreadedWebsocketManager(api_key=api_key, api_secret=api_secret) + # start is required to initialise its internal loop + twm.start() + + def handle_socket_message(msg): + print(f"message type: {msg['e']}") + print(msg) + + twm.start_kline_socket(callback=handle_socket_message, symbol=symbol) + + # multiple sockets can be started + twm.start_depth_socket(callback=handle_socket_message, symbol=symbol) + + # or a multiplex socket can be started like this + # see Binance docs for stream names + streams = ['bnbbtc@miniTicker', 'bnbbtc@bookTicker'] + twm.start_multiplex_socket(callback=handle_socket_message, streams=streams) + + twm.join() + + + if __name__ == "__main__": + main() + +**Stop Individual Stream** + +When starting a stream, a name for that stream will be returned. This can be used to stop that individual stream. .. code:: python - def process_message(msg): - print("message type: {}".format(msg['e'])) + from binance import ThreadedWebsocketManager + + symbol = 'BNBBTC' + + twm = ThreadedWebsocketManager() + # start is required to initialise its internal loop + twm.start() + + def handle_socket_message(msg): + print(f"message type: {msg['e']}") print(msg) - # do something -Set a custom timeout for the websocket connection + twm.start_kline_socket(callback=handle_socket_message, symbol=symbol) + depth_stream_name = twm.start_depth_socket(callback=handle_socket_message, symbol=symbol) + + # some time later + + twm.stop_socket(depth_stream_name) + +**Stop All Streams** + +.. code:: python + + from binance import ThreadedWebsocketManager + + twm = ThreadedWebsocketManager() + # start is required to initialise its internal loop + twm.start() + + def handle_socket_message(msg): + print(f"message type: {msg['e']}") + print(msg) + + depth_stream_name = twm.start_depth_socket(callback=handle_socket_message, symbol=symbol) + + twm.stop() + +Attempting to start a stream after `stop` is called will not work. + + +BinanceSocketManager Websocket Usage +------------------------------------ + +Create the manager like so, passing an AsyncClient. + +.. code:: python + + import asyncio + from binance import AsyncClient, BinanceSocketManager + + + async def main(): + client = await AsyncClient.create() + bm = BinanceSocketManager(client) + # start any sockets here, i.e a trade socket + ts = bm.trade_socket('BNBBTC') + # then start receiving messages + async with ts as tscm: + while True: + res = await tscm.recv() + print(res) + + await client.close_connection() + + if __name__ == "__main__": + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + +Set a custom timeout for the websocket connections .. code:: python # set a timeout of 60 seconds bm = BinanceSocketManager(client, user_timeout=60) +Manually enter and exit the Asynchronous context manager + +.. code:: python + + ts = bm.trade_socket('BNBBTC') + # enter the context manager + await ts.__aenter__() + # receive a message + msg = await ts.recv() + print(msg) + # exit the context manager + await ts.__aexit__(None, None, None) + + +Using a different TLD +--------------------- + +The ThreadedWebsocketManager can take the tld when created if required. + +.. code:: python + + from binance.streams import ThreadedWebsocketManager + + twm = ThreadedWebsocketManager(tld='us') + +The BinanceSocketManager uses the same tld value as the AsyncClient that is passed in. To use the 'us' tld we +can do this. + +.. code:: python + + from binance import AsyncClient, BinanceSocketManager + + async def x(): + client = await AsyncClient.create(tld='us') + bm = BinanceSocketManager(client) + + # start a socket... + + await client.close_connection() + Websocket Errors ---------------- -If the websocket is disconnected and is unable to reconnect a message is sent to the callback to indicate this. The format is +If the websocket is disconnected and is unable to reconnect, a message is sent to the callback to indicate this. The format is .. code:: python @@ -65,8 +212,8 @@ If the websocket is disconnected and is unable to reconnect a message is sent to # process message normally -`Multiplex Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Multiplex Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Create a socket combining multiple streams. @@ -74,18 +221,15 @@ These streams can include the depth, kline, ticker and trade streams but not the Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker -See the `Binance Websocket Streams API documentation `_ for details on socket names. +See the `Binance Websocket Streams API documentation `_ for details on socket names. .. code:: python - def process_m_message(msg): - print("stream: {} data: {}".format(msg['stream'], msg['data'])) - # pass a list of stream names - conn_key = bm.start_multiplex_socket(['bnbbtc@aggTrade', 'neobtc@ticker'], process_m_message) + ms = bm.multiplex_socket(['bnbbtc@aggTrade', 'neobtc@ticker']) -`Depth Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Depth Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Depth sockets have an optional depth parameter to receive partial book rather than a diff response. By default this the diff response is returned. @@ -94,14 +238,14 @@ Valid depth values are 5, 10 and 20 and `defined as enums `_. .. code:: python # depth diff response - diff_key = bm.start_depth_socket('BNBBTC', process_message) + ds = bm.depth_socket('BNBBTC') # partial book response - partial_key = bm.start_depth_socket('BNBBTC', process_message, depth=BinanceSocketManager.WEBSOCKET_DEPTH_5) + ds = bm.depth_socket('BNBBTC', depth=BinanceSocketManager.WEBSOCKET_DEPTH_5) -`Kline Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Kline Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Kline sockets have an optional interval parameter. By default this is set to 1 minute. Valid interval values are `defined as enums `_. @@ -109,52 +253,52 @@ Valid interval values are `defined as enums `_. .. code:: python from binance.enums import * - conn_key = bm.start_kline_socket('BNBBTC', process_message, interval=KLINE_INTERVAL_30MINUTE) + ks = bm.kline_socket('BNBBTC', interval=KLINE_INTERVAL_30MINUTE) -`Aggregated Trade Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Aggregated Trade Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code:: python - conn_key = bm.start_aggtrade_socket('BNBBTC', process_message) + ats = bm.aggtrade_socket('BNBBTC') -`Trade Socket `_ -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Trade Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code:: python - conn_key = bm.start_trade_socket('BNBBTC', process_message) + ts = bm.trade_socket('BNBBTC') -`Symbol Ticker Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Symbol Ticker Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code:: python - conn_key = bm.start_symbol_ticker_socket('BNBBTC', process_message) + sts = bm.symbol_ticker_socket('BNBBTC') -`Ticker Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Ticker Socket `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code:: python - conn_key = bm.start_ticker_socket(process_message) + ts = bm.ticker_socket(process_message) -`Mini Ticker Socket `_ -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +`Mini Ticker Socket `_ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code:: python # by default updates every second - conn_key = bm.start_miniticker_socket(process_message) + mts = bm.miniticker_socket() # this socket can take an update interval parameter # set as 5000 to receive updates every 5 seconds - conn_key = bm.start_miniticker_socket(process_message, 5000) + mts = bm.miniticker_socket(5000) -`User Socket `_ -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +User Socket ++++++++++++ This watches for 3 different user events @@ -164,44 +308,28 @@ This watches for 3 different user events The Manager handles keeping the socket alive. -.. code:: python - - bm.start_user_socket(process_message) - - -`Close a Socket `_ -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +There are separate sockets for Spot, Cross-margin and separate Isolated margin accounts. -To close an individual socket call the `stop_socket` function. -This takes a conn_key parameter which is returned when starting the socket. +`Spot trading `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python - bm.stop_socket(conn_key) + bm.user_socket() -To stop all sockets and end the manager call `close` after doing this a `start` call would be required to connect any new sockets. +`Cross-margin `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python - bm.close() + bm.margin_socket() -.. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance/docs/websockets?pixel - -Close and exit program -++++++++++++++++++++++ - -Websockets utilise a reactor loop from the Twisted library. Using the `close` method above will close -the websocket connections but it won't stop the reactor loop so your code may not exit when you expect. - -If you do want to exit then use the `stop` method from reactor like below. +`Isolated margin `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python - from twisted.internet import reactor - - # program code here + bm.isolated_margin_socket(symbol) - # when you need to exit - reactor.stop() diff --git a/docs/withdraw.rst b/docs/withdraw.rst index ac23d27ee..9bd7914e0 100644 --- a/docs/withdraw.rst +++ b/docs/withdraw.rst @@ -8,34 +8,30 @@ Make sure you enable Withdrawal permissions for your API Key to use this call. You must have withdrawn to the address through the website and approved the withdrawal via email before you can withdraw using the API. -Raises a `BinanceWithdrawException `_ if the withdraw fails. - .. code:: python - from binance.exceptions import BinanceAPIException, BinanceWithdrawException + from binance.exceptions import BinanceAPIException try: # name parameter will be set to the asset value by the client if not passed result = client.withdraw( - asset='ETH', + coin='ETH', address='', amount=100) except BinanceAPIException as e: print(e) - except BinanceWithdrawException as e: - print(e) else: print("Success") # passing a name parameter result = client.withdraw( - asset='ETH', + coin='ETH', address='', amount=100, name='Withdraw') # if the coin requires a extra tag or name such as XRP or XMR then pass an `addressTag` parameter. result = client.withdraw( - asset='XRP', + coin='XRP', address='', addressTag='', amount=10000) @@ -46,7 +42,7 @@ Raises a `BinanceWithdrawException `_ @@ -55,18 +51,11 @@ Raises a `BinanceWithdrawException `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code:: python - address = client.get_deposit_address(asset='BTC') - -`Get withdraw fee `_ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code:: python - - address = client.get_withdraw_fee(asset='BTC') + address = client.get_deposit_address(coin='BTC') diff --git a/examples/binace_socket_manager.py b/examples/binace_socket_manager.py new file mode 100644 index 000000000..89daf8cd8 --- /dev/null +++ b/examples/binace_socket_manager.py @@ -0,0 +1,35 @@ +import os +import sys +import asyncio + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(root) + +import time + +import asyncio +from binance import AsyncClient, BinanceSocketManager + + +async def main(): + client = await AsyncClient.create() + bm = BinanceSocketManager(client) + # start any sockets here, i.e a trade socket + ts = bm.trade_socket('BTCUSDT') + # then start receiving messages + async with ts as tscm: + start_time = time.time() + while time.time() - start_time < 30: + try: + res = await tscm.recv() + print(res) + except Exception as e: + print(f"An error occurred: {e}") + break + + await client.close_connection() + print("WebSocket connection closed after 10 seconds.") + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) diff --git a/examples/create_order.py b/examples/create_order.py new file mode 100644 index 000000000..8d9dbd906 --- /dev/null +++ b/examples/create_order.py @@ -0,0 +1,40 @@ +import os +import sys + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(root) + +from binance.client import Client + + +api_key = "" # your api_key here +secret = "" # your secret here +client = Client(api_key, secret, testnet=True) + +# create futures order +def create_futures_order(): + order = client.futures_create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + positionSide="LONG" # BOTH for One-way Mode ; LONG or SHORT for Hedge Mode + ) + print(order) + +def create_spot_order(): + order = client.create_order( + symbol="LTCUSDT", + side="BUY", + type="LIMIT", + price=60, + quantity=0.1 + ) + print(order) + + +def main(): + create_futures_order() + create_spot_order() + +main() \ No newline at end of file diff --git a/examples/create_order_async.py b/examples/create_order_async.py new file mode 100644 index 000000000..bcb244fc5 --- /dev/null +++ b/examples/create_order_async.py @@ -0,0 +1,25 @@ +import os +import sys +import asyncio + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(root) + +from binance.client import AsyncClient + +# create futures order +async def main(): + api_key = "" # your api_key here + secret = "" # your secret here + client = AsyncClient(api_key, secret, testnet=True) + order = await client.futures_create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + positionSide="LONG" # BOTH for One-way Mode ; LONG or SHORT for Hedge Mode + ) + print(order) + await client.close_connection() + +asyncio.run(main()) \ No newline at end of file diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 000000000..b25b31253 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,9 @@ +{ + "include": [ + "binance" + ], + "reportMissingImports": false, + "reportMissingModuleSource": false, + "typeCheckingMode": "basic", + "reportWildcardImportFromLibrary": false +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..e69de29bb diff --git a/requirements.txt b/requirements.txt index 6dd6e581b..089810457 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,6 @@ -autobahn==18.7.1 -certifi==2018.4.16 -chardet==3.0.4 -cryptography==2.3 -dateparser==0.7.0 -pyOpenSSL==19.0.0 -requests==2.21.0 -service-identity==17.0.0 -Twisted==18.7.0 +aiohttp +dateparser +pycryptodome +requests +ujson +websockets diff --git a/setup.py b/setup.py index e08caaa65..f5facdeeb 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,52 @@ #!/usr/bin/env python from setuptools import setup +import codecs +import os +import re + + +with codecs.open( + os.path.join( + os.path.abspath(os.path.dirname(__file__)), + 'binance', + '__init__.py' + ), 'r', 'latin1') as fp: + try: + version = re.findall(r'^__version__ = "([^"]+)"\r?$', fp.read(), re.M)[0] + except IndexError: + raise RuntimeError('Unable to determine version.') + +with open("README.rst", "r") as fh: + long_description = fh.read() setup( name='python-binance', - version='0.7.1', + version=version, packages=['binance'], description='Binance REST API python implementation', + long_description=long_description, + long_description_content_type="text/x-rst", url='https://github.com/sammchardy/python-binance', author='Sam McHardy', license='MIT', author_email='', - install_requires=['requests', 'six', 'Twisted', 'pyOpenSSL', 'autobahn', 'service-identity', 'dateparser', 'urllib3', 'chardet', 'certifi', 'cryptography', ], + install_requires=[ + 'requests', 'six', 'dateparser', 'aiohttp', 'ujson', 'websockets', 'pycryptodome' + ], keywords='binance exchange rest api bitcoin ethereum btc eth neo', classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules', ], diff --git a/test-requirements.txt b/test-requirements.txt index ca7247873..9078d8f9b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,10 @@ coverage flake8 pytest +pytest-asyncio pytest-cov -pytest-pep8 -python-coveralls +pytest-flake8 requests-mock tox -setuptools \ No newline at end of file +setuptools +aioresponses diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..8ae19a167 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +import pytest +from binance.client import Client, AsyncClient +import os +import asyncio + +proxies = {} +proxy = os.getenv("PROXY") + +if proxy: + proxies = {"http": proxy, 'https': proxy } # tmp: improve this in the future +else: + print("No proxy set") + + +@pytest.fixture(scope="module") +def client(): + return Client("test_api_key", "test_api_secret", {'proxies': proxies}) + +@pytest.fixture(scope="module") +async def clientAsync(): + # for now this is not working inside the tests + res = await AsyncClient().create(api_key="api_key", api_secret="api_secret", https_proxy=proxy, loop=asyncio.new_event_loop()) + return res \ No newline at end of file diff --git a/tests/test_api_request.py b/tests/test_api_request.py old mode 100644 new mode 100755 index c127c5b5d..7ad6a4ea2 --- a/tests/test_api_request.py +++ b/tests/test_api_request.py @@ -1,21 +1,31 @@ -#!/usr/bin/env python -# coding=utf-8 - from binance.client import Client -from binance.exceptions import BinanceAPIException, BinanceRequestException, BinanceWithdrawException +from binance.exceptions import BinanceAPIException, BinanceRequestException import pytest import requests_mock +import os +proxies = {} +proxy = os.getenv("PROXY") +if proxy: + proxies = {"http": proxy, 'https': proxy } # tmp: improve this in the future +else: + print("No proxy set") -client = Client('api_key', 'api_secret') - +client = Client("api_key", "api_secret", {'proxies': proxies}) def test_invalid_json(): """Test Invalid response Exception""" with pytest.raises(BinanceRequestException): with requests_mock.mock() as m: - m.get('https://www.binance.com/exchange/public/product', text='') + m.get( + "https://www.binance.com/exchange-api/v1/public/asset-service/product/get-products?includeEtf=true", + text="", + ) + m.get( + "https://www.binance.com/bapi/asset/v2/public/asset-service/product/get-products?includeEtf=true", + text="", + ) client.get_products() @@ -25,7 +35,7 @@ def test_api_exception(): with pytest.raises(BinanceAPIException): with requests_mock.mock() as m: json_obj = {"code": 1002, "msg": "Invalid API call"} - m.get('https://api.binance.com/api/v1/time', json=json_obj, status_code=400) + m.get("https://api.binance.com/api/v3/time", json=json_obj, status_code=400) client.get_server_time() @@ -35,16 +45,5 @@ def test_api_exception_invalid_json(): with pytest.raises(BinanceAPIException): with requests_mock.mock() as m: not_json_str = "Error" - m.get('https://api.binance.com/api/v1/time', text=not_json_str, status_code=400) + m.get("https://api.binance.com/api/v3/time", text=not_json_str, status_code=400) client.get_server_time() - - -def test_withdraw_api_exception(): - """Test Withdraw API response Exception""" - - with pytest.raises(BinanceWithdrawException): - - with requests_mock.mock() as m: - json_obj = {"success": False, "msg": "Insufficient funds"} - m.register_uri('POST', requests_mock.ANY, json=json_obj, status_code=200) - client.withdraw(asset='BTC', address='BTCADDRESS', amount=100) diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 000000000..c3f725fff --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,6 @@ + + +def test_client_initialization(client): + assert client.API_KEY == 'test_api_key' + assert client.API_SECRET == 'test_api_secret' + assert client.testnet == False diff --git a/tests/test_cryptography.py b/tests/test_cryptography.py new file mode 100644 index 000000000..2edd5b423 --- /dev/null +++ b/tests/test_cryptography.py @@ -0,0 +1,49 @@ +from binance.client import Client + +test_cases = [ + { + "description": "Unencrypted PKCS8 ed22519 private key", + "private_key": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIPQmzwVKJETqVd7L9E/DFbkvrOigy1tLL+9QF0mSn6dV\n-----END PRIVATE KEY-----\n", + "password": None, + "expected_signature": "a4Pm3p02D2HXtNfo3DBaVCe9Ov7kledewgYtGjekotFmZ5wXa3mC5AtLB7CpAphyNjeyovIuDP+9fyjYmsojCw==" + }, + { + "description": "Unencrypted PKCS8 ed22519 private key in bytes", + "private_key": b'-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIPQmzwVKJETqVd7L9E/DFbkvrOigy1tLL+9QF0mSn6dV\n-----END PRIVATE KEY-----\n', + "password": None, + "expected_signature": "a4Pm3p02D2HXtNfo3DBaVCe9Ov7kledewgYtGjekotFmZ5wXa3mC5AtLB7CpAphyNjeyovIuDP+9fyjYmsojCw==" + }, + { + "description": "Encrypted PKCS8 RSA private key", + "private_key": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQWW+iEMYYCPUntrPq\nZ2RCMAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIw3ViSuTp8JeN43\n5VGlHt0EggTQBvEzd2w2F561CzU+MDouZDOPj4RTIStC471z0/bxTgYqH3gYchoe\nOfi2lsLuD8B+ivIRuXB8GT66BIseIOMV8t/tiMe97rFI/cV4h6DrBO1xlmSrBG97\nvFF9qPA5yPRlrHtWKkGxhXteNVsT3w/7Y7KsulO/gA2KpsOElMElOhUP462Yd0Wl\nOxAIV3+knl2niozws2Kq3EdzTF3N6hlavUPryiU/w4RRsPN5qgjchVVLq/sYRYhx\nN8uWJbkjhCcHsULkD5KkdgddR0VOhpQPXIdY+gPkSBJq1ltRWy/TYdXiU2fEBNZW\nhFUVrxnS76+u2R3vukY2IAX8zTC6h2AbCBG+r4XXzgk/l/4peySKHsPQRzQ0in39\na9o5sctOmUNeD4uJ6cClXDdqyEwXhnPmRKZjJ8qeH4D9wl7HOG7iQsYiyfJe/igi\nFEXVRZOtLBdbwX45rU6wiWWjxzY+mDnw4BXE31ZBPwgtoh+CLTyK8NI8LnCV/CgO\nzOY4sm/KDWmbfTTZjLSdYRFj7wEpOdUWjZ13viDFZqnmy/o1auvLmBcqbRrCyW+B\nOMI7aHE0mZ/52vEFQYU1tH0BxMmRfWXUCJj0TjwxDY6BQmmW4YlhsrgGNekLFDo1\n6phFd0pA4UPqGXfNLzHp1dtLhUEb4YzcpDn+HMzMf1gfez7qeqU28nNFg/AwwqHZ\nTWdGclCFjiah7SfvOslob4vdLGwkUhgCBKQUQoU1DltX2GOgIv9SNY3q6X0NwdZG\nL5gqk225WVUwIRzmi5nfUEXlbaTvyHg3BuGedUKJ91IhRCW1ZjvU8GQcfVsu8bse\nTCKMdr7wi/zEZXSldCza6vL4m3tmBLtWkHVOW8bcDWvoVwRswbFHfleHzckl7EeC\n9C4TRa66gA5UOv14SrpC8noQUNpSegg+1KI4BSNvwaheiSUqjQbisb0qYCxML0ZP\nmQodwVsXG6LYo+Y6y6CpHbT7UYkfa59q/CGOZByL1bEzzgd98ZHwjihOjHVaV6sY\nBW018AvGxr7kjEU4LNqIteydTp0o31ZJN/qK78w5EQFfJxfImrx/E4nYKtg4higj\nKOQCgJALKIveidqQEFsbGWsulYrMXwnu0nPThofR1D8eCJZpdTxvOh2nIrNrAeY8\nZMAwG1uQos5A0yEZ1auHxz+rb4errnk92OnVlWnElf1TwwlkFFNLdNDl8VpiMP40\n6en9VtlOfgH8AwB03WsoeuEQsxYTIcRKWZZPRsLx3hd0BsOw0FcYDSX2XIGPkVVW\niYf9hzFSQsWV3d6utloIm4nG8XONfNaRimGECbUSZyHZimrO1m4Gga5pE3LKuDri\nJKR2lR7b6XPR7+FS+lG1zq5KY7onAVQY1oABfTjpJRju6pQGWt70hairo6EaVC3u\nrBy8UkLwBbfDuigSvsVk+sF2+Ic0IzX6IniU0F5kMe+MKqGB4aicXP6FFGBpPFTe\nv6yHD+DYAu1rnlXrqmFL50CfutTF78uPPJ9D2Sm0DcGPFj+6IrCigj48uxoHR9Qb\nFeNzfsmVwoFAWWq/MpkPbX6Aql8ddCbpMxDUUkybwVV9rJmEMTLil44FrxKAKFhP\n0Av7JeFvdz15pfnf/IQ3IOvVhHGFChFS13sbYSvFHMQF3P0BiyvjhBI=\n-----END ENCRYPTED PRIVATE KEY-----\n", + "password": "testpwd", + "expected_signature": "S4l9IONXGHIdt4NjwmpCIhawDTitjUQls73d+mi0HJTSbTGyn95NabX5hC9+n6HsTqLcWPvxKgTvLFMnTaf6Jxl+xwQMbu9/6mw88KF7i1pEQizerKcr91rPUPVBQ4OY10Q018QEamIAymRgo/eoRYSm7CqCdeibGyO0XfXZBaJnVGFJ9hgrPIwSKHgeUnfK8qMenULvL0qKMEJ6ziYPiqh7k9xX3xIV7lGIpokk+ekqlFd01f/Lov45osJCFuccJO4xuUUZewZnVGF7Uw6Rim3UsKhXKZUN9WZWa5RT+dpBIJ5DTBIXBSvowwj3GZC3j+XvWw8Sn0Ls9836l89BXw==" + }, + { + "description": "Encrypted PKCS8 RSA private key in bytes", + "private_key": b'-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQWW+iEMYYCPUntrPq\nZ2RCMAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIw3ViSuTp8JeN43\n5VGlHt0EggTQBvEzd2w2F561CzU+MDouZDOPj4RTIStC471z0/bxTgYqH3gYchoe\nOfi2lsLuD8B+ivIRuXB8GT66BIseIOMV8t/tiMe97rFI/cV4h6DrBO1xlmSrBG97\nvFF9qPA5yPRlrHtWKkGxhXteNVsT3w/7Y7KsulO/gA2KpsOElMElOhUP462Yd0Wl\nOxAIV3+knl2niozws2Kq3EdzTF3N6hlavUPryiU/w4RRsPN5qgjchVVLq/sYRYhx\nN8uWJbkjhCcHsULkD5KkdgddR0VOhpQPXIdY+gPkSBJq1ltRWy/TYdXiU2fEBNZW\nhFUVrxnS76+u2R3vukY2IAX8zTC6h2AbCBG+r4XXzgk/l/4peySKHsPQRzQ0in39\na9o5sctOmUNeD4uJ6cClXDdqyEwXhnPmRKZjJ8qeH4D9wl7HOG7iQsYiyfJe/igi\nFEXVRZOtLBdbwX45rU6wiWWjxzY+mDnw4BXE31ZBPwgtoh+CLTyK8NI8LnCV/CgO\nzOY4sm/KDWmbfTTZjLSdYRFj7wEpOdUWjZ13viDFZqnmy/o1auvLmBcqbRrCyW+B\nOMI7aHE0mZ/52vEFQYU1tH0BxMmRfWXUCJj0TjwxDY6BQmmW4YlhsrgGNekLFDo1\n6phFd0pA4UPqGXfNLzHp1dtLhUEb4YzcpDn+HMzMf1gfez7qeqU28nNFg/AwwqHZ\nTWdGclCFjiah7SfvOslob4vdLGwkUhgCBKQUQoU1DltX2GOgIv9SNY3q6X0NwdZG\nL5gqk225WVUwIRzmi5nfUEXlbaTvyHg3BuGedUKJ91IhRCW1ZjvU8GQcfVsu8bse\nTCKMdr7wi/zEZXSldCza6vL4m3tmBLtWkHVOW8bcDWvoVwRswbFHfleHzckl7EeC\n9C4TRa66gA5UOv14SrpC8noQUNpSegg+1KI4BSNvwaheiSUqjQbisb0qYCxML0ZP\nmQodwVsXG6LYo+Y6y6CpHbT7UYkfa59q/CGOZByL1bEzzgd98ZHwjihOjHVaV6sY\nBW018AvGxr7kjEU4LNqIteydTp0o31ZJN/qK78w5EQFfJxfImrx/E4nYKtg4higj\nKOQCgJALKIveidqQEFsbGWsulYrMXwnu0nPThofR1D8eCJZpdTxvOh2nIrNrAeY8\nZMAwG1uQos5A0yEZ1auHxz+rb4errnk92OnVlWnElf1TwwlkFFNLdNDl8VpiMP40\n6en9VtlOfgH8AwB03WsoeuEQsxYTIcRKWZZPRsLx3hd0BsOw0FcYDSX2XIGPkVVW\niYf9hzFSQsWV3d6utloIm4nG8XONfNaRimGECbUSZyHZimrO1m4Gga5pE3LKuDri\nJKR2lR7b6XPR7+FS+lG1zq5KY7onAVQY1oABfTjpJRju6pQGWt70hairo6EaVC3u\nrBy8UkLwBbfDuigSvsVk+sF2+Ic0IzX6IniU0F5kMe+MKqGB4aicXP6FFGBpPFTe\nv6yHD+DYAu1rnlXrqmFL50CfutTF78uPPJ9D2Sm0DcGPFj+6IrCigj48uxoHR9Qb\nFeNzfsmVwoFAWWq/MpkPbX6Aql8ddCbpMxDUUkybwVV9rJmEMTLil44FrxKAKFhP\n0Av7JeFvdz15pfnf/IQ3IOvVhHGFChFS13sbYSvFHMQF3P0BiyvjhBI=\n-----END ENCRYPTED PRIVATE KEY-----\n', + "password": "testpwd", + "expected_signature": "S4l9IONXGHIdt4NjwmpCIhawDTitjUQls73d+mi0HJTSbTGyn95NabX5hC9+n6HsTqLcWPvxKgTvLFMnTaf6Jxl+xwQMbu9/6mw88KF7i1pEQizerKcr91rPUPVBQ4OY10Q018QEamIAymRgo/eoRYSm7CqCdeibGyO0XfXZBaJnVGFJ9hgrPIwSKHgeUnfK8qMenULvL0qKMEJ6ziYPiqh7k9xX3xIV7lGIpokk+ekqlFd01f/Lov45osJCFuccJO4xuUUZewZnVGF7Uw6Rim3UsKhXKZUN9WZWa5RT+dpBIJ5DTBIXBSvowwj3GZC3j+XvWw8Sn0Ls9836l89BXw==" + } +] + +def test_encryption(): + data = { + "symbol": "BTCUSDT", + "side": "BUY", + "type": "LIMIT", + "quantity": 1, + "timestamp": 1631234567890, + "price": 50000, + } + + for case in test_cases: + client = Client( + api_key="api_key", + api_secret="api_secret", + private_key=case["private_key"], + private_key_pass=case["password"], + ping=False + ) + signature = client._generate_signature(data) + assert signature == case["expected_signature"], f"Test failed: {case['description']}" diff --git a/tests/test_depth_cache.py b/tests/test_depth_cache.py index f3daaa5da..df4cb2b3b 100644 --- a/tests/test_depth_cache.py +++ b/tests/test_depth_cache.py @@ -1,4 +1,5 @@ from binance.depthcache import DepthCache +from decimal import Decimal import pytest TEST_SYMBOL = "BNBBTC" @@ -6,7 +7,7 @@ @pytest.fixture def fresh_cache(): - return DepthCache(TEST_SYMBOL) + return DepthCache(TEST_SYMBOL, Decimal) def test_add_bids(fresh_cache): @@ -23,6 +24,9 @@ def test_add_bids(fresh_cache): assert bids == sorted(bids, reverse=True) + assert isinstance(bids[0][0], Decimal) + assert isinstance(bids[0][1], Decimal) + def test_add_asks(fresh_cache): """Verify basic functionality for adding an ask to the cache""" @@ -40,3 +44,6 @@ def test_add_asks(fresh_cache): # Lowest ask price should be first (ascending order) assert asks == sorted(asks) + + assert isinstance(asks[0][0], Decimal) + assert isinstance(asks[0][1], Decimal) diff --git a/tests/test_futures.py b/tests/test_futures.py new file mode 100644 index 000000000..fc512bc1a --- /dev/null +++ b/tests/test_futures.py @@ -0,0 +1,40 @@ + +import requests_mock +import pytest +import json +from binance.client import Client, AsyncClient +import re + +client = Client(api_key="api_key", api_secret="api_secret", ping=False) + +def test_futures_position_information(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v3\/positionRisk\?.+") + response = [{'symbol': 'LTCUSDT', 'positionSide': 'LONG', 'positionAmt': '0.700', 'entryPrice': '75.6', 'breakEvenPrice': '75.63024', 'markPrice': '73.18000000', 'unRealizedProfit': '-1.69400000', 'liquidationPrice': '0', 'isolatedMargin': '0', 'notional': '51.22600000', 'marginAsset': 'USDT', 'isolatedWallet': '0', 'initialMargin': '10.24520000', 'maintMargin': '0.33296900', 'positionInitialMargin': '10.24520000', 'openOrderInitialMargin': '0', 'adl': 0, 'bidNotional': '0', 'askNotional': '0', 'updateTime': 1729436057076}] + m.register_uri("GET", url_matcher, json=json.dumps(response), status_code=200) + pos = client.futures_position_information(symbol="LTCUSDT") + assert m.last_request.qs['symbol'][0] == 'LTCUSDT'.lower() + assert m.last_request.path == '/fapi/v3/positionrisk' + +def test_futures_position_information_version_override(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v2\/positionRisk\?.+") + response = [{'symbol': 'LTCUSDT', 'positionSide': 'LONG', 'positionAmt': '0.700', 'entryPrice': '75.6', 'breakEvenPrice': '75.63024', 'markPrice': '73.18000000', 'unRealizedProfit': '-1.69400000', 'liquidationPrice': '0', 'isolatedMargin': '0', 'notional': '51.22600000', 'marginAsset': 'USDT', 'isolatedWallet': '0', 'initialMargin': '10.24520000', 'maintMargin': '0.33296900', 'positionInitialMargin': '10.24520000', 'openOrderInitialMargin': '0', 'adl': 0, 'bidNotional': '0', 'askNotional': '0', 'updateTime': 1729436057076}] + m.register_uri("GET", url_matcher, json=json.dumps(response), status_code=200) + pos = client.futures_position_information(symbol="LTCUSDT", version=2) + assert m.last_request.qs['symbol'][0] == 'LTCUSDT'.lower() + assert m.last_request.path == '/fapi/v2/positionrisk' + +def test_futures_account_balance(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v3\/balance\?.+") + m.register_uri("GET", url_matcher, json={}, status_code=200) + client.futures_account_balance() + assert m.last_request.path == '/fapi/v3/balance' + +def test_futures_account_config(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v1\/accountConfig\?.+") + m.register_uri("GET", url_matcher, json={}, status_code=200) + client.futures_account_config() + assert m.last_request.path == '/fapi/v1/accountconfig' \ No newline at end of file diff --git a/tests/test_get_order_book.py b/tests/test_get_order_book.py new file mode 100644 index 000000000..dd2b823a8 --- /dev/null +++ b/tests/test_get_order_book.py @@ -0,0 +1,56 @@ +import pytest +from binance.exceptions import BinanceAPIException +from binance import AsyncClient +import os + +proxies = {} +proxy = os.getenv("PROXY") + + +def assert_ob(order_book): + assert isinstance(order_book, dict) + assert 'lastUpdateId' in order_book + assert 'bids' in order_book + assert 'asks' in order_book + + assert isinstance(order_book['bids'], list) + assert isinstance(order_book['asks'], list) + + if order_book['bids']: + bid = order_book['bids'][0] + assert len(bid) == 2 + assert all(isinstance(item, str) for item in bid[:2]) + + if order_book['asks']: + ask = order_book['asks'][0] + assert len(ask) == 2 + assert all(isinstance(item, str) for item in ask[:2]) + +def test_get_order_book(client): + try: + order_book = client.get_order_book(symbol='BTCUSDT') + assert_ob(order_book) + + except BinanceAPIException as e: + pytest.fail(f"API request failed: {str(e)}") + +def test_get_order_book_with_limit(client): + try: + order_book = client.get_order_book(symbol='BTCUSDT', limit=5) + + assert_ob(order_book) + assert len(order_book['bids']) <= 5 + assert len(order_book['asks']) <= 5 + + except BinanceAPIException as e: + pytest.fail(f"API request failed: {str(e)}") + + +async def test_get_order_book_async(): + try: + client = AsyncClient(api_key="api_key", api_secret="api_secret", https_proxy=proxy) + order_book = await client.get_order_book(symbol='BTCUSDT') + + assert_ob(order_book) + except BinanceAPIException as e: + pytest.fail(f"API request failed: {str(e)}") diff --git a/tests/test_historical_klines.py b/tests/test_historical_klines.py index dd3eb8228..9f7926106 100644 --- a/tests/test_historical_klines.py +++ b/tests/test_historical_klines.py @@ -4,18 +4,46 @@ from binance.client import Client import pytest import requests_mock +import os - -client = Client('api_key', 'api_secret') +client = Client("api_key", "api_secret", ping=False) def test_exact_amount(): """Test Exact amount returned""" - first_available_res = [[1500004800000, "0.00005000", "0.00005300", "0.00001000", "0.00004790", "663152.00000000", 1500004859999, "30.55108144", 43, "559224.00000000", "25.65468144", "83431971.04346950"]] + first_available_res = [ + [ + 1500004800000, + "0.00005000", + "0.00005300", + "0.00001000", + "0.00004790", + "663152.00000000", + 1500004859999, + "30.55108144", + 43, + "559224.00000000", + "25.65468144", + "83431971.04346950", + ] + ] first_res = [] - row = [1519892340000, "0.00099400", "0.00099810", "0.00099400", "0.00099810", "4806.04000000", 1519892399999, "4.78553253", 154, "1785.14000000", "1.77837524", "0"] + row = [ + 1519892340000, + "0.00099400", + "0.00099810", + "0.00099400", + "0.00099810", + "4806.04000000", + 1519892399999, + "4.78553253", + 154, + "1785.14000000", + "1.77837524", + "0", + ] for i in range(0, 500): first_res.append(row) @@ -23,13 +51,20 @@ def test_exact_amount(): second_res = [] with requests_mock.mock() as m: - m.get('https://api.binance.com/api/v1/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC', json=first_available_res) - m.get('https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519862400000&symbol=BNBBTC', json=first_res) - m.get('https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519892400000&symbol=BNBBTC', json=second_res) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + json=first_available_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519862400000&symbol=BNBBTC", + json=first_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519892400000&symbol=BNBBTC", + json=second_res, + ) klines = client.get_historical_klines( - symbol="BNBBTC", - interval=Client.KLINE_INTERVAL_1MINUTE, - start_str="1st March 2018" + symbol="BNBBTC", interval=Client.KLINE_INTERVAL_1MINUTE, start_str="1st March 2018" ) assert len(klines) == 500 @@ -74,11 +109,11 @@ def test_start_and_end_str(): with requests_mock.mock() as m: m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", json=first_available_res, ) m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", json=first_res, ) klines = client.get_historical_klines( @@ -130,11 +165,11 @@ def test_start_and_end_timestamp(): with requests_mock.mock() as m: m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", json=first_available_res, ) m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", json=first_res, ) klines = client.get_historical_klines( @@ -186,11 +221,11 @@ def test_historical_kline_generator(): with requests_mock.mock() as m: m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", json=first_available_res, ) m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", json=first_res, ) klines = client.get_historical_klines_generator( @@ -229,11 +264,11 @@ def test_historical_kline_generator_empty_response(): with requests_mock.mock() as m: m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", json=first_available_res, ) m.get( - "https://api.binance.com/api/v1/klines?interval=1m&limit=500&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", + "https://api.binance.com/api/v3/klines?interval=1m&limit=1000&startTime=1519862400000&endTime=1519880400000&symbol=BNBBTC", json=first_res, ) klines = client.get_historical_klines_generator( @@ -245,3 +280,62 @@ def test_historical_kline_generator_empty_response(): with pytest.raises(StopIteration): next(klines) + +def test_start_and_limit(): + """Test start_str and limit work correctly with integer timestamp""" + + first_available_res = [ + [ + 1500004800000, + "0.00005000", + "0.00005300", + "0.00001000", + "0.00004790", + "663152.00000000", + 1500004859999, + "30.55108144", + 43, + "559224.00000000", + "25.65468144", + "83431971.04346950", + ] + ] + first_res = [] + row = [ + 1519892340000, + "0.00099400", + "0.00099810", + "0.00099400", + "0.00099810", + "4806.04000000", + 1519892399999, + "4.78553253", + 154, + "1785.14000000", + "1.77837524", + "0", + ] + + for i in range(0, 5): + first_res.append(row) + + with requests_mock.mock() as m: + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=1&startTime=0&symbol=BNBBTC", + json=first_available_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=5&startTime=1519892400000&symbol=BNBBTC", + json=first_res, + ) + m.get( + "https://api.binance.com/api/v3/klines?interval=1m&limit=5&startTime=1519862400000&symbol=BNBBTC", + json=first_res, + ) + klines = client.get_historical_klines( + symbol="BNBBTC", + interval=Client.KLINE_INTERVAL_1MINUTE, + start_str=1519862400000, + limit=5, + ) + assert len(klines) == 5 diff --git a/tests/test_ids.py b/tests/test_ids.py new file mode 100644 index 000000000..79273f387 --- /dev/null +++ b/tests/test_ids.py @@ -0,0 +1,184 @@ +import requests_mock +import pytest +from aioresponses import aioresponses + +from binance.client import Client, AsyncClient + +client = Client(api_key="api_key", api_secret="api_secret", ping=False) + +def test_spot_id(): + with requests_mock.mock() as m: + m.post("https://api.binance.com/api/v3/order", json={}, status_code=200) + client.create_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + assert url_dict['symbol'] == 'LTCUSDT' + assert url_dict['side'] == 'BUY' + assert url_dict['type'] == 'MARKET' + assert url_dict['quantity'] == '0.1' + assert url_dict['newClientOrderId'].startswith('x-HNA2TXFJ') + +def test_spot_limit_id(): + with requests_mock.mock() as m: + m.post("https://api.binance.com/api/v3/order", json={}, status_code=200) + client.order_limit_buy(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + assert url_dict['newClientOrderId'].startswith('x-HNA2TXFJ') + +def test_spot_market_id(): + with requests_mock.mock() as m: + m.post("https://api.binance.com/api/v3/order", json={}, status_code=200) + client.order_market_buy(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + assert url_dict['newClientOrderId'].startswith('x-HNA2TXFJ') + +def test_swap_id(): + with requests_mock.mock() as m: + m.post("https://fapi.binance.com/fapi/v1/order", json={}, status_code=200) + client.futures_create_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.query.split('&')) + # why lowercase? check this later + assert url_dict['symbol'] == 'ltcusdt' + assert url_dict['side'] == 'buy' + assert url_dict['type'] == 'market' + assert url_dict['quantity'] == '0.1' + assert url_dict['newClientOrderId'.lower()].startswith('x-Cb7ytekJ'.lower()) + +def test_swap_batch_id(): + with requests_mock.mock() as m: + m.post("https://fapi.binance.com/fapi/v1/batchOrders", json={}, status_code=200) + order = {"symbol" : "LTCUSDT", "side":"BUY", "type":"MARKET", "quantity":0.1} + orders = [order, order] + client.futures_place_batch_order(batchOrders=orders) + text = m.last_request.text + assert 'x-Cb7ytekJ' in text + +def test_coin_id(): + with requests_mock.mock() as m: + m.post("https://dapi.binance.com/dapi/v1/order", json={}, status_code=200) + client.futures_coin_create_order(symbol="LTCUSD_PERP", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + # why lowercase? check this later + assert url_dict['symbol'] == 'LTCUSD_PERP' + assert url_dict['side'] == 'BUY' + assert url_dict['type'] == 'MARKET' + assert url_dict['quantity'] == '0.1' + assert url_dict['newClientOrderId'].startswith('x-Cb7ytekJ') + + +def test_coin_batch_id(): + with requests_mock.mock() as m: + m.post("https://dapi.binance.com/dapi/v1/batchOrders", json={}, status_code=200) + order = {"symbol" : "BTCUSD_PERP", "side":"BUY", "type":"MARKET", "quantity":0.1} + orders = [order, order] + client.futures_coin_place_batch_order(batchOrders=orders) + text = m.last_request.text + assert 'x-Cb7ytekJ' in text + + +def test_papi_um_id(): + with requests_mock.mock() as m: + m.post("https://papi.binance.com/papi/v1/um/order", json={}, status_code=200) + client.papi_create_um_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + # why lowercase? check this later + assert url_dict['symbol'] == 'LTCUSDT' + assert url_dict['side'] == 'BUY' + assert url_dict['type'] == 'MARKET' + assert url_dict['quantity'] == '0.1' + assert url_dict['newClientOrderId'].startswith('x-Cb7ytekJ') + + +def test_papi_cm_id(): + with requests_mock.mock() as m: + m.post("https://papi.binance.com/papi/v1/cm/order", json={}, status_code=200) + client.papi_create_cm_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + url_dict = dict(pair.split('=') for pair in m.last_request.text.split('&')) + # why lowercase? check this later + assert url_dict['symbol'] == 'LTCUSDT' + assert url_dict['side'] == 'BUY' + assert url_dict['type'] == 'MARKET' + assert url_dict['quantity'] == '0.1' + assert url_dict['newClientOrderId'].startswith('x-Cb7ytekJ') + + +@pytest.mark.asyncio() +async def test_spot_id_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") # reuse client later + with aioresponses() as m: + def handler(url, **kwargs): + client_order_id = kwargs['data'][0][1] + assert client_order_id.startswith('x-HNA2TXFJ') + m.post("https://api.binance.com/api/v3/order", payload={'id': 1}, status=200, callback=handler) + await clientAsync.create_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + await clientAsync.close_connection() + + +@pytest.mark.asyncio() +async def test_swap_id_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + with aioresponses() as m: + def handler(url, **kwargs): + client_order_id = kwargs['data'][0][1] + assert client_order_id.startswith('x-Cb7ytekJ') + m.post("https://fapi.binance.com/fapi/v1/order", payload={'id': 1}, status=200, callback=handler) + await clientAsync.futures_create_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + await clientAsync.close_connection() + +@pytest.mark.asyncio() +async def test_papi_um_id_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + with aioresponses() as m: + def handler(url, **kwargs): + client_order_id = kwargs['data'][0][1] + assert client_order_id.startswith('x-Cb7ytekJ') + m.post("https://papi.binance.com/papi/v1/um/order", payload={'id': 1}, status=200, callback=handler) + await clientAsync.papi_create_um_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + await clientAsync.close_connection() + + +@pytest.mark.asyncio() +async def test_papi_cm_id_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + with aioresponses() as m: + def handler(url, **kwargs): + client_order_id = kwargs['data'][0][1] + assert client_order_id.startswith('x-Cb7ytekJ') + m.post("https://papi.binance.com/papi/v1/cm/order", payload={'id': 1}, status=200, callback=handler) + await clientAsync.papi_create_cm_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + await clientAsync.close_connection() + +@pytest.mark.asyncio() +async def test_coin_id_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + with aioresponses() as m: + def handler(url, **kwargs): + client_order_id = kwargs['data'][0][1] + assert client_order_id.startswith('x-Cb7ytekJ') + m.post("https://dapi.binance.com/dapi/v1/order", payload={'id': 1}, status=200, callback=handler) + await clientAsync.futures_coin_create_order(symbol="LTCUSD_PERP", side="BUY", type="MARKET", quantity=0.1) + await clientAsync.close_connection() + +@pytest.mark.asyncio() +async def test_swap_batch_id_async(): + with aioresponses() as m: + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + def handler(url, **kwargs): + assert 'x-Cb7ytekJ' in kwargs['data'][0][1] + m.post("https://fapi.binance.com/fapi/v1/batchOrders", payload={'id': 1}, status=200, callback=handler) + order = {"symbol" : "LTCUSDT", "side":"BUY", "type":"MARKET", "quantity":0.1} + orders = [order, order] + await clientAsync.futures_place_batch_order(batchOrders=orders) + await clientAsync.close_connection() + + +@pytest.mark.asyncio() +async def test_coin_batch_id_async(): + with aioresponses() as m: + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") + def handler(url, **kwargs): + assert 'x-Cb7ytekJ' in kwargs['data'][0][1] + m.post("https://dapi.binance.com/dapi/v1/batchOrders", payload={'id': 1}, status=200, callback=handler) + order = {"symbol" : "LTCUSD_PERP", "side":"BUY", "type":"MARKET", "quantity":0.1} + orders = [order, order] + await clientAsync.futures_coin_place_batch_order(batchOrders=orders) + await clientAsync.close_connection() diff --git a/tests/test_ping.py b/tests/test_ping.py new file mode 100644 index 000000000..6bdfbd1c3 --- /dev/null +++ b/tests/test_ping.py @@ -0,0 +1,48 @@ + +from binance.client import Client, AsyncClient +import os +import pytest + + +proxies = {} +proxy = os.getenv("PROXY") + +def test_papi_ping_sync(client): + ping_response = client.papi_ping() + assert ping_response != None + +def test_ping_sync(client): + ping_response = client.ping() + assert ping_response != None + +def test_futures_ping(client): + ping_response = client.futures_ping() + assert ping_response != None + +def test_coin_ping(client): + ping_response = client.futures_coin_ping() + assert ping_response != None + +@pytest.mark.asyncio() +async def test_papi_ping_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret", https_proxy=proxy) + ping_response = await clientAsync.papi_ping() + assert ping_response != None + +@pytest.mark.asyncio() +async def test_ping_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret", https_proxy=proxy) + ping_response = await clientAsync.ping() + assert ping_response != None + +@pytest.mark.asyncio() +async def test_futures_ping_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret", https_proxy=proxy) + ping_response = await clientAsync.futures_ping() + assert ping_response != None + +@pytest.mark.asyncio() +async def test_coin_ping_async(): + clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret", https_proxy=proxy) + ping_response = await clientAsync.futures_coin_ping() + assert ping_response != None \ No newline at end of file diff --git a/tests/test_streams.py b/tests/test_streams.py new file mode 100644 index 000000000..b371ff3a6 --- /dev/null +++ b/tests/test_streams.py @@ -0,0 +1,15 @@ +from binance.streams import BinanceSocketManager +from binance.client import AsyncClient +import pytest + + +@pytest.mark.asyncio +async def test_socket_stopped_on_aexit(): + client = AsyncClient() + bm = BinanceSocketManager(client) + ts1 = bm.trade_socket('BNBBTC') + async with ts1: + pass + ts2 = bm.trade_socket('BNBBTC') + assert ts2 is not ts1, "socket should be removed from _conn on exit" + await client.close_connection() diff --git a/tox.ini b/tox.ini index dfc006901..52038b8d3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,24 +1,18 @@ [tox] -envlist = py27, py34, py35, py36 +envlist = py36, py37, py38, py39, py310, py311, py312 [testenv] deps = -rtest-requirements.txt -rrequirements.txt -commands = py.test -v tests/ --doctest-modules --cov binance --cov-report term-missing +commands = pytest -v tests/ --doctest-modules --cov binance --cov-report term-missing passenv = - TRAVIS - TRAVIS_BRANCH - TRAVIS_JOB_ID + PROXY [testenv:flake8] commands = flake8 binance/ setup.py examples/ deps = flake8 -[travis] -python = - 3.6: py36, flake8 - [flake8] exclude = .git,