Full Code of veighna-global/vnpy_binance for AI

main 3408672b12bf cached
16 files
236.6 KB
51.3k tokens
315 symbols
1 requests
Download .txt
Showing preview only (246K chars total). Download the full file or copy to clipboard to get everything.
Repository: veighna-global/vnpy_binance
Branch: main
Commit: 3408672b12bf
Files: 16
Total size: 236.6 KB

Directory structure:
gitextract_nwvrw0w8/

├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SUPPORT.md
│   └── workflows/
│       └── pythonapp.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pyproject.toml
├── script/
│   └── run.py
└── vnpy_binance/
    ├── __init__.py
    ├── inverse_gateway.py
    ├── linear_gateway.py
    ├── portfolio_gateway.py
    └── spot_gateway.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# 行为准则

这是一份VeighNa项目社区的行为准则,也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望:

* 为交易员而生:作为一款从金融机构量化业务中诞生的交易系统开发框架,设计上都优先满足机构专业交易员的使用习惯,而不是其他用户(散户、爱好者、技术人员等)

* 对新用户友好,保持耐心:大部分人在接触新东西的时候都是磕磕碰碰、有很多的问题,请记住此时别人对你伸出的援助之手,并把它传递给未来需要的人

* 尊重他人,慎重言行:礼貌文明的交流方式除了能得到别人同样的回应,更能减少不必要的摩擦,保证高效的交流


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## 环境

* 操作系统: 如Windows 11或者Ubuntu 22.04
* Python版本: 如VeighNa Studio-4.0.0
* VeighNa版本: 如v4.0.0发行版或者dev branch 20250320(下载日期)

## Issue类型
三选一:Bug/Enhancement/Question

## 预期程序行为


## 实际程序行为


## 重现步骤

针对Bug类型Issue,请提供具体重现步骤以及报错截图



================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
建议每次发起的PR内容尽可能精简,复杂的修改请拆分为多次PR,便于管理合并。

## 改进内容

1. 
2. 
3.

## 相关的Issue号(如有)

Close #

================================================
FILE: .github/SUPPORT.md
================================================
# 获取帮助

在开发和使用项目的过程中遇到问题时,获取帮助的渠道包括:

* Github Issues:[Issues页面](https://github.com/veighna-global/vnpy_evo/issues)



================================================
FILE: .github/workflows/pythonapp.yml
================================================
name: Python application

on: [push]

jobs:
  build:

    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python 3.13
      uses: actions/setup-python@v1
      with:
        python-version: '3.13'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install ta-lib==0.6.3 --index=https://pypi.vnpy.com
        pip install vnpy ruff mypy uv
    - name: Lint with ruff
      run: |
        # Run ruff linter based on pyproject.toml configuration
        ruff check .
    - name: Type check with mypy
      run: |
        # Run mypy type checking based on pyproject.toml configuration
        mypy vnpy_binance
    - name: Build packages with uv
      run: |
        # Build source distribution and wheel distribution
        uv build

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
*.pya


================================================
FILE: CHANGELOG.md
================================================
# 2026.04.27

1. update linear and portfolio gateways with new endpoints for routed streams

# 2026.04.15

1. fix missing data when querying bar history of inverse contract

# 2026.04.13

1. update tick.datetime when receiving book update
2. fix bug: start time is greater than end time when querying bar history

# 2026.03.27

1. add TRADIFI_PERPETUAL contract support for portfolio gateway

# 2026.03.26

1. support TRADIFI_PERPETUAL contract

# 2026.03.06

1. new PortfolioGateway for portfolio margin mode
2. update SpotGateway to latest version
3. support funding rate subscription for linear swap

# 2026.01.25

1. use periodic subscription mechanism to avoid connection loss

# 2025.06.17

1. refactor BinanceSpotGateway and BinanceInverseGateway
2. remove unused disconnect function

# 2025.05.08

1. remove dependency on vnpy_evo
2. change to use GLOBAL exchange
3. refactor BinanceLinearGateway 

# 2025.1.25

1. BinanceLinearGateway replace REST API with Websocket API for sending orders

# 2024.12.16

1. write log (event) when exception raised by websocket client

# 2024.9.16

1. add more detail to TickData.extra including: active_volume/active_turnover/trade_count
2. BinanceLinearGateway upgrade to v3 api for querying account and position

# 2024.9.4

1. add extra data dict for BarData

# 2024.9.3

1. use vnpy_evo for rest and websocket client

# 2024.8.10

1. fix the problem of datetime timezone
2. fix the problem of account data receiving no update when the balance is all sold out
3. only keep user stream when key is provided

# 2024.5.7

1. use numpy.format_float_positional to improve float number precisio…
2. output log message of time offse
3. query private data after time offset is calculated


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2015-present, Xiaoyou Chen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Binance trading gateway for VeighNa

<p align="center">
  <img src ="https://github.com/veighna-global/vnpy_evo/blob/dev/logo.png" width="300" height="300"/>
</p>

<p align="center">
    <img src ="https://img.shields.io/badge/version-2026.04.27-blueviolet.svg"/>
    <img src ="https://img.shields.io/badge/platform-windows|linux|macos-yellow.svg"/>
    <img src ="https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg"/>
    <img src ="https://img.shields.io/github/license/veighna-global/vnpy_binance.svg?color=orange"/>
</p>


## Introduction

This gateway is developed based on Binance's REST and Websocket API, and supports spot, linear contract and inverse contract trading.

**For derivatives contract trading, please notice:**

1. Only supports cross margin mode.
2. Only supports one-way position mode.

## Install

Users can easily install ``vnpy_binance`` by pip according to the following command.

```
pip install vnpy_binance
```

Also, users can install ``vnpy_binance`` using the source code. Clone the repository and install as follows:

```
git clone https://github.com/veighna-global/vnpy_binance.git && cd vnpy_binance

python setup.py install
```

## A Simple Example

Save this as run.py.

```
from vnpy.event import EventEngine
from vnpy.trader.engine import MainEngine
from vnpy.trader.ui import MainWindow, create_qapp

from vnpy_binance import (
    BinanceLinearGateway,
)


def main() -> None:
    """main entry"""
    qapp = create_qapp()

    event_engine = EventEngine()
    main_engine = MainEngine(event_engine)
    main_engine.add_gateway(BinanceLinearGateway)

    main_window = MainWindow(main_engine, event_engine)
    main_window.showMaximized()

    qapp.exec()


if __name__ == "__main__":
    main()
```


================================================
FILE: pyproject.toml
================================================
[project]
name = "vnpy_binance"
dynamic = ["version"]
description = "BINANCE trading gateway for VeighNa."
readme = "README.md"
license = {text = "MIT"}
authors = [{name = "VeighNa Global", email = "veighna@hotmail.com"}]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Operating System :: Microsoft :: Windows",
    "Operating System :: POSIX :: Linux",
    "Operating System :: MacOS",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Office/Business :: Financial :: Investment",
    "Programming Language :: Python :: Implementation :: CPython",
    "License :: OSI Approved :: MIT License",
    "Natural Language :: English",
]
requires-python = ">=3.10"
keywords = ["quant", "quantitative", "investment", "trading", "algotrading", "binance", "btc", "crypto"]
dependencies = [
    "vnpy_rest",
    "vnpy_websocket",
]

[project.urls]
"Homepage" = "https://www.github.com/veighna-global"
"Source" = "https://www.github.com/veighna-global"

[build-system]
requires = ["hatchling>=1.27.0"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "vnpy_binance/__init__.py"
pattern = "__version__ = ['\"](?P<version>[^'\"]+)['\"]"

[tool.hatch.build.targets.wheel]
packages = ["vnpy_binance"]
include-package-data = true

[tool.hatch.build.targets.sdist]
include = ["vnpy_binance*"] 

[tool.ruff]
target-version = "py310"
output-format = "full"

[tool.ruff.lint]
select = [
    "B",  # flake8-bugbear
    "E",  # pycodestyle error
    "F",  # pyflakes
    "UP",  # pyupgrade
    "W",  # pycodestyle warning
]
ignore = ["E501"]

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
ignore_missing_imports = true 

[[tool.mypy.overrides]]
module = [
    "vnpy.*",
    "vnpy_rest.*",
    "vnpy_websocket.*"
]
ignore_errors = true
ignore_missing_imports = true

================================================
FILE: script/run.py
================================================
from vnpy.event import EventEngine
from vnpy.trader.engine import MainEngine
from vnpy.trader.ui import MainWindow, create_qapp

from vnpy_binance import (
    BinanceLinearGateway,
    BinanceInverseGateway,
    BinanceSpotGateway,
    BinancePortfolioGateway
)
from vnpy_datamanager import DataManagerApp


def main() -> None:
    """main entry"""
    qapp = create_qapp()

    event_engine = EventEngine()
    main_engine = MainEngine(event_engine)
    main_engine.add_gateway(BinanceLinearGateway)
    main_engine.add_gateway(BinanceInverseGateway)
    main_engine.add_gateway(BinanceSpotGateway)
    main_engine.add_gateway(BinancePortfolioGateway)

    main_engine.add_app(DataManagerApp)

    main_window = MainWindow(main_engine, event_engine)
    main_window.showMaximized()

    qapp.exec()


if __name__ == "__main__":
    main()


================================================
FILE: vnpy_binance/__init__.py
================================================
# The MIT License (MIT)
#
# Copyright (c) 2015-present, Xiaoyou Chen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from .linear_gateway import BinanceLinearGateway
from .inverse_gateway import BinanceInverseGateway
from .spot_gateway import BinanceSpotGateway
from .portfolio_gateway import BinancePortfolioGateway


__version__ = "2026.04.27"


__all__ = [
    "BinanceLinearGateway",
    "BinanceInverseGateway",
    "BinanceSpotGateway",
    "BinancePortfolioGateway"
]


================================================
FILE: vnpy_binance/inverse_gateway.py
================================================
import hashlib
import hmac
import time
import urllib.parse
from copy import copy
from typing import Any
from collections.abc import Callable
from time import sleep
from datetime import datetime, timedelta

from numpy import format_float_positional

from vnpy.event import Event, EventEngine
from vnpy.trader.constant import (
    Direction,
    Exchange,
    Product,
    Status,
    OrderType,
    Interval
)
from vnpy.trader.gateway import BaseGateway
from vnpy.trader.object import (
    TickData,
    OrderData,
    TradeData,
    AccountData,
    ContractData,
    PositionData,
    BarData,
    OrderRequest,
    CancelRequest,
    SubscribeRequest,
    HistoryRequest
)
from vnpy.trader.event import EVENT_TIMER
from vnpy.trader.utility import round_to, ZoneInfo
from vnpy_rest import Request, RestClient, Response
from vnpy_websocket import WebsocketClient


# Timezone constant
UTC_TZ = ZoneInfo("UTC")

# Real server hosts
REAL_REST_HOST: str = "https://dapi.binance.com"
REAL_TRADE_HOST: str = "wss://ws-dapi.binance.com/ws-dapi/v1"
REAL_USER_HOST: str = "wss://dstream.binance.com/ws/"
REAL_DATA_HOST: str = "wss://dstream.binance.com/stream"

# Testnet server hosts
TESTNET_REST_HOST: str = "https://testnet.binancefuture.com"
TESTNET_TRADE_HOST: str = "wss://testnet.binancefuture.com/ws-dapi/v1"
TESTNET_USER_HOST: str = "wss://dstream.binancefuture.com/ws/"
TESTNET_DATA_HOST: str = "wss://dstream.binancefuture.com/stream"

# Order status map
STATUS_BINANCE2VT: dict[str, Status] = {
    "NEW": Status.NOTTRADED,
    "PARTIALLY_FILLED": Status.PARTTRADED,
    "FILLED": Status.ALLTRADED,
    "CANCELED": Status.CANCELLED,
    "REJECTED": Status.REJECTED,
    "EXPIRED": Status.CANCELLED
}

# Order type map
ORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {
    OrderType.LIMIT: ("LIMIT", "GTC"),
    OrderType.MARKET: ("MARKET", "GTC"),
    OrderType.FAK: ("LIMIT", "IOC"),
    OrderType.FOK: ("LIMIT", "FOK"),
}
ORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCE.items()}

# Direction map
DIRECTION_VT2BINANCE: dict[Direction, str] = {
    Direction.LONG: "BUY",
    Direction.SHORT: "SELL"
}
DIRECTION_BINANCE2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCE.items()}

# Product map
PRODUCT_BINANCE2VT: dict[str, Product] = {
    "PERPETUAL": Product.SWAP,
    "PERPETUAL_DELIVERING": Product.SWAP,
    "CURRENT_MONTH": Product.FUTURES,
    "NEXT_MONTH": Product.FUTURES,
    "CURRENT_QUARTER": Product.FUTURES,
    "NEXT_QUARTER": Product.FUTURES,
}

# Kline interval map
INTERVAL_VT2BINANCE: dict[Interval, str] = {
    Interval.MINUTE: "1m",
    Interval.HOUR: "1h",
    Interval.DAILY: "1d",
}

# Timedelta map
TIMEDELTA_MAP: dict[Interval, timedelta] = {
    Interval.MINUTE: timedelta(minutes=1),
    Interval.HOUR: timedelta(hours=1),
    Interval.DAILY: timedelta(days=1),
}

# Set weboscket timeout to 24 hour
WEBSOCKET_TIMEOUT = 24 * 60 * 60


class BinanceInverseGateway(BaseGateway):
    """
    The Binance inverse trading gateway for VeighNa.

    This gateway provides trading functionality for Binance Coin-M perpetual contracts
    and delivery futures through their API.

    Features:
    1. Only support crossed position
    2. Only support one-way mode
    3. Provides market data, trading, and account management capabilities
    """

    default_name: str = "BINANCE_INVERSE"

    default_setting: dict = {
        "API Key": "",
        "API Secret": "",
        "Server": ["REAL", "TESTNET"],
        "Kline Stream": ["False", "True"],
        "Proxy Host": "",
        "Proxy Port": 0
    }

    exchanges: list[Exchange] = [Exchange.GLOBAL]

    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
        """
        The init method of the gateway.

        This method initializes the gateway components including REST API,
        trading API, user data API, and market data API. It also sets up
        the data structures for order and contract storage.

        Parameters:
            event_engine: the global event engine object of VeighNa
            gateway_name: the unique name for identifying the gateway
        """
        super().__init__(event_engine, gateway_name)

        self.trade_api: TradeApi = TradeApi(self)
        self.user_api: UserApi = UserApi(self)
        self.md_api: MdApi = MdApi(self)
        self.rest_api: RestApi = RestApi(self)

        self.orders: dict[str, OrderData] = {}
        self.symbol_contract_map: dict[str, ContractData] = {}
        self.name_contract_map: dict[str, ContractData] = {}

    def connect(self, setting: dict) -> None:
        """
        Start server connections.

        This method establishes connections to Binance servers
        using the provided settings.

        Parameters:
            setting: A dictionary containing connection parameters including
                    API credentials, server selection, and proxy configuration
        """
        key: str = setting["API Key"]
        secret: str = setting["API Secret"]
        server: str = setting["Server"]
        kline_stream: bool = setting["Kline Stream"] == "True"
        proxy_host: str = setting["Proxy Host"]
        proxy_port: int = setting["Proxy Port"]

        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)
        self.trade_api.connect(key, secret, server, proxy_host, proxy_port)
        self.md_api.connect(server, kline_stream, proxy_host, proxy_port)

        self.event_engine.register(EVENT_TIMER, self.process_timer_event)

    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe to market data.

        This method forwards the subscription request to the market data API.

        Parameters:
            req: Subscription request object containing the symbol to subscribe
        """
        self.md_api.subscribe(req)

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order.

        This method forwards the order request to the trading API.

        Parameters:
            req: Order request object containing order details

        Returns:
            str: The VeighNa order ID if successful, empty string if failed
        """
        return self.trade_api.send_order(req)

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order.

        This method forwards the cancellation request to the trading API.

        Parameters:
            req: Cancel request object containing order details
        """
        self.trade_api.cancel_order(req)

    def query_account(self) -> None:
        """
        Query account balance.

        Not required since Binance provides websocket updates for account balances.
        """
        pass

    def query_position(self) -> None:
        """
        Query current positions.

        Not required since Binance provides websocket updates for positions.
        """
        pass

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """
        Query historical kline data.

        This method forwards the history request to the REST API.

        Parameters:
            req: History request object containing query parameters

        Returns:
            list[BarData]: List of historical kline data bars
        """
        return self.rest_api.query_history(req)

    def close(self) -> None:
        """
        Close server connections.

        This method stops all API connections and releases resources.
        """
        self.rest_api.stop()
        self.user_api.stop()
        self.md_api.stop()
        self.trade_api.stop()

    def process_timer_event(self, event: Event) -> None:
        """
        Process timer task.

        This function is called regularly by the event engine to perform scheduled tasks,
        such as keeping the user stream alive.

        Parameters:
            event: Timer event object
        """
        self.rest_api.keep_user_stream()

        self.md_api.subscribe_new_channels()

    def on_order(self, order: OrderData) -> None:
        """
        Save a copy of order and then push to event engine.

        Parameters:
            order: Order data object
        """
        self.orders[order.orderid] = copy(order)
        super().on_order(order)

    def get_order(self, orderid: str) -> OrderData | None:
        """
        Get previously saved order by order id.

        Parameters:
            orderid: The ID of the order to retrieve

        Returns:
            Order data object if found, None otherwise
        """
        return self.orders.get(orderid, None)

    def on_contract(self, contract: ContractData) -> None:
        """
        Save contract data in mappings and push to event engine.

        Parameters:
            contract: Contract data object
        """
        self.symbol_contract_map[contract.symbol] = contract
        self.name_contract_map[contract.name] = contract
        super().on_contract(contract)

    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
        """
        Get contract data by VeighNa symbol.

        Parameters:
            symbol: VeighNa symbol (e.g. "BTC_SWAP_BINANCE")

        Returns:
            Contract data object if found, None otherwise
        """
        return self.symbol_contract_map.get(symbol, None)

    def get_contract_by_name(self, name: str) -> ContractData | None:
        """
        Get contract data by exchange symbol name.

        Parameters:
            name: Exchange symbol name (e.g. "BTCUSD")

        Returns:
            Contract data object if found, None otherwise
        """
        return self.name_contract_map.get(name, None)


class RestApi(RestClient):
    """
    The REST API of BinanceInverseGateway.

    This class handles HTTP requests to Binance API endpoints, including:
    - Authentication and signature generation
    - Contract information queries
    - Account and position queries
    - Order management
    - Historical data queries
    - User data stream management
    """

    def __init__(self, gateway: BinanceInverseGateway) -> None:
        """
        The init method of the API.

        This method initializes the REST API with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceInverseGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.user_api: UserApi = self.gateway.user_api

        self.key: str = ""
        self.secret: bytes = b""

        self.user_stream_key: str = ""
        self.keep_alive_count: int = 0
        self.time_offset: int = 0

        self.order_count: int = 1_000_000
        self.order_prefix: str = ""

    def sign(self, request: Request) -> Request:
        """
        Standard callback for signing a request.

        This method adds the necessary authentication parameters and signature
        to requests that require API key authentication.

        It handles:
        1. Path construction with query parameters
        2. Timestamp generation with server time offset adjustment
        3. HMAC-SHA256 signature generation
        4. Required authentication headers

        Parameters:
            request: Request object to be signed

        Returns:
            Request: Modified request with authentication parameters
        """
        # Construct path with query parameters if they exist
        if request.params:
            path: str = request.path + "?" + urllib.parse.urlencode(request.params)
        else:
            request.params = {}
            path = request.path

        # Get current timestamp in milliseconds
        timestamp: int = int(time.time() * 1000)

        # Adjust timestamp based on time offset with server
        if self.time_offset > 0:
            timestamp -= abs(self.time_offset)
        elif self.time_offset < 0:
            timestamp += abs(self.time_offset)

        # Add timestamp to request parameters
        request.params["timestamp"] = timestamp

        # Generate signature using HMAC SHA256
        query: str = urllib.parse.urlencode(sorted(request.params.items()))
        signature: str = hmac.new(
            self.secret,
            query.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()

        # Append signature to query string
        query += f"&signature={signature}"
        path = request.path + "?" + query

        # Update request with signed path and clear params/data
        request.path = path
        request.params = {}
        request.data = {}

        # Add required headers for API authentication
        request.headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
            "X-MBX-APIKEY": self.key,
            "Connection": "close"
        }

        return request

    def connect(
        self,
        key: str,
        secret: str,
        server: str,
        proxy_host: str,
        proxy_port: int
    ) -> None:
        """Start server connection"""
        self.key = key
        self.secret = secret.encode()
        self.proxy_port = proxy_port
        self.proxy_host = proxy_host
        self.server = server

        self.order_prefix = datetime.now().strftime("%y%m%d%H%M%S")

        if self.server == "REAL":
            self.init(REAL_REST_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)

        self.start()

        self.gateway.write_log("REST API started")

        self.query_time()

    def query_time(self) -> None:
        """
        Query server time to calculate local time offset.

        This function sends a request to get the exchange server time,
        which is used to calculate the local time offset for timestamp synchronization.
        """
        path: str = "/dapi/v1/time"

        self.add_request(
            "GET",
            path,
            callback=self.on_query_time
        )

    def query_account(self) -> None:
        """
        Query account balance.

        This function sends a request to get the account balance information,
        including wallet balance, available balance, and margin.
        """
        path: str = "/dapi/v1/account"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_account,
        )

    def query_position(self) -> None:
        """
        Query holding positions.

        This function sends a request to get current position data,
        including position amount, entry price, and unrealized profit/loss.
        """
        path: str = "/dapi/v1/positionRisk"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_position,
        )

    def query_order(self) -> None:
        """
        Query open orders.

        This function sends a request to get all active orders
        that have not been fully filled or cancelled.
        """
        path: str = "/dapi/v1/openOrders"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_order,
        )

    def query_contract(self) -> None:
        """
        Query available contracts.

        This function sends a request to get exchange information,
        including all available trading instruments, their precision,
        and trading rules.
        """
        path: str = "/dapi/v1/exchangeInfo"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_contract,
        )

    def start_user_stream(self) -> None:
        """
        Create listen key for user stream.

        This function sends a request to create a listen key which is
        required to establish a user data websocket connection.
        """
        path: str = "/dapi/v1/listenKey"

        self.add_request(
            method="POST",
            path=path,
            callback=self.on_start_user_stream,
        )

    def keep_user_stream(self) -> None:
        """
        Extend listen key validity.

        This function sends a request to keep the listen key active,
        which is required to maintain the user data websocket connection.
        The listen key will expire after 60 minutes if not refreshed.
        """
        if not self.user_stream_key:
            return

        self.keep_alive_count += 1
        if self.keep_alive_count < 600:
            return
        self.keep_alive_count = 0

        params: dict = {"listenKey": self.user_stream_key}

        path: str = "/dapi/v1/listenKey"

        self.add_request(
            method="PUT",
            path=path,
            callback=self.on_keep_user_stream,
            params=params,
            on_error=self.on_keep_user_stream_error
        )

    def on_query_time(self, data: dict, request: Request) -> None:
        """
        Callback of server time query.

        This function processes the server time response and calculates
        the time offset between local and server time, which is used for
        request timestamp synchronization.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        local_time: int = int(time.time() * 1000)
        server_time: int = int(data["serverTime"])
        self.time_offset = local_time - server_time

        self.gateway.write_log(f"Server time updated, local offset: {self.time_offset}ms")

        self.query_contract()

    def on_query_account(self, data: dict, request: Request) -> None:
        """
        Callback of account balance query.

        This function processes the account balance response and
        creates AccountData objects for each asset in the account.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for asset in data["assets"]:
            account: AccountData = AccountData(
                accountid=asset["asset"],
                balance=float(asset["walletBalance"]),
                frozen=float(asset["maintMargin"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        self.gateway.write_log("Account data received")

    def on_query_position(self, data: list, request: Request) -> None:
        """
        Callback of holding positions query.

        This function processes the position data response and
        creates PositionData objects for each position held.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data:
            name: str = d["symbol"]
            contract: ContractData | None = self.gateway.get_contract_by_name(name)
            if not contract:
                continue

            position: PositionData = PositionData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                direction=Direction.NET,
                volume=float(d["positionAmt"]),
                price=float(d["entryPrice"]),
                pnl=float(d["unRealizedProfit"]),
                gateway_name=self.gateway_name,
            )

            if position.volume:
                self.gateway.on_position(position)

        self.gateway.write_log("Position data received")

    def on_query_order(self, data: list, request: Request) -> None:
        """
        Callback of open orders query.

        This function processes the open orders response and
        creates OrderData objects for each active order.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data:
            key: tuple[str, str] = (d["type"], d["timeInForce"])
            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
            if not order_type:
                continue

            contract: ContractData | None = self.gateway.get_contract_by_symbol(d["symbol"])
            if not contract:
                continue

            order: OrderData = OrderData(
                orderid=d["clientOrderId"],
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                price=float(d["price"]),
                volume=float(d["origQty"]),
                type=order_type,
                direction=DIRECTION_BINANCE2VT[d["side"]],
                traded=float(d["executedQty"]),
                status=STATUS_BINANCE2VT[d["status"]],
                datetime=generate_datetime(d["time"]),
                gateway_name=self.gateway_name,
            )
            self.gateway.on_order(order)

        self.gateway.write_log("Order data received")

    def on_query_contract(self, data: dict, request: Request) -> None:
        """
        Callback of available contracts query.

        This function processes the exchange info response and
        creates ContractData objects for each trading instrument.
        It handles different contract types and extracts trading rules
        like price tick, minimum/maximum volumes from filters.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data["symbols"]:
            pricetick: float = 1
            min_volume: float = 1
            max_volume: float = 1

            for f in d["filters"]:
                if f["filterType"] == "PRICE_FILTER":
                    pricetick = float(f["tickSize"])
                elif f["filterType"] == "LOT_SIZE":
                    min_volume = float(f["minQty"])
                    max_volume = float(f["maxQty"])

            product: Product | None = PRODUCT_BINANCE2VT.get(d["contractType"], None)
            if product == Product.SWAP:
                symbol: str = d["symbol"].replace("_PERP", "") + "_SWAP_BINANCE"
            elif product == Product.FUTURES:
                symbol = d["symbol"] + "_BINANCE"
            else:
                continue

            contract: ContractData = ContractData(
                symbol=symbol,
                exchange=Exchange.GLOBAL,
                name=d["symbol"],
                pricetick=pricetick,
                size=1,
                min_volume=min_volume,
                max_volume=max_volume,
                product=PRODUCT_BINANCE2VT.get(d["contractType"], Product.SWAP),
                net_position=True,
                history_data=True,
                gateway_name=self.gateway_name,
                stop_supported=True
            )
            self.gateway.on_contract(contract)

        self.gateway.write_log("Contract data received")

        # Query private data after time offset is calculated
        if self.key and self.secret:
            self.query_order()
            self.query_account()
            self.query_position()
            self.start_user_stream()

    def on_start_user_stream(self, data: dict, request: Request) -> None:
        """
        Successful callback of start_user_stream.

        This function processes the listen key response and initializes
        the user data websocket connection with the provided key.

        Parameters:
            data: Response data from the server containing the listen key
            request: Original request object
        """
        self.user_stream_key = data["listenKey"]
        self.keep_alive_count = 0

        if self.server == "REAL":
            url = REAL_USER_HOST + self.user_stream_key
        else:
            url = TESTNET_USER_HOST + self.user_stream_key

        self.user_api.connect(url, self.proxy_host, self.proxy_port)

    def on_keep_user_stream(self, data: dict, request: Request) -> None:
        """
        Successful callback of keep_user_stream.

        This function handles the successful response of the listen key
        refresh request. No action is needed on success.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        pass

    def on_keep_user_stream_error(self, exception_type: type, exception_value: Exception, tb: Any, request: Request) -> None:
        """
        Error callback of keep_user_stream.

        This function handles errors from the listen key refresh request.
        Timeout exceptions are ignored as they are common and non-critical.

        Parameters:
            exception_type: Type of the exception
            exception_value: Exception instance
            tb: Traceback object
            request: Original request object
        """
        if not issubclass(exception_type, TimeoutError):        # Ignore timeout exception
            self.on_error(exception_type, exception_value, tb, request)

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """Query kline history data"""
        # Check if the contract and interval exist
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            return []

        if not req.interval:
            return []

        # Prepare history list
        history: list[BarData] = []
        limit: int = 1500
        interval_delta: timedelta = TIMEDELTA_MAP[req.interval]
        page_span: timedelta = interval_delta * (limit - 1)
        current_start_dt: datetime = req.start

        while True:
            if req.end and current_start_dt >= req.end:
                break

            # Create query parameters
            params: dict = {
                "symbol": contract.name,
                "interval": INTERVAL_VT2BINANCE[req.interval],
                "limit": limit
            }

            params["startTime"] = int(datetime.timestamp(current_start_dt)) * 1000
            path: str = "/dapi/v1/klines"
            if req.end:
                page_end_dt: datetime = min(req.end, current_start_dt + page_span)
                params["endTime"] = int(datetime.timestamp(page_end_dt)) * 1000

            resp: Response = self.request(
                "GET",
                path=path,
                params=params
            )

            # Break the loop if request failed
            if resp.status_code // 100 != 2:
                msg: str = f"Query kline history failed, status code: {resp.status_code}, message: {resp.text}"
                self.gateway.write_log(msg)
                break
            else:
                data: list = resp.json()
                if not data:
                    msg = f"No kline history data is received, start time: {current_start_dt}"
                    self.gateway.write_log(msg)
                    break

                buf: list[BarData] = []

                for row in data:
                    bar: BarData = BarData(
                        symbol=req.symbol,
                        exchange=req.exchange,
                        datetime=generate_datetime(row[0]),
                        interval=req.interval,
                        volume=float(row[5]),
                        turnover=float(row[7]),
                        open_price=float(row[1]),
                        high_price=float(row[2]),
                        low_price=float(row[3]),
                        close_price=float(row[4]),
                        gateway_name=self.gateway_name
                    )
                    bar.extra = {
                        "trade_count": int(row[8]),
                        "active_volume": float(row[9]),
                        "active_turnover": float(row[10]),
                    }
                    buf.append(bar)

                begin_dt: datetime = buf[0].datetime
                end_dt: datetime = buf[-1].datetime

                history.extend(buf)
                msg = f"Query kline history finished, {req.symbol} - {req.interval.value}, {begin_dt} - {end_dt}"
                self.gateway.write_log(msg)

                next_start_dt: datetime = end_dt + interval_delta

                if next_start_dt <= current_start_dt:
                    msg = (
                        "Query kline history pagination stopped because received data "
                        f"did not advance, start: {current_start_dt}, end: {end_dt}"
                    )
                    self.gateway.write_log(msg)
                    break

                # Break the loop if the latest data received
                if (
                    len(data) < limit
                    or (req.end and next_start_dt >= req.end)
                ):
                    break

                # Update query start time
                current_start_dt = next_start_dt

            # Wait to meet request flow limit
            sleep(0.5)

        # Remove the unclosed kline
        if history:
            history.pop(-1)

        return history


class UserApi(WebsocketClient):
    """
    The user data websocket API of BinanceInverseGateway.

    This class handles user data events from Binance through websocket connection.
    It processes real-time updates for:
    - Account balance changes
    - Position updates
    - Order status changes
    - Trade executions
    """

    def __init__(self, gateway: BinanceInverseGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceInverseGateway = gateway
        self.gateway_name: str = gateway.gateway_name

    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
        """
        Start server connection.

        This method establishes a websocket connection to Binance user data stream.

        Parameters:
            url: Websocket endpoint URL with listen key
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        self.start()

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the websocket connection to the server
        is successfully established. It logs the connection status.
        """
        self.gateway.write_log("User API connected")

    def on_packet(self, packet: dict) -> None:
        """
        Callback of data update.

        This function processes websocket messages from the user data stream.
        It handles different event types including account updates, order updates,
        and listen key expiration.

        Parameters:
            packet: JSON data received from websocket
        """
        match packet["e"]:
            case "ACCOUNT_UPDATE":
                self.on_account(packet)
            case "ORDER_TRADE_UPDATE":
                self.on_order(packet)
            case "listenKeyExpired":
                self.on_listen_key_expired()

    def on_listen_key_expired(self) -> None:
        """
        Callback of listen key expired.

        This function is called when the exchange notifies that the listen key
        has expired. It will log a message and disconnect the websocket connection.
        """
        self.gateway.write_log("Listen key expired")
        self.disconnect()

    def on_account(self, packet: dict) -> None:
        """
        Callback of account balance and holding position update.

        This function processes the account update event from the user data stream,
        including balance changes and position updates.

        Parameters:
            packet: JSON data received from websocket
        """
        for acc_data in packet["a"]["B"]:
            account: AccountData = AccountData(
                accountid=acc_data["a"],
                balance=float(acc_data["wb"]),
                frozen=float(acc_data["wb"]) - float(acc_data["cw"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        for pos_data in packet["a"]["P"]:
            if pos_data["ps"] == "BOTH":
                volume = pos_data["pa"]
                if "." in volume:
                    volume = float(volume)
                else:
                    volume = int(volume)

                name: str = pos_data["s"]
                contract: ContractData | None = self.gateway.get_contract_by_name(name)
                if not contract:
                    continue

                position: PositionData = PositionData(
                    symbol=contract.symbol,
                    exchange=Exchange.GLOBAL,
                    direction=Direction.NET,
                    volume=volume,
                    price=float(pos_data["ep"]),
                    pnl=float(pos_data["up"]),
                    gateway_name=self.gateway_name,
                )
                self.gateway.on_position(position)

    def on_order(self, packet: dict) -> None:
        """
        Callback of order and trade update.

        This function processes the order update event from the user data stream,
        including order status changes and trade executions.

        Parameters:
            packet: JSON data received from websocket
        """
        ord_data: dict = packet["o"]

        # Filter unsupported order type
        key: tuple[str, str] = (ord_data["o"], ord_data["f"])
        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
        if not order_type:
            return

        # Filter unsupported symbol
        name: str = ord_data["s"]
        contract: ContractData | None = self.gateway.get_contract_by_name(name)
        if not contract:
            return

        # Create and push order
        order: OrderData = OrderData(
            symbol=contract.symbol,
            exchange=Exchange.GLOBAL,
            orderid=str(ord_data["c"]),
            type=order_type,
            direction=DIRECTION_BINANCE2VT[ord_data["S"]],
            price=float(ord_data["p"]),
            volume=float(ord_data["q"]),
            traded=float(ord_data["z"]),
            status=STATUS_BINANCE2VT[ord_data["X"]],
            datetime=generate_datetime(packet["E"]),
            gateway_name=self.gateway_name,
        )

        self.gateway.on_order(order)

        # Round trade volume to meet step size
        trade_volume: float = float(ord_data["l"])
        trade_volume = round_to(trade_volume, contract.min_volume)
        if not trade_volume:
            return

        # Create and push trade
        trade: TradeData = TradeData(
            symbol=order.symbol,
            exchange=order.exchange,
            orderid=order.orderid,
            tradeid=ord_data["t"],
            direction=order.direction,
            price=float(ord_data["L"]),
            volume=trade_volume,
            datetime=generate_datetime(ord_data["T"]),
            gateway_name=self.gateway_name,
        )
        self.gateway.on_trade(trade)

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the websocket connection is closed.
        It logs the disconnection details and attempts to restart the user stream.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"User API disconnected, code: {status_code}, msg: {msg}")
        self.gateway.rest_api.start_user_stream()

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the websocket connection.
        It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"User API exception: {e}")


class MdApi(WebsocketClient):
    """
    The market data websocket API of BinanceInverseGateway.

    This class handles market data from Binance through websocket connection.
    It processes real-time updates for:
    - Tickers (24hr statistics)
    - Order book depth (10 levels)
    - Klines (candlestick data) if enabled
    """

    def __init__(self, gateway: BinanceInverseGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceInverseGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.ticks: dict[str, TickData] = {}
        self.reqid: int = 0
        self.kline_stream: bool = False

        self.new_channels: list[str] = []

    def connect(
        self,
        server: str,
        kline_stream: bool,
        proxy_host: str,
        proxy_port: int,
    ) -> None:
        """
        Start server connection.

        This method establishes a websocket connection to Binance market data stream.

        Parameters:
            server: Server type ("REAL" or "TESTNET")
            kline_stream: Whether to include kline data stream
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.kline_stream = kline_stream

        if server == "REAL":
            self.init(REAL_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        else:
            self.init(TESTNET_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)

        self.start()

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the market data websocket connection
        is successfully established. It logs the connection status and
        resubscribes to previously subscribed market data channels.
        """
        self.gateway.write_log("MD API connected")

        # Resubscribe market data
        if self.ticks:
            channels = []
            for symbol in self.ticks.keys():
                channels.append(f"{symbol}@ticker")
                channels.append(f"{symbol}@depth10")

                if self.kline_stream:
                    channels.append(f"{symbol}@kline_1m")

            packet: dict = {
                "method": "SUBSCRIBE",
                "params": channels,
                "id": self.reqid
            }
            self.send_packet(packet)

    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe to market data.

        This function sends subscription requests for ticker and depth data
        for the specified trading instrument. If kline_stream is enabled,
        it will also subscribe to 1-minute kline data.

        Parameters:
            req: Subscription request object containing symbol information
        """
        if req.symbol in self.ticks:
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to subscribe data, symbol not found: {req.symbol}")
            return

        self.reqid += 1

        # Initialize tick object
        tick: TickData = TickData(
            symbol=req.symbol,
            name=contract.name,
            exchange=Exchange.GLOBAL,
            datetime=datetime.now(UTC_TZ),
            gateway_name=self.gateway_name,
        )
        tick.extra = {}
        self.ticks[req.symbol] = tick

        channels: list[str] = [
            f"{contract.name.lower()}@ticker",
            f"{contract.name.lower()}@depth10"
        ]

        if self.kline_stream:
            channels.append(f"{contract.name.lower()}@kline_1m")

        self.new_channels.extend(channels)

    def subscribe_new_channels(self) -> None:
        """
        Update timer event.

        This function sends subscription requests for new channels
        to the market data websocket server.
        """
        if not self.new_channels:
            return

        packet: dict = {
            "method": "SUBSCRIBE",
            "params": self.new_channels,
            "id": self.reqid
        }
        self.send_packet(packet)

        self.new_channels = []

    def on_packet(self, packet: dict) -> None:
        """
        Callback of market data update.

        This function processes different types of market data updates,
        including ticker, depth, and kline data. It updates the corresponding
        TickData object and pushes updates to the gateway.

        Parameters:
            packet: JSON data received from websocket
        """
        stream: str | None = packet.get("stream", None)
        if not stream:
            return

        data: dict = packet["data"]

        name, channel = stream.split("@")
        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper())
        if not contract:
            return
        tick: TickData = self.ticks[contract.symbol]

        if channel == "ticker":
            tick.volume = float(data["v"])
            tick.turnover = float(data["q"])
            tick.open_price = float(data["o"])
            tick.high_price = float(data["h"])
            tick.low_price = float(data["l"])
            tick.last_price = float(data["c"])
            tick.datetime = generate_datetime(float(data["E"]))
        elif channel == "depth10":
            bids: list = data["b"]
            for n in range(min(10, len(bids))):
                price, volume = bids[n]
                tick.__setattr__("bid_price_" + str(n + 1), float(price))
                tick.__setattr__("bid_volume_" + str(n + 1), float(volume))

            asks: list = data["a"]
            for n in range(min(10, len(asks))):
                price, volume = asks[n]
                tick.__setattr__("ask_price_" + str(n + 1), float(price))
                tick.__setattr__("ask_volume_" + str(n + 1), float(volume))
            tick.datetime = generate_datetime(float(data["E"]))
        else:
            kline_data: dict = data["k"]

            # Check if bar is closed
            bar_ready: bool = kline_data.get("x", False)
            if not bar_ready:
                return

            if tick.extra is None:
                tick.extra = {}

            dt: datetime = generate_datetime(float(kline_data["t"]))

            tick.extra["bar"] = BarData(
                symbol=name.upper(),
                exchange=Exchange.GLOBAL,
                datetime=dt.replace(second=0, microsecond=0),
                interval=Interval.MINUTE,
                volume=float(kline_data["v"]),
                turnover=float(kline_data["q"]),
                open_price=float(kline_data["o"]),
                high_price=float(kline_data["h"]),
                low_price=float(kline_data["l"]),
                close_price=float(kline_data["c"]),
                gateway_name=self.gateway_name
            )

        if tick.last_price:
            tick.localtime = datetime.now()
            self.gateway.on_tick(copy(tick))

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the market data websocket connection
        is closed. It logs the disconnection details.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"MD API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the market data
        websocket connection. It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"MD API exception: {e}")


class TradeApi(WebsocketClient):
    """
    The trading websocket API of BinanceInverseGateway.

    This class handles trading operations with Binance through websocket connection.
    It provides functionality for:
    - Order placement
    - Order cancellation
    - Request authentication and signature generation
    """

    def __init__(self, gateway: BinanceInverseGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceInverseGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.key: str = ""
        self.secret: bytes = b""
        self.proxy_port: int = 0
        self.proxy_host: str = ""
        self.server: str = ""

        self.reqid: int = 0
        self.order_count: int = 0
        self.order_prefix: str = ""

        self.reqid_callback_map: dict[int, Callable] = {}
        self.reqid_order_map: dict[int, OrderData] = {}

    def connect(
        self,
        key: str,
        secret: str,
        server: str,
        proxy_host: str,
        proxy_port: int
    ) -> None:
        """
        Start server connection.

        This method initializes the API credentials and establishes
        a websocket connection to Binance trading API.

        Parameters:
            key: API Key for authentication
            secret: API Secret for request signing
            server: Server type ("REAL" or "TESTNET")
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.key = key
        self.secret = secret.encode()
        self.proxy_port = proxy_port
        self.proxy_host = proxy_host
        self.server = server

        self.order_prefix = datetime.now().strftime("%y%m%d%H%M%S")

        if self.server == "REAL":
            self.init(REAL_TRADE_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_TRADE_HOST, proxy_host, proxy_port)

        self.start()

    def sign(self, params: dict) -> None:
        """
        Generate the signature for the request.

        This function creates an HMAC-SHA256 signature required for
        authenticated API requests to Binance.

        Parameters:
            params: Dictionary containing the parameters to be signed
        """
        timestamp: int = int(time.time() * 1000)
        params["timestamp"] = timestamp

        payload: str = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
        signature: str = hmac.new(
            self.secret,
            payload.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()
        params["signature"] = signature

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order to Binance.

        This function creates and sends a new order request to the exchange.
        It handles different order types including market, limit, and stop orders.

        Parameters:
            req: Order request object containing order details

        Returns:
            vt_orderid: The VeighNa order ID (gateway_name.orderid) if successful,
                       empty string otherwise
        """
        # Get contract
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to send order, symbol not found: {req.symbol}")
            return ""

        # Generate new order id
        self.order_count += 1
        orderid: str = self.order_prefix + str(self.order_count)

        # Push a submitting order event
        order: OrderData = req.create_order_data(
            orderid,
            self.gateway_name
        )
        self.gateway.on_order(order)

        # Create order parameters
        params: dict = {
            "apiKey": self.key,
            "symbol": contract.name,
            "side": DIRECTION_VT2BINANCE[req.direction],
            "quantity": format_float(req.volume),
            "newClientOrderId": orderid,
        }

        if req.type == OrderType.MARKET:
            params["type"] = "MARKET"
        elif req.type == OrderType.STOP:
            params["type"] = "STOP_MARKET"
            params["stopPrice"] = format_float(req.price)
        else:
            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]
            params["type"] = order_type
            params["timeInForce"] = time_condition
            params["price"] = format_float(req.price)

        self.sign(params)

        self.reqid += 1
        self.reqid_callback_map[self.reqid] = self.on_send_order
        self.reqid_order_map[self.reqid] = order

        packet: dict = {
            "id": self.reqid,
            "method": "order.place",
            "params": params,
        }
        self.send_packet(packet)
        return order.vt_orderid

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order on Binance.

        This function sends a request to cancel an existing order on the exchange.

        Parameters:
            req: Cancel request object containing order details
        """
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to cancel order, symbol not found: {req.symbol}")
            return

        params: dict = {
            "apiKey": self.key,
            "symbol": contract.name,
            "origClientOrderId": req.orderid
        }
        self.sign(params)

        self.reqid += 1
        self.reqid_callback_map[self.reqid] = self.on_cancel_order

        packet: dict = {
            "id": self.reqid,
            "method": "order.cancel",
            "params": params,
        }
        self.send_packet(packet)

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the trading websocket connection
        is successfully established. It logs the connection status.
        """
        self.gateway.write_log("Trade API connected")

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the trading websocket connection
        is closed. It logs the disconnection details.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"Trade API disconnected, code: {status_code}, msg: {msg}")

    def on_packet(self, packet: dict) -> None:
        """
        Callback of data update.

        This function processes responses from the trading websocket API.
        It routes the response to the appropriate callback function based
        on the request ID.

        Parameters:
            packet: JSON data received from websocket
        """
        reqid: int = packet.get("id", 0)
        callback: Callable | None = self.reqid_callback_map.get(reqid, None)
        if callback:
            callback(packet)

    def on_send_order(self, packet: dict) -> None:
        """
        Callback of send order.

        This function processes the response to an order placement request.
        It handles errors by logging the details and updating the order status.

        Parameters:
            packet: JSON data received from websocket
        """
        error: dict | None = packet.get("error", None)
        if not error:
            return

        error_code: str = error["code"]
        error_msg: str = error["msg"]
        msg: str = f"Order rejected, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

        reqid: int = packet.get("id", 0)
        order: OrderData | None = self.reqid_order_map.get(reqid, None)
        if order:
            order.status = Status.REJECTED
            self.gateway.on_order(order)

    def on_cancel_order(self, packet: dict) -> None:
        """
        Callback of cancel order.

        This function processes the response to an order cancellation request.
        It handles errors by logging the details.

        Parameters:
            packet: JSON data received from websocket
        """
        error: dict | None = packet.get("error", None)
        if not error:
            return

        error_code: str = error["code"]
        error_msg: str = error["msg"]
        msg: str = f"Cancel rejected, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the trading
        websocket connection. It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"Trade API exception: {e}")


def generate_datetime(timestamp: float) -> datetime:
    """
    Generate datetime object from Binance timestamp.

    This function converts a Binance millisecond timestamp to a datetime object
    with UTC timezone.

    Parameters:
        timestamp: Binance timestamp in milliseconds

    Returns:
        Datetime object with UTC timezone
    """
    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)
    return dt


def format_float(f: float) -> str:
    """
    Convert float number to string with correct precision.

    This function formats floating point numbers to avoid precision errors
    when sending requests to Binance.

    Parameters:
        f: The floating point number to format

    Returns:
        Formatted string representation of the number

    Note:
        Fixes potential error -1111: Parameter "quantity" has too much precision
    """
    return format_float_positional(f, trim="-")


================================================
FILE: vnpy_binance/linear_gateway.py
================================================
import hashlib
import hmac
import time
import urllib.parse
from copy import copy
from typing import Any
from collections.abc import Callable
from time import sleep
from datetime import datetime, timedelta

from numpy import format_float_positional

from vnpy.event import Event, EventEngine
from vnpy.trader.constant import (
    Direction,
    Exchange,
    Product,
    Status,
    OrderType,
    Interval
)
from vnpy.trader.gateway import BaseGateway
from vnpy.trader.object import (
    TickData,
    OrderData,
    TradeData,
    AccountData,
    ContractData,
    PositionData,
    BarData,
    OrderRequest,
    CancelRequest,
    SubscribeRequest,
    HistoryRequest
)
from vnpy.trader.event import EVENT_TIMER
from vnpy.trader.utility import round_to, ZoneInfo
from vnpy_rest import Request, RestClient, Response
from vnpy_websocket import WebsocketClient


# Timezone constant
UTC_TZ = ZoneInfo("UTC")

# Real server hosts
REAL_REST_HOST: str = "https://fapi.binance.com"
REAL_TRADE_HOST: str = "wss://ws-fapi.binance.com/ws-fapi/v1"
REAL_USER_HOST: str = "wss://fstream.binance.com/private/ws"
REAL_PUBLIC_HOST: str = "wss://fstream.binance.com/public/stream"
REAL_MARKET_HOST: str = "wss://fstream.binance.com/market/stream"

# Testnet server hosts
TESTNET_REST_HOST: str = "https://demo-fapi.binance.com"
TESTNET_TRADE_HOST: str = "wss://testnet.binancefuture.com/ws-fapi/v1"
TESTNET_USER_HOST: str = "wss://fstream.binancefuture.com/private/ws"
TESTNET_PUBLIC_HOST: str = "wss://fstream.binancefuture.com/public/stream"
TESTNET_MARKET_HOST: str = "wss://fstream.binancefuture.com/market/stream"

# Order status map
STATUS_BINANCE2VT: dict[str, Status] = {
    "NEW": Status.NOTTRADED,
    "PARTIALLY_FILLED": Status.PARTTRADED,
    "FILLED": Status.ALLTRADED,
    "CANCELED": Status.CANCELLED,
    "REJECTED": Status.REJECTED,
    "EXPIRED": Status.CANCELLED
}

# Order type map
ORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {
    OrderType.LIMIT: ("LIMIT", "GTC"),
    OrderType.MARKET: ("MARKET", "GTC"),
    OrderType.FAK: ("LIMIT", "IOC"),
    OrderType.FOK: ("LIMIT", "FOK"),
}
ORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCE.items()}

# Direction map
DIRECTION_VT2BINANCE: dict[Direction, str] = {
    Direction.LONG: "BUY",
    Direction.SHORT: "SELL"
}
DIRECTION_BINANCE2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCE.items()}

# Product map
PRODUCT_BINANCE2VT: dict[str, Product] = {
    "PERPETUAL": Product.SWAP,
    "PERPETUAL_DELIVERING": Product.SWAP,
    "TRADIFI_PERPETUAL": Product.SWAP,
    "CURRENT_MONTH": Product.FUTURES,
    "NEXT_MONTH": Product.FUTURES,
    "CURRENT_QUARTER": Product.FUTURES,
    "NEXT_QUARTER": Product.FUTURES,
}

# Kline interval map
INTERVAL_VT2BINANCE: dict[Interval, str] = {
    Interval.MINUTE: "1m",
    Interval.HOUR: "1h",
    Interval.DAILY: "1d",
}

# Timedelta map
TIMEDELTA_MAP: dict[Interval, timedelta] = {
    Interval.MINUTE: timedelta(minutes=1),
    Interval.HOUR: timedelta(hours=1),
    Interval.DAILY: timedelta(days=1),
}

# Set weboscket timeout to 24 hour
WEBSOCKET_TIMEOUT = 24 * 60 * 60


class BinanceLinearGateway(BaseGateway):
    """
    The Binance linear trading gateway for VeighNa.

    This gateway provides trading functionality for Binance USDT perpetual contracts
    and delivery futures through their API.

    Features:
    1. Only support crossed position
    2. Only support one-way mode
    3. Provides market data, trading, and account management capabilities
    """

    default_name: str = "BINANCE_LINEAR"

    default_setting: dict = {
        "API Key": "",
        "API Secret": "",
        "Server": ["REAL", "TESTNET"],
        "Kline Stream": ["False", "True"],
        "Proxy Host": "",
        "Proxy Port": 0
    }

    exchanges: list[Exchange] = [Exchange.GLOBAL]

    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
        """
        The init method of the gateway.

        This method initializes the gateway components including REST API,
        trading API, user data API, and market data API. It also sets up
        the data structures for order and contract storage.

        Parameters:
            event_engine: the global event engine object of VeighNa
            gateway_name: the unique name for identifying the gateway
        """
        super().__init__(event_engine, gateway_name)

        self.trade_api: TradeApi = TradeApi(self)
        self.user_api: UserApi = UserApi(self)
        self.md_api: MdApi = MdApi(self)
        self.rest_api: RestApi = RestApi(self)

        self.orders: dict[str, OrderData] = {}
        self.symbol_contract_map: dict[str, ContractData] = {}
        self.name_contract_map: dict[str, ContractData] = {}

    def connect(self, setting: dict) -> None:
        """
        Start server connections.

        This method establishes connections to Binance servers
        using the provided settings.

        Parameters:
            setting: A dictionary containing connection parameters including
                    API credentials, server selection, and proxy configuration
        """
        key: str = setting["API Key"]
        secret: str = setting["API Secret"]
        server: str = setting["Server"]
        kline_stream: bool = setting["Kline Stream"] == "True"
        proxy_host: str = setting["Proxy Host"]
        proxy_port: int = setting["Proxy Port"]

        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)
        self.trade_api.connect(key, secret, server, proxy_host, proxy_port)
        self.md_api.connect(server, kline_stream, proxy_host, proxy_port)

        self.event_engine.register(EVENT_TIMER, self.process_timer_event)

    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe to market data.

        This method forwards the subscription request to the market data API.

        Parameters:
            req: Subscription request object containing the symbol to subscribe
        """
        self.md_api.subscribe(req)

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order.

        This method forwards the order request to the trading API.

        Parameters:
            req: Order request object containing order details

        Returns:
            str: The VeighNa order ID if successful, empty string if failed
        """
        return self.trade_api.send_order(req)

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order.

        This method forwards the cancellation request to the trading API.

        Parameters:
            req: Cancel request object containing order details
        """
        self.trade_api.cancel_order(req)

    def query_account(self) -> None:
        """
        Query account balance.

        Not required since Binance provides websocket updates for account balances.
        """
        pass

    def query_position(self) -> None:
        """
        Query current positions.

        Not required since Binance provides websocket updates for positions.
        """
        pass

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """
        Query historical kline data.

        This method forwards the history request to the REST API.

        Parameters:
            req: History request object containing query parameters

        Returns:
            list[BarData]: List of historical kline data bars
        """
        return self.rest_api.query_history(req)

    def close(self) -> None:
        """
        Close server connections.

        This method stops all API connections and releases resources.
        """
        self.rest_api.stop()
        self.user_api.stop()
        self.md_api.stop()
        self.trade_api.stop()

    def process_timer_event(self, event: Event) -> None:
        """
        Process timer task.

        This function is called regularly by the event engine to perform scheduled tasks,
        such as keeping the user stream alive.

        Parameters:
            event: Timer event object
        """
        self.rest_api.keep_user_stream()

        self.md_api.subscribe_new_channels()

    def on_order(self, order: OrderData) -> None:
        """
        Save a copy of order and then push to event engine.

        Parameters:
            order: Order data object
        """
        self.orders[order.orderid] = copy(order)
        super().on_order(order)

    def get_order(self, orderid: str) -> OrderData | None:
        """
        Get previously saved order by order id.

        Parameters:
            orderid: The ID of the order to retrieve

        Returns:
            Order data object if found, None otherwise
        """
        return self.orders.get(orderid, None)

    def on_contract(self, contract: ContractData) -> None:
        """
        Save contract data in mappings and push to event engine.

        Parameters:
            contract: Contract data object
        """
        self.symbol_contract_map[contract.symbol] = contract
        self.name_contract_map[contract.name] = contract
        super().on_contract(contract)

    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
        """
        Get contract data by VeighNa symbol.

        Parameters:
            symbol: VeighNa symbol (e.g. "BTC_SWAP_BINANCE")

        Returns:
            Contract data object if found, None otherwise
        """
        return self.symbol_contract_map.get(symbol, None)

    def get_contract_by_name(self, name: str) -> ContractData | None:
        """
        Get contract data by exchange symbol name.

        Parameters:
            name: Exchange symbol name (e.g. "BTCUSDT")

        Returns:
            Contract data object if found, None otherwise
        """
        return self.name_contract_map.get(name, None)


class RestApi(RestClient):
    """
    The REST API of BinanceLinearGateway.

    This class handles HTTP requests to Binance API endpoints, including:
    - Authentication and signature generation
    - Contract information queries
    - Account and position queries
    - Order management
    - Historical data queries
    - User data stream management
    """

    def __init__(self, gateway: BinanceLinearGateway) -> None:
        """
        The init method of the API.

        This method initializes the REST API with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceLinearGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.user_api: UserApi = self.gateway.user_api

        self.key: str = ""
        self.secret: bytes = b""

        self.user_stream_key: str = ""
        self.keep_alive_count: int = 0
        self.time_offset: int = 0

        self.order_count: int = 1_000_000
        self.order_prefix: str = ""

    def sign(self, request: Request) -> Request:
        """
        Standard callback for signing a request.

        This method adds the necessary authentication parameters and signature
        to requests that require API key authentication.

        It handles:
        1. Path construction with query parameters
        2. Timestamp generation with server time offset adjustment
        3. HMAC-SHA256 signature generation
        4. Required authentication headers

        Parameters:
            request: Request object to be signed

        Returns:
            Request: Modified request with authentication parameters
        """
        # Construct path with query parameters if they exist
        if request.params:
            path: str = request.path + "?" + urllib.parse.urlencode(request.params)
        else:
            request.params = {}
            path = request.path

        # Get current timestamp in milliseconds
        timestamp: int = int(time.time() * 1000)

        # Adjust timestamp based on time offset with server
        if self.time_offset > 0:
            timestamp -= abs(self.time_offset)
        elif self.time_offset < 0:
            timestamp += abs(self.time_offset)

        # Add timestamp to request parameters
        request.params["timestamp"] = timestamp

        # Generate signature using HMAC SHA256
        query: str = urllib.parse.urlencode(sorted(request.params.items()))
        signature: str = hmac.new(
            self.secret,
            query.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()

        # Append signature to query string
        query += f"&signature={signature}"
        path = request.path + "?" + query

        # Update request with signed path and clear params/data
        request.path = path
        request.params = {}
        request.data = {}

        # Add required headers for API authentication
        request.headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
            "X-MBX-APIKEY": self.key,
            "Connection": "close"
        }

        return request

    def connect(
        self,
        key: str,
        secret: str,
        server: str,
        proxy_host: str,
        proxy_port: int
    ) -> None:
        """Start server connection"""
        self.key = key
        self.secret = secret.encode()
        self.proxy_port = proxy_port
        self.proxy_host = proxy_host
        self.server = server

        self.order_prefix = datetime.now().strftime("%y%m%d%H%M%S")

        if self.server == "REAL":
            self.init(REAL_REST_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)

        self.start()

        self.gateway.write_log("REST API started")

        self.query_time()

    def query_time(self) -> None:
        """
        Query server time to calculate local time offset.

        This function sends a request to get the exchange server time,
        which is used to calculate the local time offset for timestamp synchronization.
        """
        path: str = "/fapi/v1/time"

        self.add_request(
            "GET",
            path,
            callback=self.on_query_time
        )

    def query_account(self) -> None:
        """
        Query account balance.

        This function sends a request to get the account balance information,
        including wallet balance, available balance, and margin.
        """
        path: str = "/fapi/v3/account"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_account,
        )

    def query_position(self) -> None:
        """
        Query holding positions.

        This function sends a request to get current position data,
        including position amount, entry price, and unrealized profit/loss.
        """
        path: str = "/fapi/v3/positionRisk"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_position,
        )

    def query_order(self) -> None:
        """
        Query open orders.

        This function sends a request to get all active orders
        that have not been fully filled or cancelled.
        """
        path: str = "/fapi/v1/openOrders"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_order,
        )

    def query_contract(self) -> None:
        """
        Query available contracts.

        This function sends a request to get exchange information,
        including all available trading instruments, their precision,
        and trading rules.
        """
        path: str = "/fapi/v1/exchangeInfo"

        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_contract,
        )

    def start_user_stream(self) -> None:
        """
        Create listen key for user stream.

        This function sends a request to create a listen key which is
        required to establish a user data websocket connection.
        """
        path: str = "/fapi/v1/listenKey"

        self.add_request(
            method="POST",
            path=path,
            callback=self.on_start_user_stream,
        )

    def keep_user_stream(self) -> None:
        """
        Extend listen key validity.

        This function sends a request to keep the listen key active,
        which is required to maintain the user data websocket connection.
        The listen key will expire after 60 minutes if not refreshed.
        """
        if not self.user_stream_key:
            return

        self.keep_alive_count += 1
        if self.keep_alive_count < 600:
            return
        self.keep_alive_count = 0

        params: dict = {"listenKey": self.user_stream_key}

        path: str = "/fapi/v1/listenKey"

        self.add_request(
            method="PUT",
            path=path,
            callback=self.on_keep_user_stream,
            params=params,
            on_error=self.on_keep_user_stream_error
        )

    def on_query_time(self, data: dict, request: Request) -> None:
        """
        Callback of server time query.

        This function processes the server time response and calculates
        the time offset between local and server time, which is used for
        request timestamp synchronization.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        local_time: int = int(time.time() * 1000)
        server_time: int = int(data["serverTime"])
        self.time_offset = local_time - server_time

        self.gateway.write_log(f"Server time updated, local offset: {self.time_offset}ms")

        self.query_contract()

    def on_query_account(self, data: dict, request: Request) -> None:
        """
        Callback of account balance query.

        This function processes the account balance response and
        creates AccountData objects for each asset in the account.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for asset in data["assets"]:
            account: AccountData = AccountData(
                accountid=asset["asset"],
                balance=float(asset["walletBalance"]),
                frozen=float(asset["maintMargin"]),
                gateway_name=self.gateway_name
            )

            self.gateway.on_account(account)

        self.gateway.write_log("Account data received")

    def on_query_position(self, data: list, request: Request) -> None:
        """
        Callback of holding positions query.

        This function processes the position data response and
        creates PositionData objects for each position held.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data:
            name: str = d["symbol"]
            contract: ContractData | None = self.gateway.get_contract_by_name(name)
            if not contract:
                continue

            position: PositionData = PositionData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                direction=Direction.NET,
                volume=float(d["positionAmt"]),
                price=float(d["entryPrice"]),
                pnl=float(d["unRealizedProfit"]),
                gateway_name=self.gateway_name,
            )

            self.gateway.on_position(position)

        self.gateway.write_log("Position data received")

    def on_query_order(self, data: list, request: Request) -> None:
        """
        Callback of open orders query.

        This function processes the open orders response and
        creates OrderData objects for each active order.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data:
            key: tuple[str, str] = (d["type"], d["timeInForce"])
            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
            if not order_type:
                continue

            contract: ContractData | None = self.gateway.get_contract_by_symbol(d["symbol"])
            if not contract:
                continue

            order: OrderData = OrderData(
                orderid=d["clientOrderId"],
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                price=float(d["price"]),
                volume=float(d["origQty"]),
                type=order_type,
                direction=DIRECTION_BINANCE2VT[d["side"]],
                traded=float(d["executedQty"]),
                status=STATUS_BINANCE2VT[d["status"]],
                datetime=generate_datetime(d["time"]),
                gateway_name=self.gateway_name,
            )
            self.gateway.on_order(order)

        self.gateway.write_log("Order data received")

    def on_query_contract(self, data: dict, request: Request) -> None:
        """
        Callback of available contracts query.

        This function processes the exchange info response and
        creates ContractData objects for each trading instrument.
        It handles different contract types and extracts trading rules
        like price tick, minimum/maximum volumes from filters.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        for d in data["symbols"]:
            pricetick: float = 1
            min_volume: float = 1
            max_volume: float = 1

            for f in d["filters"]:
                if f["filterType"] == "PRICE_FILTER":
                    pricetick = float(f["tickSize"])
                elif f["filterType"] == "LOT_SIZE":
                    min_volume = float(f["minQty"])
                    max_volume = float(f["maxQty"])

            product: Product | None = PRODUCT_BINANCE2VT.get(d["contractType"], None)
            if product == Product.SWAP:
                symbol: str = d["symbol"] + "_SWAP_BINANCE"
            elif product == Product.FUTURES:
                symbol = d["symbol"] + "_BINANCE"
            else:
                continue

            contract: ContractData = ContractData(
                symbol=symbol,
                exchange=Exchange.GLOBAL,
                name=d["symbol"],
                pricetick=pricetick,
                size=1,
                min_volume=min_volume,
                max_volume=max_volume,
                product=product,
                net_position=True,
                history_data=True,
                gateway_name=self.gateway_name,
                stop_supported=False
            )
            self.gateway.on_contract(contract)

        self.gateway.write_log("Contract data received")

        # Query private data after time offset is calculated
        if self.key and self.secret:
            self.query_order()
            self.query_account()
            self.query_position()
            self.start_user_stream()

    def on_start_user_stream(self, data: dict, request: Request) -> None:
        """
        Successful callback of start_user_stream.

        This function processes the listen key response and initializes
        the user data websocket connection with the provided key.

        Parameters:
            data: Response data from the server containing the listen key
            request: Original request object
        """
        self.user_stream_key = data["listenKey"]
        self.keep_alive_count = 0

        params: str = urllib.parse.urlencode(
            {
                "listenKey": self.user_stream_key,
                "events": "ORDER_TRADE_UPDATE/ACCOUNT_UPDATE",
            },
            safe="/"
        )

        if self.server == "REAL":
            url = f"{REAL_USER_HOST}?{params}"
        else:
            url = f"{TESTNET_USER_HOST}?{params}"

        self.user_api.connect(url, self.proxy_host, self.proxy_port)

    def on_keep_user_stream(self, data: dict, request: Request) -> None:
        """
        Successful callback of keep_user_stream.

        This function handles the successful response of the listen key
        refresh request. No action is needed on success.

        Parameters:
            data: Response data from the server
            request: Original request object
        """
        pass

    def on_keep_user_stream_error(self, exception_type: type, exception_value: Exception, tb: Any, request: Request) -> None:
        """
        Error callback of keep_user_stream.

        This function handles errors from the listen key refresh request.
        Timeout exceptions are ignored as they are common and non-critical.

        Parameters:
            exception_type: Type of the exception
            exception_value: Exception instance
            tb: Traceback object
            request: Original request object
        """
        if not issubclass(exception_type, TimeoutError):        # Ignore timeout exception
            self.on_error(exception_type, exception_value, tb, request)

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """Query kline history data"""
        # Check if the contract and interval exist
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            return []

        if not req.interval:
            return []

        # Prepare history list
        history: list[BarData] = []
        limit: int = 1500

        # Convert start time to milliseconds
        start_time: int = int(datetime.timestamp(req.start))

        while True:
            # Create query parameters
            params: dict = {
                "symbol": contract.name,
                "interval": INTERVAL_VT2BINANCE[req.interval],
                "limit": limit
            }

            params["startTime"] = start_time * 1000
            path: str = "/fapi/v1/klines"
            if req.end:
                end_time = int(datetime.timestamp(req.end))
                params["endTime"] = end_time * 1000     # Convert to milliseconds

            resp: Response = self.request(
                "GET",
                path=path,
                params=params
            )

            # Break the loop if request failed
            if resp.status_code // 100 != 2:
                msg: str = f"Query kline history failed, status code: {resp.status_code}, message: {resp.text}"
                self.gateway.write_log(msg)
                break
            else:
                data: dict = resp.json()
                if not data:
                    msg = f"No kline history data is received, start time: {start_time}"
                    self.gateway.write_log(msg)
                    break

                buf: list[BarData] = []

                for row in data:
                    bar: BarData = BarData(
                        symbol=req.symbol,
                        exchange=req.exchange,
                        datetime=generate_datetime(row[0]),
                        interval=req.interval,
                        volume=float(row[5]),
                        turnover=float(row[7]),
                        open_price=float(row[1]),
                        high_price=float(row[2]),
                        low_price=float(row[3]),
                        close_price=float(row[4]),
                        gateway_name=self.gateway_name
                    )
                    bar.extra = {
                        "trade_count": int(row[8]),
                        "active_volume": float(row[9]),
                        "active_turnover": float(row[10]),
                    }
                    buf.append(bar)

                begin: datetime = buf[0].datetime
                end: datetime = buf[-1].datetime

                history.extend(buf)
                msg = f"Query kline history finished, {req.symbol} - {req.interval.value}, {begin} - {end}"
                self.gateway.write_log(msg)

                next_start_dt = bar.datetime + TIMEDELTA_MAP[req.interval]
                next_start_time = int(datetime.timestamp(next_start_dt))

                # Break the loop if the latest data received
                if (
                    len(data) < limit
                    or (req.end and next_start_dt >= req.end)
                ):
                    break

                # Update query start time
                start_time = next_start_time

            # Wait to meet request flow limit
            sleep(0.5)

        # Remove the unclosed kline
        if history:
            history.pop(-1)

        return history


class UserApi(WebsocketClient):
    """
    The user data websocket API of BinanceLinearGateway.

    This class handles user data events from Binance through websocket connection.
    It processes real-time updates for:
    - Account balance changes
    - Position updates
    - Order status changes
    - Trade executions
    """

    def __init__(self, gateway: BinanceLinearGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceLinearGateway = gateway
        self.gateway_name: str = gateway.gateway_name

    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
        """
        Start server connection.

        This method establishes a websocket connection to Binance user data stream.

        Parameters:
            url: Websocket endpoint URL with listen key
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        self.start()

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the websocket connection to the server
        is successfully established. It logs the connection status.
        """
        self.gateway.write_log("User API connected")

    def on_packet(self, packet: dict) -> None:
        """
        Callback of data update.

        This function processes websocket messages from the user data stream.
        It handles different event types including account updates, order updates,
        and listen key expiration.

        Parameters:
            packet: JSON data received from websocket
        """
        match packet["e"]:
            case "ACCOUNT_UPDATE":
                self.on_account(packet)
            case "ORDER_TRADE_UPDATE":
                self.on_order(packet)
            case "listenKeyExpired":
                self.on_listen_key_expired()

    def on_listen_key_expired(self) -> None:
        """
        Callback of listen key expired.

        This function is called when the exchange notifies that the listen key
        has expired. It will log a message and disconnect the websocket connection.
        """
        self.gateway.write_log("Listen key expired")

    def on_account(self, packet: dict) -> None:
        """
        Callback of account balance and holding position update.

        This function processes the account update event from the user data stream,
        including balance changes and position updates.

        Parameters:
            packet: JSON data received from websocket
        """
        for acc_data in packet["a"]["B"]:
            account: AccountData = AccountData(
                accountid=acc_data["a"],
                balance=float(acc_data["wb"]),
                frozen=float(acc_data["wb"]) - float(acc_data["cw"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        for pos_data in packet["a"]["P"]:
            if pos_data["ps"] == "BOTH":
                volume = pos_data["pa"]
                if "." in volume:
                    volume = float(volume)
                else:
                    volume = int(volume)

                name: str = pos_data["s"]
                contract: ContractData | None = self.gateway.get_contract_by_name(name)
                if not contract:
                    continue

                position: PositionData = PositionData(
                    symbol=contract.symbol,
                    exchange=Exchange.GLOBAL,
                    direction=Direction.NET,
                    volume=volume,
                    price=float(pos_data["ep"]),
                    pnl=float(pos_data["up"]),
                    gateway_name=self.gateway_name,
                )
                self.gateway.on_position(position)

    def on_order(self, packet: dict) -> None:
        """
        Callback of order and trade update.

        This function processes the order update event from the user data stream,
        including order status changes and trade executions.

        Parameters:
            packet: JSON data received from websocket
        """
        ord_data: dict = packet["o"]

        # Filter unsupported order type
        key: tuple[str, str] = (ord_data["o"], ord_data["f"])
        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
        if not order_type:
            return

        # Filter unsupported symbol
        name: str = ord_data["s"]
        contract: ContractData | None = self.gateway.get_contract_by_name(name)
        if not contract:
            return

        # Create and push order
        order: OrderData = OrderData(
            symbol=contract.symbol,
            exchange=Exchange.GLOBAL,
            orderid=str(ord_data["c"]),
            type=order_type,
            direction=DIRECTION_BINANCE2VT[ord_data["S"]],
            price=float(ord_data["p"]),
            volume=float(ord_data["q"]),
            traded=float(ord_data["z"]),
            status=STATUS_BINANCE2VT[ord_data["X"]],
            datetime=generate_datetime(packet["E"]),
            gateway_name=self.gateway_name,
        )

        self.gateway.on_order(order)

        # Round trade volume to meet step size
        trade_volume: float = float(ord_data["l"])
        trade_volume = round_to(trade_volume, contract.min_volume)
        if not trade_volume:
            return

        # Create and push trade
        trade: TradeData = TradeData(
            symbol=order.symbol,
            exchange=order.exchange,
            orderid=order.orderid,
            tradeid=ord_data["t"],
            direction=order.direction,
            price=float(ord_data["L"]),
            volume=trade_volume,
            datetime=generate_datetime(ord_data["T"]),
            gateway_name=self.gateway_name,
        )
        self.gateway.on_trade(trade)

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the websocket connection is closed.
        It logs the disconnection details and attempts to restart the user stream.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"User API disconnected, code: {status_code}, msg: {msg}")
        self.gateway.rest_api.start_user_stream()

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the websocket connection.
        It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"User API exception: {e}")


class MdApi(WebsocketClient):
    """
    The market data websocket API of BinanceLinearGateway.

    This class handles market data from Binance through websocket connection.
    It processes real-time updates for:
    - Tickers (24hr statistics)
    - Order book depth (10 levels)
    - Klines (candlestick data) if enabled
    """

    def __init__(self, gateway: BinanceLinearGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceLinearGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.ticks: dict[str, TickData] = {}
        self.public_api: PublicApi = PublicApi(self)
        self.reqid: int = 0
        self.kline_stream: bool = False

        self.new_public_channels: list[str] = []
        self.new_market_channels: list[str] = []

    def connect(
        self,
        server: str,
        kline_stream: bool,
        proxy_host: str,
        proxy_port: int,
    ) -> None:
        """
        Start server connection.

        This method establishes a websocket connection to Binance market data stream.

        Parameters:
            server: Server type ("REAL" or "TESTNET")
            kline_stream: Whether to include kline data stream
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.kline_stream = kline_stream

        if server == "REAL":
            self.init(REAL_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
            self.public_api.connect(REAL_PUBLIC_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
            self.public_api.connect(TESTNET_PUBLIC_HOST, proxy_host, proxy_port)

        self.start()

    def stop(self) -> None:
        """Stop market data websocket connections."""
        self.public_api.stop()
        super().stop()

    def get_public_channels(self, contract: ContractData) -> list[str]:
        """Generate public market data channels for a contract."""
        return [f"{contract.name.lower()}@depth10"]

    def get_market_channels(self, contract: ContractData) -> list[str]:
        """Generate market data channels for a contract."""
        channels: list[str] = [f"{contract.name.lower()}@ticker"]

        if self.kline_stream:
            channels.append(f"{contract.name.lower()}@kline_1m")

        if contract.product == Product.SWAP:
            channels.append(f"{contract.name.lower()}@markPrice")

        return channels

    def send_subscribe_packet(self, api: WebsocketClient, channels: list[str]) -> None:
        """Send a subscribe packet through the given websocket client."""
        if not channels:
            return

        self.reqid += 1
        packet: dict = {
            "method": "SUBSCRIBE",
            "params": channels,
            "id": self.reqid
        }
        api.send_packet(packet)

    def resubscribe_market_channels(self) -> None:
        """Resubscribe market channels after reconnect."""
        channels: list[str] = []

        for symbol in self.ticks.keys():
            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)
            if not contract:
                continue

            channels.extend(self.get_market_channels(contract))

        self.send_subscribe_packet(self, channels)

    def resubscribe_public_channels(self) -> None:
        """Resubscribe public channels after reconnect."""
        channels: list[str] = []

        for symbol in self.ticks.keys():
            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)
            if not contract:
                continue

            channels.extend(self.get_public_channels(contract))

        self.send_subscribe_packet(self.public_api, channels)

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the market data websocket connection
        is successfully established. It logs the connection status and
        resubscribes to previously subscribed market data channels.
        """
        self.gateway.write_log("MD API connected")

        # Resubscribe market data
        if self.ticks:
            self.resubscribe_market_channels()

    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe to market data.

        This function sends subscription requests for ticker and depth data
        for the specified trading instrument. If kline_stream is enabled,
        it will also subscribe to 1-minute kline data.

        Parameters:
            req: Subscription request object containing symbol information
        """
        if req.symbol in self.ticks:
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to subscribe data, symbol not found: {req.symbol}")
            return

        self.reqid += 1

        # Initialize tick object
        tick: TickData = TickData(
            symbol=req.symbol,
            name=contract.name,
            exchange=Exchange.GLOBAL,
            datetime=datetime.now(UTC_TZ),
            gateway_name=self.gateway_name,
        )
        tick.extra = {}
        self.ticks[req.symbol] = tick

        self.new_public_channels.extend(self.get_public_channels(contract))
        self.new_market_channels.extend(self.get_market_channels(contract))

    def subscribe_new_channels(self) -> None:
        """
        Update timer event.

        This function sends subscription requests for new channels
        to the market data websocket server.
        """
        self.send_subscribe_packet(self, self.new_market_channels)
        self.send_subscribe_packet(self.public_api, self.new_public_channels)

        self.new_market_channels = []
        self.new_public_channels = []

    def on_packet(self, packet: dict) -> None:
        """
        Callback of market data update.

        This function processes different types of market data updates,
        including ticker, depth, and kline data. It updates the corresponding
        TickData object and pushes updates to the gateway.

        Parameters:
            packet: JSON data received from websocket
        """
        stream: str | None = packet.get("stream", None)
        if not stream:
            return

        data: dict = packet["data"]

        name, channel = stream.split("@", 1)
        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper())
        if not contract:
            return
        tick: TickData = self.ticks[contract.symbol]

        if channel == "ticker":
            tick.volume = float(data["v"])
            tick.turnover = float(data["q"])
            tick.open_price = float(data["o"])
            tick.high_price = float(data["h"])
            tick.low_price = float(data["l"])
            tick.last_price = float(data["c"])
            tick.datetime = generate_datetime(float(data["E"]))
        elif channel == "depth10":
            bids: list = data["b"]
            for n in range(min(10, len(bids))):
                price, volume = bids[n]
                tick.__setattr__("bid_price_" + str(n + 1), float(price))
                tick.__setattr__("bid_volume_" + str(n + 1), float(volume))

            asks: list = data["a"]
            for n in range(min(10, len(asks))):
                price, volume = asks[n]
                tick.__setattr__("ask_price_" + str(n + 1), float(price))
                tick.__setattr__("ask_volume_" + str(n + 1), float(volume))
            tick.datetime = generate_datetime(float(data["E"]))
        elif channel == "markPrice":
            if tick.extra is None:
                tick.extra = {}
            tick.extra["funding_rate"] = float(data["r"])
            tick.extra["funding_time"] = int(data["T"])
        else:
            kline_data: dict = data["k"]

            # Check if bar is closed
            bar_ready: bool = kline_data.get("x", False)
            if not bar_ready:
                return

            if tick.extra is None:
                tick.extra = {}

            dt: datetime = generate_datetime(float(kline_data["t"]))

            tick.extra["bar"] = BarData(
                symbol=name.upper(),
                exchange=Exchange.GLOBAL,
                datetime=dt.replace(second=0, microsecond=0),
                interval=Interval.MINUTE,
                volume=float(kline_data["v"]),
                turnover=float(kline_data["q"]),
                open_price=float(kline_data["o"]),
                high_price=float(kline_data["h"]),
                low_price=float(kline_data["l"]),
                close_price=float(kline_data["c"]),
                gateway_name=self.gateway_name
            )

        if tick.last_price:
            tick.localtime = datetime.now()
            self.gateway.on_tick(copy(tick))

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the market data websocket connection
        is closed. It logs the disconnection details.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"MD API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the market data
        websocket connection. It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"MD API exception: {e}")


class PublicApi(WebsocketClient):
    """Public market data websocket connection for high-frequency streams."""

    def __init__(self, md_api: MdApi) -> None:
        super().__init__()
        self.md_api: MdApi = md_api
        self.gateway: BinanceLinearGateway = md_api.gateway

    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
        """Start public market data websocket connection."""
        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        self.start()

    def on_connected(self) -> None:
        """Callback when public websocket is connected."""
        self.gateway.write_log("Public API connected")
        if self.md_api.ticks:
            self.md_api.resubscribe_public_channels()

    def on_packet(self, packet: dict) -> None:
        """Forward packets to the shared market data parser."""
        self.md_api.on_packet(packet)

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when public websocket is disconnected."""
        self.gateway.write_log(f"MD Public API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """Callback when public websocket raises an exception."""
        self.gateway.write_log(f"MD Public API exception: {e}")


class TradeApi(WebsocketClient):
    """
    The trading websocket API of BinanceLinearGateway.

    This class handles trading operations with Binance through websocket connection.
    It provides functionality for:
    - Order placement
    - Order cancellation
    - Request authentication and signature generation
    """

    def __init__(self, gateway: BinanceLinearGateway) -> None:
        """
        The init method of the API.

        This method initializes the websocket client with a reference to the parent gateway.

        Parameters:
            gateway: the parent gateway object for pushing callback data
        """
        super().__init__()

        self.gateway: BinanceLinearGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.key: str = ""
        self.secret: bytes = b""
        self.proxy_port: int = 0
        self.proxy_host: str = ""
        self.server: str = ""

        self.reqid: int = 0
        self.order_count: int = 0
        self.order_prefix: str = ""

        self.reqid_callback_map: dict[int, Callable] = {}
        self.reqid_order_map: dict[int, OrderData] = {}

    def connect(
        self,
        key: str,
        secret: str,
        server: str,
        proxy_host: str,
        proxy_port: int
    ) -> None:
        """
        Start server connection.

        This method initializes the API credentials and establishes
        a websocket connection to Binance trading API.

        Parameters:
            key: API Key for authentication
            secret: API Secret for request signing
            server: Server type ("REAL" or "TESTNET")
            proxy_host: Proxy server hostname or IP
            proxy_port: Proxy server port
        """
        self.key = key
        self.secret = secret.encode()
        self.proxy_port = proxy_port
        self.proxy_host = proxy_host
        self.server = server

        self.order_prefix = datetime.now().strftime("%y%m%d%H%M%S")

        if self.server == "REAL":
            self.init(REAL_TRADE_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_TRADE_HOST, proxy_host, proxy_port)

        self.start()

    def sign(self, params: dict) -> None:
        """
        Generate the signature for the request.

        This function creates an HMAC-SHA256 signature required for
        authenticated API requests to Binance.

        Parameters:
            params: Dictionary containing the parameters to be signed
        """
        timestamp: int = int(time.time() * 1000)
        params["timestamp"] = timestamp

        payload: str = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
        signature: str = hmac.new(
            self.secret,
            payload.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()
        params["signature"] = signature

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order to Binance.

        This function creates and sends a new order request to the exchange.
        It handles different order types including market, limit, and stop orders.

        Parameters:
            req: Order request object containing order details

        Returns:
            vt_orderid: The VeighNa order ID (gateway_name.orderid) if successful,
                       empty string otherwise
        """
        # Get contract
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to send order, symbol not found: {req.symbol}")
            return ""

        # Generate new order id
        self.order_count += 1
        orderid: str = self.order_prefix + str(self.order_count)

        # Push a submitting order event
        order: OrderData = req.create_order_data(
            orderid,
            self.gateway_name
        )
        self.gateway.on_order(order)

        # Create order parameters
        params: dict = {
            "apiKey": self.key,
            "symbol": contract.name,
            "side": DIRECTION_VT2BINANCE[req.direction],
            "quantity": format_float(req.volume),
            "newClientOrderId": orderid,
        }

        if req.type == OrderType.MARKET:
            params["type"] = "MARKET"
        elif req.type == OrderType.STOP:
            params["type"] = "STOP_MARKET"
            params["stopPrice"] = format_float(req.price)
        else:
            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]
            params["type"] = order_type
            params["timeInForce"] = time_condition
            params["price"] = format_float(req.price)

        self.sign(params)

        self.reqid += 1
        self.reqid_callback_map[self.reqid] = self.on_send_order
        self.reqid_order_map[self.reqid] = order

        packet: dict = {
            "id": self.reqid,
            "method": "order.place",
            "params": params,
        }
        self.send_packet(packet)
        return order.vt_orderid

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order on Binance.

        This function sends a request to cancel an existing order on the exchange.

        Parameters:
            req: Cancel request object containing order details
        """
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to cancel order, symbol not found: {req.symbol}")
            return

        params: dict = {
            "apiKey": self.key,
            "symbol": contract.name,
            "origClientOrderId": req.orderid
        }
        self.sign(params)

        self.reqid += 1
        self.reqid_callback_map[self.reqid] = self.on_cancel_order

        packet: dict = {
            "id": self.reqid,
            "method": "order.cancel",
            "params": params,
        }
        self.send_packet(packet)

    def on_connected(self) -> None:
        """
        Callback when server is connected.

        This function is called when the trading websocket connection
        is successfully established. It logs the connection status.
        """
        self.gateway.write_log("Trade API connected")

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """
        Callback when server is disconnected.

        This function is called when the trading websocket connection
        is closed. It logs the disconnection details.

        Parameters:
            status_code: HTTP status code for the disconnection
            msg: Disconnection message
        """
        self.gateway.write_log(f"Trade API disconnected, code: {status_code}, msg: {msg}")

    def on_packet(self, packet: dict) -> None:
        """
        Callback of data update.

        This function processes responses from the trading websocket API.
        It routes the response to the appropriate callback function based
        on the request ID.

        Parameters:
            packet: JSON data received from websocket
        """
        reqid: int = packet.get("id", 0)
        callback: Callable | None = self.reqid_callback_map.get(reqid, None)
        if callback:
            callback(packet)

    def on_send_order(self, packet: dict) -> None:
        """
        Callback of send order.

        This function processes the response to an order placement request.
        It handles errors by logging the details and updating the order status.

        Parameters:
            packet: JSON data received from websocket
        """
        error: dict | None = packet.get("error", None)
        if not error:
            return

        error_code: str = error["code"]
        error_msg: str = error["msg"]
        msg: str = f"Order rejected, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

        reqid: int = packet.get("id", 0)
        order: OrderData | None = self.reqid_order_map.get(reqid, None)
        if order:
            order.status = Status.REJECTED
            self.gateway.on_order(order)

    def on_cancel_order(self, packet: dict) -> None:
        """
        Callback of cancel order.

        This function processes the response to an order cancellation request.
        It handles errors by logging the details.

        Parameters:
            packet: JSON data received from websocket
        """
        error: dict | None = packet.get("error", None)
        if not error:
            return

        error_code: str = error["code"]
        error_msg: str = error["msg"]
        msg: str = f"Cancel rejected, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

    def on_error(self, e: Exception) -> None:
        """
        Callback when exception raised.

        This function is called when an exception occurs in the trading
        websocket connection. It logs the exception details for troubleshooting.

        Parameters:
            e: The exception that was raised
        """
        self.gateway.write_log(f"Trade API exception: {e}")


def generate_datetime(timestamp: float) -> datetime:
    """
    Generate datetime object from Binance timestamp.

    This function converts a Binance millisecond timestamp to a datetime object
    with UTC timezone.

    Parameters:
        timestamp: Binance timestamp in milliseconds

    Returns:
        Datetime object with UTC timezone
    """
    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)
    return dt


def format_float(f: float) -> str:
    """
    Convert float number to string with correct precision.

    This function formats floating point numbers to avoid precision errors
    when sending requests to Binance.

    Parameters:
        f: The floating point number to format

    Returns:
        Formatted string representation of the number

    Note:
        Fixes potential error -1111: Parameter "quantity" has too much precision
    """
    return format_float_positional(f, trim="-")


================================================
FILE: vnpy_binance/portfolio_gateway.py
================================================
"""
Binance Portfolio Margin Gateway for VeighNa.

This module provides trading functionality for Binance Portfolio Margin account,
which supports unified account management across USDT-M futures, Coin-M futures,
and cross margin trading.
"""

import hashlib
import hmac
import time
import urllib.parse
from copy import copy
from enum import Enum
from typing import Any
from time import sleep
from datetime import datetime, timedelta

from numpy import format_float_positional

from vnpy.event import Event, EventEngine
from vnpy.trader.constant import (
    Direction,
    Exchange,
    Product,
    Status,
    OrderType,
    Interval
)
from vnpy.trader.gateway import BaseGateway
from vnpy.trader.object import (
    TickData,
    OrderData,
    TradeData,
    AccountData,
    ContractData,
    PositionData,
    BarData,
    OrderRequest,
    CancelRequest,
    SubscribeRequest,
    HistoryRequest
)
from vnpy.trader.event import EVENT_TIMER
from vnpy.trader.utility import round_to, ZoneInfo
from vnpy_rest import Request, RestClient, Response
from vnpy_websocket import WebsocketClient


# Timezone constant
UTC_TZ = ZoneInfo("UTC")

# Real server hosts
REAL_REST_HOST: str = "https://papi.binance.com"
REAL_UM_REST_HOST: str = "https://fapi.binance.com"
REAL_CM_REST_HOST: str = "https://dapi.binance.com"
REAL_MARGIN_REST_HOST: str = "https://api.binance.com"

REAL_USER_HOST: str = "wss://fstream.binance.com/pm/ws/"
REAL_UM_PUBLIC_HOST: str = "wss://fstream.binance.com/public/stream"
REAL_UM_MARKET_HOST: str = "wss://fstream.binance.com/market/stream"
REAL_CM_DATA_HOST: str = "wss://dstream.binance.com/stream"
REAL_MARGIN_DATA_HOST: str = "wss://stream.binance.com:9443/stream"

# Testnet server hosts
TESTNET_REST_HOST: str = "https://testnet.binancefuture.com"
TESTNET_UM_REST_HOST: str = "https://testnet.binancefuture.com"
TESTNET_CM_REST_HOST: str = "https://testnet.binancefuture.com"
TESTNET_MARGIN_REST_HOST: str = "https://testnet.binance.vision"

TESTNET_USER_HOST: str = "wss://stream.binancefuture.com/ws/"
TESTNET_UM_PUBLIC_HOST: str = "wss://fstream.binancefuture.com/public/stream"
TESTNET_UM_MARKET_HOST: str = "wss://fstream.binancefuture.com/market/stream"
TESTNET_CM_DATA_HOST: str = "wss://dstream.binancefuture.com/stream"
TESTNET_MARGIN_DATA_HOST: str = "wss://testnet.binance.vision/stream"

# Order status map
STATUS_BINANCE2VT: dict[str, Status] = {
    "NEW": Status.NOTTRADED,
    "PARTIALLY_FILLED": Status.PARTTRADED,
    "FILLED": Status.ALLTRADED,
    "CANCELED": Status.CANCELLED,
    "REJECTED": Status.REJECTED,
    "EXPIRED": Status.CANCELLED
}

# Order type map
ORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {
    OrderType.LIMIT: ("LIMIT", "GTC"),
    OrderType.MARKET: ("MARKET", "GTC"),
    OrderType.FAK: ("LIMIT", "IOC"),
    OrderType.FOK: ("LIMIT", "FOK"),
}
ORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {
    v: k for k, v in ORDERTYPE_VT2BINANCE.items()
}

# Direction map
DIRECTION_VT2BINANCE: dict[Direction, str] = {
    Direction.LONG: "BUY",
    Direction.SHORT: "SELL"
}
DIRECTION_BINANCE2VT: dict[str, Direction] = {
    v: k for k, v in DIRECTION_VT2BINANCE.items()
}

# Product map for futures
PRODUCT_BINANCE2VT: dict[str, Product] = {
    "PERPETUAL": Product.SWAP,
    "PERPETUAL_DELIVERING": Product.SWAP,
    "TRADIFI_PERPETUAL": Product.SWAP,
    "CURRENT_MONTH": Product.FUTURES,
    "NEXT_MONTH": Product.FUTURES,
    "CURRENT_QUARTER": Product.FUTURES,
    "NEXT_QUARTER": Product.FUTURES,
}

# Kline interval map
INTERVAL_VT2BINANCE: dict[Interval, str] = {
    Interval.MINUTE: "1m",
    Interval.HOUR: "1h",
    Interval.DAILY: "1d",
}

# Timedelta map
TIMEDELTA_MAP: dict[Interval, timedelta] = {
    Interval.MINUTE: timedelta(minutes=1),
    Interval.HOUR: timedelta(hours=1),
    Interval.DAILY: timedelta(days=1),
}

# Set websocket timeout to 24 hours
WEBSOCKET_TIMEOUT = 24 * 60 * 60


class MarketType(Enum):
    """Market type for portfolio margin account"""
    UM = "um"           # USDT-M Futures
    CM = "cm"           # Coin-M Futures
    MARGIN = "margin"   # Cross Margin


def get_market_type(symbol: str) -> MarketType:
    """
    Determine market type from symbol.

    Parameters:
        symbol: VeighNa symbol string

    Returns:
        MarketType enum value
    """
    if "_SWAP_BINANCE" in symbol:
        base = symbol.replace("_SWAP_BINANCE", "")
        if base.endswith("USDT") or base.endswith("USDC"):
            return MarketType.UM
        else:
            return MarketType.CM
    elif "_SPOT_BINANCE" in symbol:
        return MarketType.MARGIN
    elif "_BINANCE" in symbol:
        # Delivery futures
        base = symbol.split("_")[0]
        if "USDT" in base or "USDC" in base:
            return MarketType.UM
        else:
            return MarketType.CM
    return MarketType.UM


def generate_datetime(timestamp: float) -> datetime:
    """
    Generate datetime object from Binance timestamp.

    Parameters:
        timestamp: Binance timestamp in milliseconds

    Returns:
        Datetime object with UTC timezone
    """
    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)
    return dt


def format_float(f: float) -> str:
    """
    Convert float number to string with correct precision.

    Parameters:
        f: The floating point number to format

    Returns:
        Formatted string representation of the number
    """
    return format_float_positional(f, trim="-")


class BinancePortfolioGateway(BaseGateway):
    """
    The Binance portfolio margin trading gateway for VeighNa.

    This gateway provides unified trading functionality for Binance portfolio margin account,
    which supports USDT-M futures, Coin-M futures, and cross margin trading.

    Features:
    1. Unified account management across all markets
    2. Real-time market data with optional kline streaming
    3. Only support crossed position and one-way mode
    """

    default_name: str = "BINANCE_PORTFOLIO"

    default_setting: dict = {
        "API Key": "",
        "API Secret": "",
        "Server": ["REAL", "TESTNET"],
        "Kline Stream": ["False", "True"],
        "Proxy Host": "",
        "Proxy Port": 0
    }

    exchanges: list[Exchange] = [Exchange.GLOBAL]

    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
        """
        The init method of the gateway.

        Parameters:
            event_engine: the global event engine object of VeighNa
            gateway_name: the unique name for identifying the gateway
        """
        super().__init__(event_engine, gateway_name)

        self.user_api: UserApi = UserApi(self)
        self.um_md_api: UmMdApi = UmMdApi(self)
        self.cm_md_api: CmMdApi = CmMdApi(self)
        self.margin_md_api: MarginMdApi = MarginMdApi(self)
        self.rest_api: RestApi = RestApi(self)

        self.orders: dict[str, OrderData] = {}
        self.symbol_contract_map: dict[str, ContractData] = {}
        self.um_name_contract_map: dict[str, ContractData] = {}
        self.cm_name_contract_map: dict[str, ContractData] = {}
        self.margin_name_contract_map: dict[str, ContractData] = {}

    def connect(self, setting: dict) -> None:
        """
        Start server connections.

        Parameters:
            setting: A dictionary containing connection parameters
        """
        key: str = setting["API Key"]
        secret: str = setting["API Secret"]
        server: str = setting["Server"]
        kline_stream: bool = setting["Kline Stream"] == "True"
        proxy_host: str = setting["Proxy Host"]
        proxy_port: int = setting["Proxy Port"]

        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)
        self.um_md_api.connect(server, kline_stream, proxy_host, proxy_port)
        self.cm_md_api.connect(server, kline_stream, proxy_host, proxy_port)
        self.margin_md_api.connect(server, kline_stream, proxy_host, proxy_port)

        self.event_engine.register(EVENT_TIMER, self.process_timer_event)

    def subscribe(self, req: SubscribeRequest) -> None:
        """
        Subscribe to market data.

        Parameters:
            req: Subscription request object
        """
        market_type: MarketType = get_market_type(req.symbol)

        if market_type == MarketType.UM:
            self.um_md_api.subscribe(req)
        elif market_type == MarketType.CM:
            self.cm_md_api.subscribe(req)
        else:
            self.margin_md_api.subscribe(req)

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order.

        Parameters:
            req: Order request object

        Returns:
            str: The VeighNa order ID if successful, empty string if failed
        """
        return self.rest_api.send_order(req)

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order.

        Parameters:
            req: Cancel request object
        """
        self.rest_api.cancel_order(req)

    def query_account(self) -> None:
        """Query account balance."""
        pass

    def query_position(self) -> None:
        """Query current positions."""
        pass

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """
        Query historical kline data.

        Parameters:
            req: History request object

        Returns:
            list[BarData]: List of historical kline data bars
        """
        return self.rest_api.query_history(req)

    def close(self) -> None:
        """Close server connections."""
        self.rest_api.stop()
        self.user_api.stop()
        self.um_md_api.stop()
        self.cm_md_api.stop()
        self.margin_md_api.stop()

    def process_timer_event(self, event: Event) -> None:
        """
        Process timer task.

        Parameters:
            event: Timer event object
        """
        self.rest_api.keep_user_stream()

    def on_order(self, order: OrderData) -> None:
        """
        Save a copy of order and then push to event engine.

        Parameters:
            order: Order data object
        """
        self.orders[order.orderid] = copy(order)
        super().on_order(order)

    def get_order(self, orderid: str) -> OrderData | None:
        """
        Get previously saved order by order id.

        Parameters:
            orderid: The ID of the order to retrieve

        Returns:
            Order data object if found, None otherwise
        """
        return self.orders.get(orderid)

    def on_contract(self, contract: ContractData) -> None:
        """
        Save contract data in mappings and push to event engine.

        Parameters:
            contract: Contract data object
        """
        self.symbol_contract_map[contract.symbol] = contract

        market_type: MarketType = get_market_type(contract.symbol)
        if market_type == MarketType.UM:
            self.um_name_contract_map[contract.name] = contract
        elif market_type == MarketType.CM:
            self.cm_name_contract_map[contract.name] = contract
        else:
            self.margin_name_contract_map[contract.name] = contract

        super().on_contract(contract)

    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
        """
        Get contract data by VeighNa symbol.

        Parameters:
            symbol: VeighNa symbol

        Returns:
            Contract data object if found, None otherwise
        """
        return self.symbol_contract_map.get(symbol, None)

    def get_contract_by_name(self, name: str, market_type: MarketType) -> ContractData | None:
        """
        Get contract data by exchange symbol name and market type.

        Parameters:
            name: Exchange symbol name
            market_type: Market type for contract lookup

        Returns:
            Contract data object if found, None otherwise
        """
        if market_type == MarketType.UM:
            return self.um_name_contract_map.get(name, None)
        elif market_type == MarketType.CM:
            return self.cm_name_contract_map.get(name, None)
        else:
            return self.margin_name_contract_map.get(name, None)

    def get_futures_contract_by_name(self, name: str) -> ContractData | None:
        """
        Get futures contract data by exchange symbol name.

        Parameters:
            name: Exchange symbol name

        Returns:
            Contract data object if found, None otherwise
        """
        contract: ContractData | None = self.um_name_contract_map.get(name, None)
        if contract:
            return contract
        return self.cm_name_contract_map.get(name, None)


class RestApi(RestClient):
    """
    The REST API of BinancePortfolioGateway.

    This class handles HTTP requests to Binance API endpoints, including:
    - Authentication and signature generation
    - Contract information queries for all markets
    - Account and position queries
    - Order management via Portfolio Margin endpoints
    - Historical data queries
    - User data stream management
    """

    def __init__(self, gateway: BinancePortfolioGateway) -> None:
        """
        The init method of the API.

        Parameters:
            gateway: the parent gateway object
        """
        super().__init__()

        self.gateway: BinancePortfolioGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.user_api: UserApi = self.gateway.user_api

        self.key: str = ""
        self.secret: bytes = b""

        self.user_stream_key: str = ""
        self.keep_alive_count: int = 0
        self.time_offset: int = 0

        self.order_count: int = 1_000_000
        self.order_prefix: str = ""

        # Additional REST clients for exchange info queries
        self.um_client: RestClient | None = None
        self.cm_client: RestClient | None = None
        self.margin_client: RestClient | None = None

    def sign(self, request: Request) -> Request:
        """
        Standard callback for signing a request.

        Parameters:
            request: Request object to be signed

        Returns:
            Request: Modified request with authentication parameters
        """
        if request.params:
            path: str = request.path + "?" + urllib.parse.urlencode(request.params)
        else:
            request.params = {}
            path = request.path

        timestamp: int = int(time.time() * 1000)

        if self.time_offset > 0:
            timestamp -= abs(self.time_offset)
        elif self.time_offset < 0:
            timestamp += abs(self.time_offset)

        request.params["timestamp"] = timestamp

        query: str = urllib.parse.urlencode(sorted(request.params.items()))
        signature: str = hmac.new(
            self.secret,
            query.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()

        query += f"&signature={signature}"
        path = request.path + "?" + query

        request.path = path
        request.params = {}
        request.data = {}

        request.headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
            "X-MBX-APIKEY": self.key,
            "Connection": "close"
        }

        return request

    def connect(
        self,
        key: str,
        secret: str,
        server: str,
        proxy_host: str,
        proxy_port: int
    ) -> None:
        """Start server connection."""
        self.key = key
        self.secret = secret.encode()
        self.proxy_port = proxy_port
        self.proxy_host = proxy_host
        self.server = server

        self.order_prefix = datetime.now().strftime("%y%m%d%H%M%S")

        # Initialize main REST client (Portfolio Margin)
        if self.server == "REAL":
            self.init(REAL_REST_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)

        self.start()
        self.gateway.write_log("REST API started")

        # Initialize additional REST clients for exchange info
        self._init_market_clients(proxy_host, proxy_port)

        self.query_time()

    def _init_market_clients(self, proxy_host: str, proxy_port: int) -> None:
        """Initialize REST clients for each market's exchange info."""
        if self.server == "REAL":
            um_host = REAL_UM_REST_HOST
            cm_host = REAL_CM_REST_HOST
            margin_host = REAL_MARGIN_REST_HOST
        else:
            um_host = TESTNET_UM_REST_HOST
            cm_host = TESTNET_CM_REST_HOST
            margin_host = TESTNET_MARGIN_REST_HOST

        # UM client
        self.um_client = RestClient()
        self.um_client.init(um_host, proxy_host, proxy_port)
        self.um_client.start()

        # CM client
        self.cm_client = RestClient()
        self.cm_client.init(cm_host, proxy_host, proxy_port)
        self.cm_client.start()

        # Margin client
        self.margin_client = RestClient()
        self.margin_client.init(margin_host, proxy_host, proxy_port)
        self.margin_client.start()

    def query_time(self) -> None:
        """Query server time to calculate local time offset."""
        path: str = "/papi/v1/time"

        self.add_request(
            "GET",
            path,
            callback=self.on_query_time
        )

    def query_account(self) -> None:
        """Query account balance for all markets."""
        # Query UM account
        path: str = "/papi/v1/um/account"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_um_account,
        )

        # Query CM account
        path = "/papi/v1/cm/account"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_cm_account,
        )

        # Query margin balance
        path = "/papi/v1/balance"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_margin_account,
        )

    def query_position(self) -> None:
        """Query holding positions for futures markets."""
        # Query UM positions
        path: str = "/papi/v1/um/positionRisk"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_um_position,
        )

        # Query CM positions
        path = "/papi/v1/cm/positionRisk"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_cm_position,
        )

    def query_order(self) -> None:
        """Query open orders for all markets."""
        # Query UM orders
        path: str = "/papi/v1/um/openOrders"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_um_order,
        )

        # Query CM orders
        path = "/papi/v1/cm/openOrders"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_cm_order,
        )

        # Query margin orders
        path = "/papi/v1/margin/openOrders"
        self.add_request(
            method="GET",
            path=path,
            callback=self.on_query_margin_order,
        )

    def query_contract(self) -> None:
        """Query available contracts for all markets."""
        # Query UM contracts via fapi
        if self.um_client:
            resp: Response = self.um_client.request(
                "GET",
                "/fapi/v1/exchangeInfo"
            )
            if resp.status_code == 200:
                self.on_query_um_contract(resp.json())
            else:
                self.gateway.write_log(f"Query UM contract failed: {resp.text}")

        # Query CM contracts via dapi
        if self.cm_client:
            resp = self.cm_client.request(
                "GET",
                "/dapi/v1/exchangeInfo"
            )
            if resp.status_code == 200:
                self.on_query_cm_contract(resp.json())
            else:
                self.gateway.write_log(f"Query CM contract failed: {resp.text}")

        # Query margin contracts via spot api
        if self.margin_client:
            resp = self.margin_client.request(
                "GET",
                "/api/v3/exchangeInfo"
            )
            if resp.status_code == 200:
                self.on_query_margin_contract(resp.json())
            else:
                self.gateway.write_log(f"Query margin contract failed: {resp.text}")

    def send_order(self, req: OrderRequest) -> str:
        """
        Send new order via REST API.

        Parameters:
            req: Order request object

        Returns:
            vt_orderid: The VeighNa order ID if successful, empty string otherwise
        """
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to send order, symbol not found: {req.symbol}")
            return ""

        # Generate new order id
        self.order_count += 1
        orderid: str = self.order_prefix + str(self.order_count)

        # Push a submitting order event
        order: OrderData = req.create_order_data(
            orderid,
            self.gateway_name
        )
        self.gateway.on_order(order)

        # Create order parameters
        params: dict = {
            "symbol": contract.name,
            "side": DIRECTION_VT2BINANCE[req.direction],
            "quantity": format_float(req.volume),
            "newClientOrderId": orderid,
        }

        if req.type == OrderType.MARKET:
            params["type"] = "MARKET"
        else:
            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]
            params["type"] = order_type
            params["timeInForce"] = time_condition
            params["price"] = format_float(req.price)

        # Select endpoint based on market type
        market_type: MarketType = get_market_type(req.symbol)
        if market_type == MarketType.UM:
            path = "/papi/v1/um/order"
        elif market_type == MarketType.CM:
            path = "/papi/v1/cm/order"
        else:
            path = "/papi/v1/margin/order"

        self.add_request(
            method="POST",
            path=path,
            callback=self.on_send_order,
            params=params,
            extra=order,
            on_failed=self.on_send_order_failed,
            on_error=self.on_send_order_error
        )

        return order.vt_orderid

    def cancel_order(self, req: CancelRequest) -> None:
        """
        Cancel existing order.

        Parameters:
            req: Cancel request object
        """
        order: OrderData | None = self.gateway.get_order(req.orderid)
        if not order:
            self.gateway.write_log(f"Failed to cancel order, order not found: {req.orderid}")
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(order.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to cancel order, symbol not found: {order.symbol}")
            return

        params: dict = {
            "symbol": contract.name,
            "origClientOrderId": req.orderid
        }

        # Select endpoint based on market type
        market_type: MarketType = get_market_type(order.symbol)
        if market_type == MarketType.UM:
            path = "/papi/v1/um/order"
        elif market_type == MarketType.CM:
            path = "/papi/v1/cm/order"
        else:
            path = "/papi/v1/margin/order"

        self.add_request(
            method="DELETE",
            path=path,
            callback=self.on_cancel_order,
            params=params,
            extra=order,
            on_failed=self.on_cancel_order_failed
        )

    def start_user_stream(self) -> None:
        """Create listen key for user stream."""
        path: str = "/papi/v1/listenKey"

        self.add_request(
            method="POST",
            path=path,
            callback=self.on_start_user_stream,
        )

    def keep_user_stream(self) -> None:
        """Extend listen key validity."""
        if not self.user_stream_key:
            return

        self.keep_alive_count += 1
        if self.keep_alive_count < 600:
            return
        self.keep_alive_count = 0

        params: dict = {"listenKey": self.user_stream_key}
        path: str = "/papi/v1/listenKey"

        self.add_request(
            method="PUT",
            path=path,
            callback=self.on_keep_user_stream,
            params=params,
            on_error=self.on_keep_user_stream_error
        )

    def on_query_time(self, data: dict, request: Request) -> None:
        """Callback of server time query."""
        local_time: int = int(time.time() * 1000)
        server_time: int = int(data["serverTime"])
        self.time_offset = local_time - server_time

        self.gateway.write_log(f"Server time updated, local offset: {self.time_offset}ms")

        # Query contracts after time sync
        self.query_contract()

        # Query private data if authenticated
        if self.key and self.secret:
            self.query_order()
            self.query_account()
            self.query_position()
            self.start_user_stream()

    def on_query_um_account(self, data: dict, request: Request) -> None:
        """Callback of UM account balance query."""
        for asset in data["assets"]:
            account: AccountData = AccountData(
                accountid=asset["asset"] + "_UM",
                balance=float(asset["crossWalletBalance"]),
                frozen=float(asset["maintMargin"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        self.gateway.write_log("UM account data received")

    def on_query_cm_account(self, data: dict, request: Request) -> None:
        """Callback of CM account balance query."""
        for asset in data["assets"]:
            account: AccountData = AccountData(
                accountid=asset["asset"] + "_CM",
                balance=float(asset["crossWalletBalance"]),
                frozen=float(asset["maintMargin"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        self.gateway.write_log("CM account data received")

    def on_query_margin_account(self, data: list, request: Request) -> None:
        """Callback of margin account balance query."""
        for asset in data:
            account: AccountData = AccountData(
                accountid=asset["asset"] + "_MARGIN",
                balance=float(asset["crossMarginAsset"]),
                frozen=float(asset["crossMarginLocked"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        self.gateway.write_log("Margin account data received")

    def on_query_um_position(self, data: list, request: Request) -> None:
        """Callback of UM positions query."""
        for d in data:
            name: str = d["symbol"]
            contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.UM)
            if not contract:
                continue

            volume_str = d["positionAmt"]
            if "." in volume_str:
                volume = float(volume_str)
            else:
                volume = int(volume_str)

            position: PositionData = PositionData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                direction=Direction.NET,
                volume=volume,
                price=float(d["entryPrice"]),
                pnl=float(d["unRealizedProfit"]),
                gateway_name=self.gateway_name,
            )

            if position.volume:
                self.gateway.on_position(position)

        self.gateway.write_log("UM position data received")

    def on_query_cm_position(self, data: list, request: Request) -> None:
        """Callback of CM positions query."""
        for d in data:
            name: str = d["symbol"]
            contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.CM)
            if not contract:
                continue

            volume_str = d["positionAmt"]
            if "." in volume_str:
                volume = float(volume_str)
            else:
                volume = int(volume_str)

            position: PositionData = PositionData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                direction=Direction.NET,
                volume=volume,
                price=float(d["entryPrice"]),
                pnl=float(d["unRealizedProfit"]),
                gateway_name=self.gateway_name,
            )

            if position.volume:
                self.gateway.on_position(position)

        self.gateway.write_log("CM position data received")

    def on_query_um_order(self, data: list, request: Request) -> None:
        """Callback of UM open orders query."""
        for d in data:
            key: tuple[str, str] = (d["type"], d["timeInForce"])
            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
            if not order_type:
                continue

            contract: ContractData | None = self.gateway.get_contract_by_name(d["symbol"], MarketType.UM)
            if not contract:
                continue

            order: OrderData = OrderData(
                orderid=d["clientOrderId"],
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                price=float(d["price"]),
                volume=float(d["origQty"]),
                type=order_type,
                direction=DIRECTION_BINANCE2VT[d["side"]],
                traded=float(d["executedQty"]),
                status=STATUS_BINANCE2VT.get(d["status"], Status.SUBMITTING),
                datetime=generate_datetime(d["time"]),
                gateway_name=self.gateway_name,
            )
            self.gateway.on_order(order)

        self.gateway.write_log("UM order data received")

    def on_query_cm_order(self, data: list, request: Request) -> None:
        """Callback of CM open orders query."""
        for d in data:
            key: tuple[str, str] = (d["type"], d["timeInForce"])
            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
            if not order_type:
                continue

            contract: ContractData | None = self.gateway.get_contract_by_name(d["symbol"], MarketType.CM)
            if not contract:
                continue

            order: OrderData = OrderData(
                orderid=d["clientOrderId"],
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                price=float(d["price"]),
                volume=float(d["origQty"]),
                type=order_type,
                direction=DIRECTION_BINANCE2VT[d["side"]],
                traded=float(d["executedQty"]),
                status=STATUS_BINANCE2VT.get(d["status"], Status.SUBMITTING),
                datetime=generate_datetime(d["time"]),
                gateway_name=self.gateway_name,
            )
            self.gateway.on_order(order)

        self.gateway.write_log("CM order data received")

    def on_query_margin_order(self, data: list, request: Request) -> None:
        """Callback of margin open orders query."""
        for d in data:
            key: tuple[str, str] = (d["type"], d["timeInForce"])
            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
            if not order_type:
                continue

            contract: ContractData | None = self.gateway.get_contract_by_name(d["symbol"], MarketType.MARGIN)
            if not contract:
                continue

            order: OrderData = OrderData(
                orderid=d["clientOrderId"],
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                price=float(d["price"]),
                volume=float(d["origQty"]),
                type=order_type,
                direction=DIRECTION_BINANCE2VT[d["side"]],
                traded=float(d["executedQty"]),
                status=STATUS_BINANCE2VT.get(d["status"], Status.SUBMITTING),
                datetime=generate_datetime(d["time"]),
                gateway_name=self.gateway_name,
            )
            self.gateway.on_order(order)

        self.gateway.write_log("Margin order data received")

    def on_query_um_contract(self, data: dict) -> None:
        """Callback of UM contracts query."""
        for d in data["symbols"]:
            pricetick: float = 1
            min_volume: float = 1
            max_volume: float = 1

            for f in d["filters"]:
                if f["filterType"] == "PRICE_FILTER":
                    pricetick = float(f["tickSize"])
                elif f["filterType"] == "LOT_SIZE":
                    min_volume = float(f["minQty"])
                    max_volume = float(f["maxQty"])

            product: Product | None = PRODUCT_BINANCE2VT.get(d["contractType"], None)
            if product == Product.SWAP:
                symbol = d["symbol"] + "_SWAP_BINANCE"
            elif product == Product.FUTURES:
                symbol = d["symbol"] + "_BINANCE"
            else:
                continue

            contract: ContractData = ContractData(
                symbol=symbol,
                exchange=Exchange.GLOBAL,
                name=d["symbol"],
                pricetick=pricetick,
                size=1,
                min_volume=min_volume,
                max_volume=max_volume,
                product=product,
                net_position=True,
                history_data=True,
                gateway_name=self.gateway_name,
                stop_supported=False
            )
            self.gateway.on_contract(contract)

        self.gateway.write_log("UM contract data received")

    def on_query_cm_contract(self, data: dict) -> None:
        """Callback of CM contracts query."""
        for d in data["symbols"]:
            pricetick: float = 1
            min_volume: float = 1
            max_volume: float = 1

            for f in d["filters"]:
                if f["filterType"] == "PRICE_FILTER":
                    pricetick = float(f["tickSize"])
                elif f["filterType"] == "LOT_SIZE":
                    min_volume = float(f["minQty"])
                    max_volume = float(f["maxQty"])

            product: Product | None = PRODUCT_BINANCE2VT.get(d["contractType"], None)
            if product == Product.SWAP:
                symbol = d["symbol"].replace("_PERP", "") + "_SWAP_BINANCE"
            elif product == Product.FUTURES:
                symbol = d["symbol"] + "_BINANCE"
            else:
                continue

            contract: ContractData = ContractData(
                symbol=symbol,
                exchange=Exchange.GLOBAL,
                name=d["symbol"],
                pricetick=pricetick,
                size=1,
                min_volume=min_volume,
                max_volume=max_volume,
                product=product,
                net_position=True,
                history_data=True,
                gateway_name=self.gateway_name,
                stop_supported=False
            )
            self.gateway.on_contract(contract)

        self.gateway.write_log("CM contract data received")

    def on_query_margin_contract(self, data: dict) -> None:
        """Callback of margin contracts query."""
        for d in data["symbols"]:
            pricetick: float = 1
            min_volume: float = 1
            max_volume: float = 1

            for f in d["filters"]:
                if f["filterType"] == "PRICE_FILTER":
                    pricetick = float(f["tickSize"])
                elif f["filterType"] == "LOT_SIZE":
                    min_volume = float(f["minQty"])
                    max_volume = float(f["maxQty"])

            symbol = d["symbol"] + "_SPOT_BINANCE"

            contract: ContractData = ContractData(
                symbol=symbol,
                exchange=Exchange.GLOBAL,
                name=d["symbol"],
                pricetick=pricetick,
                size=1,
                min_volume=min_volume,
                max_volume=max_volume,
                product=Product.SPOT,
                net_position=True,
                history_data=True,
                gateway_name=self.gateway_name,
                stop_supported=False
            )
            self.gateway.on_contract(contract)

        self.gateway.write_log("Margin contract data received")

    def on_send_order(self, data: dict, request: Request) -> None:
        """Callback of send order."""
        pass

    def on_send_order_failed(self, status_code: int, request: Request) -> None:
        """Callback when send order failed."""
        order: OrderData = request.extra
        order.status = Status.REJECTED
        self.gateway.on_order(order)

        data: dict = request.response.json()
        error_code: int = data["code"]
        error_msg: str = data["msg"]
        msg: str = f"Order failed, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

    def on_send_order_error(
        self,
        exception_type: type,
        exception_value: Exception,
        tb: Any,
        request: Request
    ) -> None:
        """Callback when send order has error."""
        order: OrderData = request.extra
        order.status = Status.REJECTED
        self.gateway.on_order(order)

        if not issubclass(exception_type, ConnectionError | TimeoutError):
            self.on_error(exception_type, exception_value, tb, request)

    def on_cancel_order(self, data: dict, request: Request) -> None:
        """Callback of cancel order."""
        pass

    def on_cancel_order_failed(self, status_code: int, request: Request) -> None:
        """Callback when cancel order failed."""
        data: dict = request.response.json()
        error_code: int = data["code"]
        error_msg: str = data["msg"]
        msg: str = f"Cancel failed, code: {error_code}, message: {error_msg}"
        self.gateway.write_log(msg)

    def on_start_user_stream(self, data: dict, request: Request) -> None:
        """Callback of start user stream."""
        self.user_stream_key = data["listenKey"]
        self.keep_alive_count = 0

        if self.server == "REAL":
            url = REAL_USER_HOST + self.user_stream_key
        else:
            url = TESTNET_USER_HOST + self.user_stream_key

        self.user_api.connect(url, self.proxy_host, self.proxy_port)

    def on_keep_user_stream(self, data: dict, request: Request) -> None:
        """Callback of keep user stream."""
        pass

    def on_keep_user_stream_error(
        self,
        exception_type: type,
        exception_value: Exception,
        tb: Any,
        request: Request
    ) -> None:
        """Error callback of keep user stream."""
        if not issubclass(exception_type, TimeoutError):
            self.on_error(exception_type, exception_value, tb, request)

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """Query kline history data."""
        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            return []

        # Check interval
        if not req.interval:
            return []

        history: list[BarData] = []
        limit: int = 1500
        start_time: int = int(datetime.timestamp(req.start))

        # Select endpoint based on market type
        market_type: MarketType = get_market_type(req.symbol)
        if market_type == MarketType.UM:
            client = self.um_client
            path = "/fapi/v1/klines"
        elif market_type == MarketType.CM:
            client = self.cm_client
            path = "/dapi/v1/klines"
        else:
            client = self.margin_client
            path = "/api/v3/klines"
            limit = 1000

        if not client:
            return []

        while True:
            params: dict = {
                "symbol": contract.name,
                "interval": INTERVAL_VT2BINANCE[req.interval],
                "limit": limit,
                "startTime": start_time * 1000
            }

            if req.end:
                end_time = int(datetime.timestamp(req.end))
                params["endTime"] = end_time * 1000

            resp: Response = client.request(
                "GET",
                path=path,
                params=params
            )

            if resp.status_code // 100 != 2:
                msg: str = f"Query kline history failed, status code: {resp.status_code}, message: {resp.text}"
                self.gateway.write_log(msg)
                break
            else:
                data: list = resp.json()
                if not data:
                    msg = f"No kline history data received, start time: {start_time}"
                    self.gateway.write_log(msg)
                    break

                buf: list[BarData] = []

                for row in data:
                    bar: BarData = BarData(
                        symbol=req.symbol,
                        exchange=req.exchange,
                        datetime=generate_datetime(row[0]),
                        interval=req.interval,
                        volume=float(row[5]),
                        turnover=float(row[7]),
                        open_price=float(row[1]),
                        high_price=float(row[2]),
                        low_price=float(row[3]),
                        close_price=float(row[4]),
                        gateway_name=self.gateway_name
                    )
                    buf.append(bar)

                begin: datetime = buf[0].datetime
                end: datetime = buf[-1].datetime

                history.extend(buf)
                msg = f"Query kline history finished, {req.symbol} - {req.interval.value}, {begin} - {end}"
                self.gateway.write_log(msg)

                next_start_dt = bar.datetime + TIMEDELTA_MAP[req.interval]
                next_start_time = int(datetime.timestamp(next_start_dt))

                if len(data) < limit or (req.end and next_start_dt >= req.end):
                    break

                start_time = next_start_time

            sleep(0.5)

        if history:
            history.pop(-1)

        return history

    def stop(self) -> None:
        """Stop REST API and cleanup."""
        super().stop()

        if self.um_client:
            self.um_client.stop()
        if self.cm_client:
            self.cm_client.stop()
        if self.margin_client:
            self.margin_client.stop()


class UserApi(WebsocketClient):
    """
    The user data websocket API of BinancePortfolioGateway.

    Handles real-time updates for:
    - Account balance changes
    - Position updates
    - Order status changes
    - Trade executions
    """

    def __init__(self, gateway: BinancePortfolioGateway) -> None:
        """Initialize the API."""
        super().__init__()

        self.gateway: BinancePortfolioGateway = gateway
        self.gateway_name: str = gateway.gateway_name

    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
        """Start server connection."""
        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        self.start()

    def on_connected(self) -> None:
        """Callback when server is connected."""
        self.gateway.write_log("User API connected")

    def on_packet(self, packet: dict) -> None:
        """Callback of data update."""
        match packet["e"]:
            case "ACCOUNT_UPDATE":
                self.on_account(packet)
            case "ORDER_TRADE_UPDATE":
                self.on_order(packet)
            case "outboundAccountPosition":
                self.on_account_outbound(packet)
            case "executionReport":
                self.on_execution_report(packet)
            case "listenKeyExpired":
                self.on_listen_key_expired()

    def on_listen_key_expired(self) -> None:
        """Callback of listen key expired."""
        self.gateway.write_log("Listen key expired")
        self.disconnect()

    def on_account(self, packet: dict) -> None:
        """Callback of futures account update."""
        for acc_data in packet["a"]["B"]:
            account: AccountData = AccountData(
                accountid=acc_data["a"],
                balance=float(acc_data["wb"]),
                frozen=float(acc_data["wb"]) - float(acc_data["cw"]),
                gateway_name=self.gateway_name
            )

            if account.balance:
                self.gateway.on_account(account)

        for pos_data in packet["a"]["P"]:
            if pos_data["ps"] == "BOTH":
                volume = pos_data["pa"]
                if "." in volume:
                    volume = float(volume)
                else:
                    volume = int(volume)

                name: str = pos_data["s"]
                contract: ContractData | None = self.gateway.get_futures_contract_by_name(name)
                if not contract:
                    continue

                position: PositionData = PositionData(
                    symbol=contract.symbol,
                    exchange=Exchange.GLOBAL,
                    direction=Direction.NET,
                    volume=volume,
                    price=float(pos_data["ep"]),
                    pnl=float(pos_data["up"]),
                    gateway_name=self.gateway_name,
                )
                self.gateway.on_position(position)

    def on_account_outbound(self, packet: dict) -> None:
        """Callback of margin account balance update."""
        for acc_data in packet["B"]:
            free = float(acc_data["f"])
            locked = float(acc_data["l"])

            if free or locked:
                account: AccountData = AccountData(
                    accountid=acc_data["a"] + "_MARGIN",
                    balance=free + locked,
                    frozen=locked,
                    gateway_name=self.gateway_name
                )
                self.gateway.on_account(account)

    def on_order(self, packet: dict) -> None:
        """Callback of futures order update."""
        ord_data: dict = packet["o"]

        key: tuple[str, str] = (ord_data["o"], ord_data["f"])
        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
        if not order_type:
            return

        name: str = ord_data["s"]
        contract: ContractData | None = self.gateway.get_futures_contract_by_name(name)
        if not contract:
            return

        order: OrderData = OrderData(
            symbol=contract.symbol,
            exchange=Exchange.GLOBAL,
            orderid=str(ord_data["c"]),
            type=order_type,
            direction=DIRECTION_BINANCE2VT[ord_data["S"]],
            price=float(ord_data["p"]),
            volume=float(ord_data["q"]),
            traded=float(ord_data["z"]),
            status=STATUS_BINANCE2VT[ord_data["X"]],
            datetime=generate_datetime(packet["E"]),
            gateway_name=self.gateway_name,
        )

        self.gateway.on_order(order)

        trade_volume: float = float(ord_data["l"])
        trade_volume = round_to(trade_volume, contract.min_volume)
        if not trade_volume:
            return

        trade: TradeData = TradeData(
            symbol=order.symbol,
            exchange=order.exchange,
            orderid=order.orderid,
            tradeid=ord_data["t"],
            direction=order.direction,
            price=float(ord_data["L"]),
            volume=trade_volume,
            datetime=generate_datetime(ord_data["T"]),
            gateway_name=self.gateway_name,
        )
        self.gateway.on_trade(trade)

    def on_execution_report(self, packet: dict) -> None:
        """Callback of margin order update."""
        key: tuple[str, str] = (packet["o"], packet["f"])
        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)
        if not order_type:
            return

        name: str = packet["s"]
        contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.MARGIN)
        if not contract:
            return

        # Handle cancel order
        if packet["x"] == "CANCELED":
            orderid = packet["C"]
        else:
            orderid = packet["c"]

        order: OrderData = OrderData(
            symbol=contract.symbol,
            exchange=Exchange.GLOBAL,
            orderid=str(orderid),
            type=order_type,
            direction=DIRECTION_BINANCE2VT[packet["S"]],
            price=float(packet["p"]),
            volume=float(packet["q"]),
            traded=float(packet["z"]),
            status=STATUS_BINANCE2VT[packet["X"]],
            datetime=generate_datetime(packet["E"]),
            gateway_name=self.gateway_name,
        )

        self.gateway.on_order(order)

        trade_volume: float = float(packet["l"])
        trade_volume = round_to(trade_volume, contract.min_volume)
        if not trade_volume:
            return

        trade: TradeData = TradeData(
            symbol=order.symbol,
            exchange=order.exchange,
            orderid=order.orderid,
            tradeid=packet["t"],
            direction=order.direction,
            price=float(packet["L"]),
            volume=trade_volume,
            datetime=generate_datetime(packet["T"]),
            gateway_name=self.gateway_name,
        )
        self.gateway.on_trade(trade)

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when server is disconnected."""
        self.gateway.write_log(f"User API disconnected, code: {status_code}, msg: {msg}")
        self.gateway.rest_api.start_user_stream()

    def on_error(self, e: Exception) -> None:
        """Callback when exception raised."""
        self.gateway.write_log(f"User API exception: {e}")


class UmMdApi(WebsocketClient):
    """
    The USDT-M futures market data websocket API.

    Handles real-time updates for:
    - Tickers (24hr statistics)
    - Order book depth (10 levels)
    - Klines (candlestick data) if enabled
    """

    def __init__(self, gateway: BinancePortfolioGateway) -> None:
        """Initialize the API."""
        super().__init__()

        self.gateway: BinancePortfolioGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.ticks: dict[str, TickData] = {}
        self.public_api: UmPublicMdApi = UmPublicMdApi(self)
        self.reqid: int = 0
        self.kline_stream: bool = False

    def connect(
        self,
        server: str,
        kline_stream: bool,
        proxy_host: str,
        proxy_port: int,
    ) -> None:
        """Start server connection."""
        self.kline_stream = kline_stream

        if server == "REAL":
            self.init(REAL_UM_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
            self.public_api.connect(REAL_UM_PUBLIC_HOST, proxy_host, proxy_port)
        else:
            self.init(TESTNET_UM_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
            self.public_api.connect(TESTNET_UM_PUBLIC_HOST, proxy_host, proxy_port)

        self.start()

    def stop(self) -> None:
        """Stop market data websocket connections."""
        self.public_api.stop()
        super().stop()

    def get_public_channels(self, contract: ContractData) -> list[str]:
        """Generate public market data channels for a contract."""
        return [f"{contract.name.lower()}@depth10"]

    def get_market_channels(self, contract: ContractData) -> list[str]:
        """Generate market data channels for a contract."""
        channels: list[str] = [f"{contract.name.lower()}@ticker"]

        if self.kline_stream:
            channels.append(f"{contract.name.lower()}@kline_1m")

        return channels

    def send_subscribe_packet(self, api: WebsocketClient, channels: list[str]) -> None:
        """Send a subscribe packet through the given websocket client."""
        if not channels:
            return

        self.reqid += 1
        packet: dict = {
            "method": "SUBSCRIBE",
            "params": channels,
            "id": self.reqid
        }
        api.send_packet(packet)

    def resubscribe_market_channels(self) -> None:
        """Resubscribe market channels after reconnect."""
        channels: list[str] = []

        for symbol in self.ticks.keys():
            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)
            if not contract:
                continue

            channels.extend(self.get_market_channels(contract))

        self.send_subscribe_packet(self, channels)

    def resubscribe_public_channels(self) -> None:
        """Resubscribe public channels after reconnect."""
        channels: list[str] = []

        for symbol in self.ticks.keys():
            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)
            if not contract:
                continue

            channels.extend(self.get_public_channels(contract))

        self.send_subscribe_packet(self.public_api, channels)

    def on_connected(self) -> None:
        """Callback when server is connected."""
        self.gateway.write_log("UM MD API connected")

        if self.ticks:
            self.resubscribe_market_channels()

    def subscribe(self, req: SubscribeRequest) -> None:
        """Subscribe to market data."""
        if req.symbol in self.ticks:
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to subscribe data, symbol not found: {req.symbol}")
            return

        tick: TickData = TickData(
            symbol=req.symbol,
            name=contract.name,
            exchange=Exchange.GLOBAL,
            datetime=datetime.now(UTC_TZ),
            gateway_name=self.gateway_name,
        )
        tick.extra = {}
        self.ticks[req.symbol] = tick

        self.send_subscribe_packet(self, self.get_market_channels(contract))
        self.send_subscribe_packet(self.public_api, self.get_public_channels(contract))

    def on_packet(self, packet: dict) -> None:
        """Callback of market data update."""
        stream: str = packet.get("stream", "")
        if not stream:
            return

        data: dict = packet["data"]
        name, channel = stream.split("@", 1)

        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.UM)
        if not contract:
            return

        tick: TickData | None = self.ticks.get(contract.symbol)
        if not tick:
            return

        if channel == "ticker":
            tick.volume = float(data["v"])
            tick.turnover = float(data["q"])
            tick.open_price = float(data["o"])
            tick.high_price = float(data["h"])
            tick.low_price = float(data["l"])
            tick.last_price = float(data["c"])
            tick.datetime = generate_datetime(float(data["E"]))
        elif channel == "depth10":
            bids: list = data["b"]
            for n in range(min(10, len(bids))):
                price, volume = bids[n]
                tick.__setattr__("bid_price_" + str(n + 1), float(price))
                tick.__setattr__("bid_volume_" + str(n + 1), float(volume))

            asks: list = data["a"]
            for n in range(min(10, len(asks))):
                price, volume = asks[n]
                tick.__setattr__("ask_price_" + str(n + 1), float(price))
                tick.__setattr__("ask_volume_" + str(n + 1), float(volume))

            tick.datetime = generate_datetime(float(data["E"]))
        else:
            kline_data: dict = data["k"]
            bar_ready: bool = kline_data.get("x", False)
            if not bar_ready:
                return

            dt: datetime = generate_datetime(float(kline_data["t"]))

            if tick.extra is None:
                tick.extra = {}

            tick.extra["bar"] = BarData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                datetime=dt.replace(second=0, microsecond=0),
                interval=Interval.MINUTE,
                volume=float(kline_data["v"]),
                turnover=float(kline_data["q"]),
                open_price=float(kline_data["o"]),
                high_price=float(kline_data["h"]),
                low_price=float(kline_data["l"]),
                close_price=float(kline_data["c"]),
                gateway_name=self.gateway_name
            )

        if tick.last_price:
            tick.localtime = datetime.now()
            self.gateway.on_tick(copy(tick))

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when server is disconnected."""
        self.gateway.write_log(f"UM MD API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """Callback when exception raised."""
        self.gateway.write_log(f"UM MD API exception: {e}")


class UmPublicMdApi(WebsocketClient):
    """Public market data websocket connection for UM high-frequency streams."""

    def __init__(self, md_api: UmMdApi) -> None:
        """Initialize the API."""
        super().__init__()

        self.md_api: UmMdApi = md_api
        self.gateway: BinancePortfolioGateway = md_api.gateway

    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
        """Start public market data websocket connection."""
        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        self.start()

    def on_connected(self) -> None:
        """Callback when public websocket is connected."""
        self.gateway.write_log("UM Public API connected")
        if self.md_api.ticks:
            self.md_api.resubscribe_public_channels()

    def on_packet(self, packet: dict) -> None:
        """Forward packets to the shared UM market data parser."""
        self.md_api.on_packet(packet)

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when public websocket is disconnected."""
        self.gateway.write_log(f"UM Public API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """Callback when public websocket raises an exception."""
        self.gateway.write_log(f"UM Public API exception: {e}")


class CmMdApi(WebsocketClient):
    """
    The Coin-M futures market data websocket API.

    Handles real-time updates for:
    - Tickers (24hr statistics)
    - Order book depth (10 levels)
    - Klines (candlestick data) if enabled
    """

    def __init__(self, gateway: BinancePortfolioGateway) -> None:
        """Initialize the API."""
        super().__init__()

        self.gateway: BinancePortfolioGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.ticks: dict[str, TickData] = {}
        self.reqid: int = 0
        self.kline_stream: bool = False

    def connect(
        self,
        server: str,
        kline_stream: bool,
        proxy_host: str,
        proxy_port: int,
    ) -> None:
        """Start server connection."""
        self.kline_stream = kline_stream

        if server == "REAL":
            self.init(REAL_CM_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        else:
            self.init(TESTNET_CM_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)

        self.start()

    def on_connected(self) -> None:
        """Callback when server is connected."""
        self.gateway.write_log("CM MD API connected")

        if self.ticks:
            channels = []
            for symbol in self.ticks.keys():
                channels.append(f"{symbol}@ticker")
                channels.append(f"{symbol}@depth10")

                if self.kline_stream:
                    channels.append(f"{symbol}@kline_1m")

            packet: dict = {
                "method": "SUBSCRIBE",
                "params": channels,
                "id": self.reqid
            }
            self.send_packet(packet)

    def subscribe(self, req: SubscribeRequest) -> None:
        """Subscribe to market data."""
        if req.symbol in self.ticks:
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to subscribe data, symbol not found: {req.symbol}")
            return

        self.reqid += 1

        tick: TickData = TickData(
            symbol=req.symbol,
            name=contract.name,
            exchange=Exchange.GLOBAL,
            datetime=datetime.now(UTC_TZ),
            gateway_name=self.gateway_name,
        )
        tick.extra = {}
        self.ticks[req.symbol] = tick

        channels: list[str] = [
            f"{contract.name.lower()}@ticker",
            f"{contract.name.lower()}@depth10"
        ]

        if self.kline_stream:
            channels.append(f"{contract.name.lower()}@kline_1m")

        packet: dict = {
            "method": "SUBSCRIBE",
            "params": channels,
            "id": self.reqid
        }
        self.send_packet(packet)

    def on_packet(self, packet: dict) -> None:
        """Callback of market data update."""
        stream: str = packet.get("stream", "")
        if not stream:
            return

        data: dict = packet["data"]
        name, channel = stream.split("@")

        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.CM)
        if not contract:
            return

        tick: TickData | None = self.ticks.get(contract.symbol)
        if not tick:
            return

        if channel == "ticker":
            tick.volume = float(data["v"])
            tick.turnover = float(data["q"])
            tick.open_price = float(data["o"])
            tick.high_price = float(data["h"])
            tick.low_price = float(data["l"])
            tick.last_price = float(data["c"])
            tick.datetime = generate_datetime(float(data["E"]))
        elif channel == "depth10":
            bids: list = data["b"]
            for n in range(min(10, len(bids))):
                price, volume = bids[n]
                tick.__setattr__("bid_price_" + str(n + 1), float(price))
                tick.__setattr__("bid_volume_" + str(n + 1), float(volume))

            asks: list = data["a"]
            for n in range(min(10, len(asks))):
                price, volume = asks[n]
                tick.__setattr__("ask_price_" + str(n + 1), float(price))
                tick.__setattr__("ask_volume_" + str(n + 1), float(volume))

            tick.datetime = generate_datetime(float(data["E"]))
        else:
            kline_data: dict = data["k"]
            bar_ready: bool = kline_data.get("x", False)
            if not bar_ready:
                return

            dt: datetime = generate_datetime(float(kline_data["t"]))

            if tick.extra is None:
                tick.extra = {}

            tick.extra["bar"] = BarData(
                symbol=contract.symbol,
                exchange=Exchange.GLOBAL,
                datetime=dt.replace(second=0, microsecond=0),
                interval=Interval.MINUTE,
                volume=float(kline_data["v"]),
                turnover=float(kline_data["q"]),
                open_price=float(kline_data["o"]),
                high_price=float(kline_data["h"]),
                low_price=float(kline_data["l"]),
                close_price=float(kline_data["c"]),
                gateway_name=self.gateway_name
            )

        if tick.last_price:
            tick.localtime = datetime.now()
            self.gateway.on_tick(copy(tick))

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when server is disconnected."""
        self.gateway.write_log(f"CM MD API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """Callback when exception raised."""
        self.gateway.write_log(f"CM MD API exception: {e}")


class MarginMdApi(WebsocketClient):
    """
    The margin/spot market data websocket API.

    Handles real-time updates for:
    - Tickers (24hr statistics)
    - Book ticker (best bid/ask)
    - Klines (candlestick data) if enabled
    """

    def __init__(self, gateway: BinancePortfolioGateway) -> None:
        """Initialize the API."""
        super().__init__()

        self.gateway: BinancePortfolioGateway = gateway
        self.gateway_name: str = gateway.gateway_name

        self.ticks: dict[str, TickData] = {}
        self.reqid: int = 0
        self.kline_stream: bool = False

    def connect(
        self,
        server: str,
        kline_stream: bool,
        proxy_host: str,
        proxy_port: int,
    ) -> None:
        """Start server connection."""
        self.kline_stream = kline_stream

        if server == "REAL":
            self.init(REAL_MARGIN_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)
        else:
            self.init(TESTNET_MARGIN_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)

        self.start()

    def on_connected(self) -> None:
        """Callback when server is connected."""
        self.gateway.write_log("Margin MD API connected")

        if self.ticks:
            channels = []
            for symbol in self.ticks.keys():
                channels.append(f"{symbol}@ticker")
                channels.append(f"{symbol}@bookTicker")

                if self.kline_stream:
                    channels.append(f"{symbol}@kline_1m")

            packet: dict = {
                "method": "SUBSCRIBE",
                "params": channels,
                "id": self.reqid
            }
            self.send_packet(packet)

    def subscribe(self, req: SubscribeRequest) -> None:
        """Subscribe to market data."""
        if req.symbol in self.ticks:
            return

        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)
        if not contract:
            self.gateway.write_log(f"Failed to subscribe data, symbol not found: {req.symbol}")
            return

        self.reqid += 1

        tick: TickData = TickData(
            symbol=req.symbol,
            name=contract.name,
            exchange=Exchange.GLOBAL,
            datetime=datetime.now(UTC_TZ),
            gateway_name=self.gateway_name,
        )
        tick.extra = {}
        self.ticks[req.symbol] = tick

        channels: list[str] = [
            f"{contract.name.lower()}@ticker",
            f"{contract.name.lower()}@bookTicker"
        ]

        if self.kline_stream:
            channels.append(f"{contract.name.lower()}@kline_1m")

        packet: dict = {
            "method": "SUBSCRIBE",
            "params": channels,
            "id": self.reqid
        }
        self.send_packet(packet)

    def on_packet(self, packet: dict) -> None:
        """Callback of market data update."""
        # Handle stream format
        stream: str = packet.get("stream", "")
        if stream:
            data: dict = packet["data"]
            name, channel = stream.split("@")

            contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.MARGIN)
            if not contract:
                return

            tick: TickData | None = self.ticks.get(contract.symbol)
            if not tick:
                return

            if channel == "ticker":
                tick.volume = float(data["v"])
                tick.turnover = float(data["q"])
                tick.open_price = float(data["o"])
                tick.high_price = float(data["h"])
                tick.low_price = float(data["l"])
                tick.last_price = float(data["c"])
                tick.datetime = generate_datetime(float(data["E"]))
            elif channel == "bookTicker":
                tick.bid_price_1 = float(data["b"])
                tick.bid_volume_1 = float(data["B"])
                tick.ask_price_1 = float(data["a"])
                tick.ask_volume_1 = float(data["A"])

                tick.datetime = generate_datetime(float(data["E"]))
            elif channel.startswith("kline"):
                kline_data: dict = data["k"]
                bar_ready: bool = kline_data.get("x", False)
                if not bar_ready:
                    return

                dt: datetime = generate_datetime(float(kline_data["t"]))

                if tick.extra is None:
                    tick.extra = {}

                tick.extra["bar"] = BarData(
                    symbol=contract.symbol,
                    exchange=Exchange.GLOBAL,
                    datetime=dt.replace(second=0, microsecond=0),
                    interval=Interval.MINUTE,
                    volume=float(kline_data["v"]),
                    turnover=float(kline_data["q"]),
                    open_price=float(kline_data["o"]),
                    high_price=float(kline_data["h"]),
                    low_price=float(kline_data["l"]),
                    close_price=float(kline_data["c"]),
                    gateway_name=self.gateway_name
                )

            if tick.last_price:
                tick.localtime = datetime.now()
                self.gateway.on_tick(copy(tick))
            return

        # Handle non-stream format (bookTicker)
        symbol_name: str = packet.get("s", "")
        if not symbol_name:
            return

        contract = self.gateway.get_contract_by_name(symbol_name, MarketType.MARGIN)
        if not contract:
            return

        tick = self.ticks.get(contract.symbol)
        if not tick:
            return

        channel = packet.get("e", "bookTicker")

        if channel == "24hrTicker":
            tick.volume = float(packet["v"])
            tick.turnover = float(packet["q"])
            tick.open_price = float(packet["o"])
            tick.high_price = float(packet["h"])
            tick.low_price = float(packet["l"])
            tick.last_price = float(packet["c"])
            tick.datetime = generate_datetime(float(packet["E"]))
        elif channel == "bookTicker":
            tick.bid_price_1 = float(packet["b"])
            tick.bid_volume_1 = float(packet["B"])
            tick.ask_price_1 = float(packet["a"])
            tick.ask_volume_1 = float(packet["A"])

        if tick.last_price:
            tick.localtime = datetime.now()
            self.gateway.on_tick(copy(tick))

    def on_disconnected(self, status_code: int, msg: str) -> None:
        """Callback when server is disconnected."""
        self.gateway.write_log(f"Margin MD API disconnected, code: {status_code}, msg: {msg}")

    def on_error(self, e: Exception) -> None:
        """Callback when exception raised."""
        self.gateway.write_log(f"Margin MD API exception: {e}")



================================================
FILE: vnpy_binance/spot_gateway.py
================================================
import hashlib
import hmac
import time
import urllib.parse
from copy import copy
from collections.abc import Callable
from time import sleep
from datetime import datetime, timedelta

from numpy import format_float_positional

from vnpy.event import EventEngine, Event
from vnpy.trader.event import EVENT_TIMER
from vnpy.trader.constant import (
    Direction,
    Exchange,
    Product,
    Status,
    OrderType,
    Interval
)
from vnpy.trader.gateway import BaseGateway
from vnpy.trader.object import (
    TickData,
    OrderData,
    TradeData,
    AccountData,
    ContractData,
    BarData,
    OrderRequest,
    CancelRequest,
    SubscribeRequest,
    HistoryRequest
)
from vnpy.trader.utility import ZoneInfo
from vnpy_rest import Request, RestClient, Response
from vnpy_websocket import WebsocketClient


# Timezone constant
UTC_TZ = ZoneInfo("UTC")

# Real server hosts
REAL_REST_HOST: str = "https://api.binance.com"
REAL_TRADE_HOST: str = "wss://ws-api.binance.com/ws-api/v3"
REAL_DATA_HOST: str = "wss://stream.binance.com:443"

# Testnet server hosts
TESTNET_REST_HOST: str = "https://testnet.binance.vision"
TESTNET_TRADE_HOST: str = "wss://ws-api.testnet.binance.vision/ws-api/v3"
TESTNET_DATA_HOST: str = "wss://stream.testnet.binance.vision/ws"

# Order status map
STATUS_BINANCE2VT: dict[str, Status] = {
    "NEW": Status.NOTTRADED,
    "PARTIALLY_FILLED": Status.PARTTRADED,
    "FILLED": Status.ALLTRADED,
    "CANCELED": Status.CANCELLED,
    "REJECTED": Status.REJECTED,
    "EXPIRED": Status.CANCELLED
}

# Order type map
ORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {
    OrderType.LIMIT: ("LIMIT", "GTC"),
    OrderType.MARKET: ("MARKET", "GTC"),
    OrderType.FAK: ("LIMIT", "IOC"),
    OrderType.FOK: ("LIMIT", "FOK"),
}
ORDERTYPE_BINANCE2VT: dict[tuple[str, str], Or
Download .txt
gitextract_nwvrw0w8/

├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SUPPORT.md
│   └── workflows/
│       └── pythonapp.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pyproject.toml
├── script/
│   └── run.py
└── vnpy_binance/
    ├── __init__.py
    ├── inverse_gateway.py
    ├── linear_gateway.py
    ├── portfolio_gateway.py
    └── spot_gateway.py
Download .txt
SYMBOL INDEX (315 symbols across 5 files)

FILE: script/run.py
  function main (line 14) | def main() -> None:

FILE: vnpy_binance/inverse_gateway.py
  class BinanceInverseGateway (line 111) | class BinanceInverseGateway(BaseGateway):
    method __init__ (line 137) | def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
    method connect (line 160) | def connect(self, setting: dict) -> None:
    method subscribe (line 184) | def subscribe(self, req: SubscribeRequest) -> None:
    method send_order (line 195) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 209) | def cancel_order(self, req: CancelRequest) -> None:
    method query_account (line 220) | def query_account(self) -> None:
    method query_position (line 228) | def query_position(self) -> None:
    method query_history (line 236) | def query_history(self, req: HistoryRequest) -> list[BarData]:
    method close (line 250) | def close(self) -> None:
    method process_timer_event (line 261) | def process_timer_event(self, event: Event) -> None:
    method on_order (line 275) | def on_order(self, order: OrderData) -> None:
    method get_order (line 285) | def get_order(self, orderid: str) -> OrderData | None:
    method on_contract (line 297) | def on_contract(self, contract: ContractData) -> None:
    method get_contract_by_symbol (line 308) | def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
    method get_contract_by_name (line 320) | def get_contract_by_name(self, name: str) -> ContractData | None:
  class RestApi (line 333) | class RestApi(RestClient):
    method __init__ (line 346) | def __init__(self, gateway: BinanceInverseGateway) -> None:
    method sign (line 372) | def sign(self, request: Request) -> Request:
    method connect (line 437) | def connect(
    method query_time (line 465) | def query_time(self) -> None:
    method query_account (line 480) | def query_account(self) -> None:
    method query_position (line 495) | def query_position(self) -> None:
    method query_order (line 510) | def query_order(self) -> None:
    method query_contract (line 525) | def query_contract(self) -> None:
    method start_user_stream (line 541) | def start_user_stream(self) -> None:
    method keep_user_stream (line 556) | def keep_user_stream(self) -> None:
    method on_query_time (line 584) | def on_query_time(self, data: dict, request: Request) -> None:
    method on_query_account (line 604) | def on_query_account(self, data: dict, request: Request) -> None:
    method on_query_position (line 628) | def on_query_position(self, data: list, request: Request) -> None:
    method on_query_order (line 660) | def on_query_order(self, data: list, request: Request) -> None:
    method on_query_contract (line 698) | def on_query_contract(self, data: dict, request: Request) -> None:
    method on_start_user_stream (line 756) | def on_start_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream (line 777) | def on_keep_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream_error (line 790) | def on_keep_user_stream_error(self, exception_type: type, exception_va...
    method query_history (line 806) | def query_history(self, req: HistoryRequest) -> list[BarData]:
  class UserApi (line 918) | class UserApi(WebsocketClient):
    method __init__ (line 930) | def __init__(self, gateway: BinanceInverseGateway) -> None:
    method connect (line 944) | def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
    method on_connected (line 958) | def on_connected(self) -> None:
    method on_packet (line 967) | def on_packet(self, packet: dict) -> None:
    method on_listen_key_expired (line 986) | def on_listen_key_expired(self) -> None:
    method on_account (line 996) | def on_account(self, packet: dict) -> None:
    method on_order (line 1041) | def on_order(self, packet: dict) -> None:
    method on_disconnected (line 1102) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1116) | def on_error(self, e: Exception) -> None:
  class MdApi (line 1129) | class MdApi(WebsocketClient):
    method __init__ (line 1140) | def __init__(self, gateway: BinanceInverseGateway) -> None:
    method connect (line 1160) | def connect(
    method on_connected (line 1187) | def on_connected(self) -> None:
    method subscribe (line 1214) | def subscribe(self, req: SubscribeRequest) -> None:
    method subscribe_new_channels (line 1256) | def subscribe_new_channels(self) -> None:
    method on_packet (line 1275) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1350) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1363) | def on_error(self, e: Exception) -> None:
  class TradeApi (line 1376) | class TradeApi(WebsocketClient):
    method __init__ (line 1387) | def __init__(self, gateway: BinanceInverseGateway) -> None:
    method connect (line 1414) | def connect(
    method sign (line 1450) | def sign(self, params: dict) -> None:
    method send_order (line 1471) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 1536) | def cancel_order(self, req: CancelRequest) -> None:
    method on_connected (line 1567) | def on_connected(self) -> None:
    method on_disconnected (line 1576) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_packet (line 1589) | def on_packet(self, packet: dict) -> None:
    method on_send_order (line 1605) | def on_send_order(self, packet: dict) -> None:
    method on_cancel_order (line 1630) | def on_cancel_order(self, packet: dict) -> None:
    method on_error (line 1649) | def on_error(self, e: Exception) -> None:
  function generate_datetime (line 1662) | def generate_datetime(timestamp: float) -> datetime:
  function format_float (line 1679) | def format_float(f: float) -> str:

FILE: vnpy_binance/linear_gateway.py
  class BinanceLinearGateway (line 114) | class BinanceLinearGateway(BaseGateway):
    method __init__ (line 140) | def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
    method connect (line 163) | def connect(self, setting: dict) -> None:
    method subscribe (line 187) | def subscribe(self, req: SubscribeRequest) -> None:
    method send_order (line 198) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 212) | def cancel_order(self, req: CancelRequest) -> None:
    method query_account (line 223) | def query_account(self) -> None:
    method query_position (line 231) | def query_position(self) -> None:
    method query_history (line 239) | def query_history(self, req: HistoryRequest) -> list[BarData]:
    method close (line 253) | def close(self) -> None:
    method process_timer_event (line 264) | def process_timer_event(self, event: Event) -> None:
    method on_order (line 278) | def on_order(self, order: OrderData) -> None:
    method get_order (line 288) | def get_order(self, orderid: str) -> OrderData | None:
    method on_contract (line 300) | def on_contract(self, contract: ContractData) -> None:
    method get_contract_by_symbol (line 311) | def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
    method get_contract_by_name (line 323) | def get_contract_by_name(self, name: str) -> ContractData | None:
  class RestApi (line 336) | class RestApi(RestClient):
    method __init__ (line 349) | def __init__(self, gateway: BinanceLinearGateway) -> None:
    method sign (line 375) | def sign(self, request: Request) -> Request:
    method connect (line 440) | def connect(
    method query_time (line 468) | def query_time(self) -> None:
    method query_account (line 483) | def query_account(self) -> None:
    method query_position (line 498) | def query_position(self) -> None:
    method query_order (line 513) | def query_order(self) -> None:
    method query_contract (line 528) | def query_contract(self) -> None:
    method start_user_stream (line 544) | def start_user_stream(self) -> None:
    method keep_user_stream (line 559) | def keep_user_stream(self) -> None:
    method on_query_time (line 587) | def on_query_time(self, data: dict, request: Request) -> None:
    method on_query_account (line 607) | def on_query_account(self, data: dict, request: Request) -> None:
    method on_query_position (line 630) | def on_query_position(self, data: list, request: Request) -> None:
    method on_query_order (line 661) | def on_query_order(self, data: list, request: Request) -> None:
    method on_query_contract (line 699) | def on_query_contract(self, data: dict, request: Request) -> None:
    method on_start_user_stream (line 757) | def on_start_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream (line 786) | def on_keep_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream_error (line 799) | def on_keep_user_stream_error(self, exception_type: type, exception_va...
    method query_history (line 815) | def query_history(self, req: HistoryRequest) -> list[BarData]:
  class UserApi (line 917) | class UserApi(WebsocketClient):
    method __init__ (line 929) | def __init__(self, gateway: BinanceLinearGateway) -> None:
    method connect (line 943) | def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
    method on_connected (line 957) | def on_connected(self) -> None:
    method on_packet (line 966) | def on_packet(self, packet: dict) -> None:
    method on_listen_key_expired (line 985) | def on_listen_key_expired(self) -> None:
    method on_account (line 994) | def on_account(self, packet: dict) -> None:
    method on_order (line 1039) | def on_order(self, packet: dict) -> None:
    method on_disconnected (line 1100) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1114) | def on_error(self, e: Exception) -> None:
  class MdApi (line 1127) | class MdApi(WebsocketClient):
    method __init__ (line 1138) | def __init__(self, gateway: BinanceLinearGateway) -> None:
    method connect (line 1160) | def connect(
    method stop (line 1189) | def stop(self) -> None:
    method get_public_channels (line 1194) | def get_public_channels(self, contract: ContractData) -> list[str]:
    method get_market_channels (line 1198) | def get_market_channels(self, contract: ContractData) -> list[str]:
    method send_subscribe_packet (line 1210) | def send_subscribe_packet(self, api: WebsocketClient, channels: list[s...
    method resubscribe_market_channels (line 1223) | def resubscribe_market_channels(self) -> None:
    method resubscribe_public_channels (line 1236) | def resubscribe_public_channels(self) -> None:
    method on_connected (line 1249) | def on_connected(self) -> None:
    method subscribe (line 1263) | def subscribe(self, req: SubscribeRequest) -> None:
    method subscribe_new_channels (line 1298) | def subscribe_new_channels(self) -> None:
    method on_packet (line 1311) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1391) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1404) | def on_error(self, e: Exception) -> None:
  class PublicApi (line 1417) | class PublicApi(WebsocketClient):
    method __init__ (line 1420) | def __init__(self, md_api: MdApi) -> None:
    method connect (line 1425) | def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
    method on_connected (line 1430) | def on_connected(self) -> None:
    method on_packet (line 1436) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1440) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1444) | def on_error(self, e: Exception) -> None:
  class TradeApi (line 1449) | class TradeApi(WebsocketClient):
    method __init__ (line 1460) | def __init__(self, gateway: BinanceLinearGateway) -> None:
    method connect (line 1487) | def connect(
    method sign (line 1523) | def sign(self, params: dict) -> None:
    method send_order (line 1544) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 1609) | def cancel_order(self, req: CancelRequest) -> None:
    method on_connected (line 1640) | def on_connected(self) -> None:
    method on_disconnected (line 1649) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_packet (line 1662) | def on_packet(self, packet: dict) -> None:
    method on_send_order (line 1678) | def on_send_order(self, packet: dict) -> None:
    method on_cancel_order (line 1703) | def on_cancel_order(self, packet: dict) -> None:
    method on_error (line 1722) | def on_error(self, e: Exception) -> None:
  function generate_datetime (line 1735) | def generate_datetime(timestamp: float) -> datetime:
  function format_float (line 1752) | def format_float(f: float) -> str:

FILE: vnpy_binance/portfolio_gateway.py
  class MarketType (line 136) | class MarketType(Enum):
  function get_market_type (line 143) | def get_market_type(symbol: str) -> MarketType:
  function generate_datetime (line 171) | def generate_datetime(timestamp: float) -> datetime:
  function format_float (line 185) | def format_float(f: float) -> str:
  class BinancePortfolioGateway (line 198) | class BinancePortfolioGateway(BaseGateway):
    method __init__ (line 224) | def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
    method connect (line 246) | def connect(self, setting: dict) -> None:
    method subscribe (line 267) | def subscribe(self, req: SubscribeRequest) -> None:
    method send_order (line 283) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 295) | def cancel_order(self, req: CancelRequest) -> None:
    method query_account (line 304) | def query_account(self) -> None:
    method query_position (line 308) | def query_position(self) -> None:
    method query_history (line 312) | def query_history(self, req: HistoryRequest) -> list[BarData]:
    method close (line 324) | def close(self) -> None:
    method process_timer_event (line 332) | def process_timer_event(self, event: Event) -> None:
    method on_order (line 341) | def on_order(self, order: OrderData) -> None:
    method get_order (line 351) | def get_order(self, orderid: str) -> OrderData | None:
    method on_contract (line 363) | def on_contract(self, contract: ContractData) -> None:
    method get_contract_by_symbol (line 382) | def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
    method get_contract_by_name (line 394) | def get_contract_by_name(self, name: str, market_type: MarketType) -> ...
    method get_futures_contract_by_name (line 412) | def get_futures_contract_by_name(self, name: str) -> ContractData | None:
  class RestApi (line 428) | class RestApi(RestClient):
    method __init__ (line 441) | def __init__(self, gateway: BinancePortfolioGateway) -> None:
    method sign (line 470) | def sign(self, request: Request) -> Request:
    method connect (line 518) | def connect(
    method _init_market_clients (line 549) | def _init_market_clients(self, proxy_host: str, proxy_port: int) -> None:
    method query_time (line 575) | def query_time(self) -> None:
    method query_account (line 585) | def query_account(self) -> None:
    method query_position (line 611) | def query_position(self) -> None:
    method query_order (line 629) | def query_order(self) -> None:
    method query_contract (line 655) | def query_contract(self) -> None:
    method send_order (line 690) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 753) | def cancel_order(self, req: CancelRequest) -> None:
    method start_user_stream (line 793) | def start_user_stream(self) -> None:
    method keep_user_stream (line 803) | def keep_user_stream(self) -> None:
    method on_query_time (line 824) | def on_query_time(self, data: dict, request: Request) -> None:
    method on_query_um_account (line 842) | def on_query_um_account(self, data: dict, request: Request) -> None:
    method on_query_cm_account (line 857) | def on_query_cm_account(self, data: dict, request: Request) -> None:
    method on_query_margin_account (line 872) | def on_query_margin_account(self, data: list, request: Request) -> None:
    method on_query_um_position (line 887) | def on_query_um_position(self, data: list, request: Request) -> None:
    method on_query_cm_position (line 916) | def on_query_cm_position(self, data: list, request: Request) -> None:
    method on_query_um_order (line 945) | def on_query_um_order(self, data: list, request: Request) -> None:
    method on_query_cm_order (line 974) | def on_query_cm_order(self, data: list, request: Request) -> None:
    method on_query_margin_order (line 1003) | def on_query_margin_order(self, data: list, request: Request) -> None:
    method on_query_um_contract (line 1032) | def on_query_um_contract(self, data: dict) -> None:
    method on_query_cm_contract (line 1072) | def on_query_cm_contract(self, data: dict) -> None:
    method on_query_margin_contract (line 1112) | def on_query_margin_contract(self, data: dict) -> None:
    method on_send_order (line 1146) | def on_send_order(self, data: dict, request: Request) -> None:
    method on_send_order_failed (line 1150) | def on_send_order_failed(self, status_code: int, request: Request) -> ...
    method on_send_order_error (line 1162) | def on_send_order_error(
    method on_cancel_order (line 1177) | def on_cancel_order(self, data: dict, request: Request) -> None:
    method on_cancel_order_failed (line 1181) | def on_cancel_order_failed(self, status_code: int, request: Request) -...
    method on_start_user_stream (line 1189) | def on_start_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream (line 1201) | def on_keep_user_stream(self, data: dict, request: Request) -> None:
    method on_keep_user_stream_error (line 1205) | def on_keep_user_stream_error(
    method query_history (line 1216) | def query_history(self, req: HistoryRequest) -> list[BarData]:
    method stop (line 1315) | def stop(self) -> None:
  class UserApi (line 1327) | class UserApi(WebsocketClient):
    method __init__ (line 1338) | def __init__(self, gateway: BinancePortfolioGateway) -> None:
    method connect (line 1345) | def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
    method on_connected (line 1350) | def on_connected(self) -> None:
    method on_packet (line 1354) | def on_packet(self, packet: dict) -> None:
    method on_listen_key_expired (line 1368) | def on_listen_key_expired(self) -> None:
    method on_account (line 1373) | def on_account(self, packet: dict) -> None:
    method on_account_outbound (line 1410) | def on_account_outbound(self, packet: dict) -> None:
    method on_order (line 1425) | def on_order(self, packet: dict) -> None:
    method on_execution_report (line 1473) | def on_execution_report(self, packet: dict) -> None:
    method on_disconnected (line 1525) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1530) | def on_error(self, e: Exception) -> None:
  class UmMdApi (line 1535) | class UmMdApi(WebsocketClient):
    method __init__ (line 1545) | def __init__(self, gateway: BinancePortfolioGateway) -> None:
    method connect (line 1557) | def connect(
    method stop (line 1576) | def stop(self) -> None:
    method get_public_channels (line 1581) | def get_public_channels(self, contract: ContractData) -> list[str]:
    method get_market_channels (line 1585) | def get_market_channels(self, contract: ContractData) -> list[str]:
    method send_subscribe_packet (line 1594) | def send_subscribe_packet(self, api: WebsocketClient, channels: list[s...
    method resubscribe_market_channels (line 1607) | def resubscribe_market_channels(self) -> None:
    method resubscribe_public_channels (line 1620) | def resubscribe_public_channels(self) -> None:
    method on_connected (line 1633) | def on_connected(self) -> None:
    method subscribe (line 1640) | def subscribe(self, req: SubscribeRequest) -> None:
    method on_packet (line 1663) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1731) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1735) | def on_error(self, e: Exception) -> None:
  class UmPublicMdApi (line 1740) | class UmPublicMdApi(WebsocketClient):
    method __init__ (line 1743) | def __init__(self, md_api: UmMdApi) -> None:
    method connect (line 1750) | def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:
    method on_connected (line 1755) | def on_connected(self) -> None:
    method on_packet (line 1761) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1765) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1769) | def on_error(self, e: Exception) -> None:
  class CmMdApi (line 1774) | class CmMdApi(WebsocketClient):
    method __init__ (line 1784) | def __init__(self, gateway: BinancePortfolioGateway) -> None:
    method connect (line 1795) | def connect(
    method on_connected (line 1812) | def on_connected(self) -> None:
    method subscribe (line 1832) | def subscribe(self, req: SubscribeRequest) -> None:
    method on_packet (line 1869) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 1937) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 1941) | def on_error(self, e: Exception) -> None:
  class MarginMdApi (line 1946) | class MarginMdApi(WebsocketClient):
    method __init__ (line 1956) | def __init__(self, gateway: BinancePortfolioGateway) -> None:
    method connect (line 1967) | def connect(
    method on_connected (line 1984) | def on_connected(self) -> None:
    method subscribe (line 2004) | def subscribe(self, req: SubscribeRequest) -> None:
    method on_packet (line 2041) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 2135) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 2139) | def on_error(self, e: Exception) -> None:

FILE: vnpy_binance/spot_gateway.py
  class BinanceSpotGateway (line 97) | class BinanceSpotGateway(BaseGateway):
    method __init__ (line 123) | def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
    method connect (line 145) | def connect(self, setting: dict) -> None:
    method subscribe (line 169) | def subscribe(self, req: SubscribeRequest) -> None:
    method send_order (line 180) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 194) | def cancel_order(self, req: CancelRequest) -> None:
    method query_account (line 205) | def query_account(self) -> None:
    method query_position (line 213) | def query_position(self) -> None:
    method query_history (line 221) | def query_history(self, req: HistoryRequest) -> list[BarData]:
    method close (line 235) | def close(self) -> None:
    method on_order (line 245) | def on_order(self, order: OrderData) -> None:
    method get_order (line 255) | def get_order(self, orderid: str) -> OrderData | None:
    method on_contract (line 267) | def on_contract(self, contract: ContractData) -> None:
    method get_contract_by_symbol (line 278) | def get_contract_by_symbol(self, symbol: str) -> ContractData | None:
    method get_contract_by_name (line 290) | def get_contract_by_name(self, name: str) -> ContractData | None:
    method process_timer_event (line 302) | def process_timer_event(self, event: Event) -> None:
  class RestApi (line 315) | class RestApi(RestClient):
    method __init__ (line 328) | def __init__(self, gateway: BinanceSpotGateway) -> None:
    method sign (line 350) | def sign(self, request: Request) -> Request:
    method connect (line 416) | def connect(
    method query_time (line 444) | def query_time(self) -> None:
    method query_account (line 459) | def query_account(self) -> None:
    method query_order (line 474) | def query_order(self) -> None:
    method query_contract (line 490) | def query_contract(self) -> None:
    method on_query_time (line 506) | def on_query_time(self, data: dict, request: Request) -> None:
    method on_query_account (line 526) | def on_query_account(self, data: dict, request: Request) -> None:
    method on_query_order (line 553) | def on_query_order(self, data: list, request: Request) -> None:
    method on_query_contract (line 591) | def on_query_contract(self, data: dict, request: Request) -> None:
    method query_history (line 641) | def query_history(self, req: HistoryRequest) -> list[BarData]:
  class MdApi (line 738) | class MdApi(WebsocketClient):
    method __init__ (line 749) | def __init__(self, gateway: BinanceSpotGateway) -> None:
    method connect (line 769) | def connect(
    method on_connected (line 796) | def on_connected(self) -> None:
    method subscribe (line 823) | def subscribe(self, req: SubscribeRequest) -> None:
    method subscribe_new_channels (line 865) | def subscribe_new_channels(self) -> None:
    method on_packet (line 884) | def on_packet(self, packet: dict) -> None:
    method on_disconnected (line 954) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_error (line 967) | def on_error(self, e: Exception) -> None:
  class TradeApi (line 980) | class TradeApi(WebsocketClient):
    method __init__ (line 995) | def __init__(self, gateway: BinanceSpotGateway) -> None:
    method connect (line 1024) | def connect(
    method sign (line 1060) | def sign(self, params: dict) -> None:
    method send_order (line 1081) | def send_order(self, req: OrderRequest) -> str:
    method cancel_order (line 1143) | def cancel_order(self, req: CancelRequest) -> None:
    method subscribe_user_data_stream (line 1174) | def subscribe_user_data_stream(self) -> None:
    method on_subscribe_user_data_stream (line 1199) | def on_subscribe_user_data_stream(self, packet: dict) -> None:
    method on_connected (line 1222) | def on_connected(self) -> None:
    method on_disconnected (line 1233) | def on_disconnected(self, status_code: int, msg: str) -> None:
    method on_packet (line 1247) | def on_packet(self, packet: dict) -> None:
    method on_user_data_event (line 1270) | def on_user_data_event(self, event: dict) -> None:
    method on_account (line 1286) | def on_account(self, event: dict) -> None:
    method on_order (line 1310) | def on_order(self, event: dict) -> None:
    method on_send_order (line 1370) | def on_send_order(self, packet: dict) -> None:
    method on_cancel_order (line 1396) | def on_cancel_order(self, packet: dict) -> None:
    method on_error (line 1416) | def on_error(self, e: Exception) -> None:
  function generate_datetime (line 1429) | def generate_datetime(timestamp: float) -> datetime:
  function format_float (line 1446) | def format_float(f: float) -> str:
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (256K chars).
[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 266,
    "preview": "# 行为准则\n\n这是一份VeighNa项目社区的行为准则,也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望:\n\n* 为交易员而生:作为一款从金融机构量化业务中诞生的交易系统开发框架,设计上都优先满足机构专业交易员的使用习惯,而不"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 231,
    "preview": "## 环境\n\n* 操作系统: 如Windows 11或者Ubuntu 22.04\n* Python版本: 如VeighNa Studio-4.0.0\n* VeighNa版本: 如v4.0.0发行版或者dev branch 20250320("
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 86,
    "preview": "建议每次发起的PR内容尽可能精简,复杂的修改请拆分为多次PR,便于管理合并。\n\n## 改进内容\n\n1. \n2. \n3.\n\n## 相关的Issue号(如有)\n\nClose #"
  },
  {
    "path": ".github/SUPPORT.md",
    "chars": 117,
    "preview": "# 获取帮助\n\n在开发和使用项目的过程中遇到问题时,获取帮助的渠道包括:\n\n* Github Issues:[Issues页面](https://github.com/veighna-global/vnpy_evo/issues)\n\n"
  },
  {
    "path": ".github/workflows/pythonapp.yml",
    "chars": 819,
    "preview": "name: Python application\n\non: [push]\n\njobs:\n  build:\n\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checko"
  },
  {
    "path": ".gitignore",
    "chars": 1805,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1726,
    "preview": "# 2026.04.27\n\n1. update linear and portfolio gateways with new endpoints for routed streams\n\n# 2026.04.15\n\n1. fix missin"
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2015-present, Xiaoyou Chen\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 1759,
    "preview": "# Binance trading gateway for VeighNa\n\n<p align=\"center\">\n  <img src =\"https://github.com/veighna-global/vnpy_evo/blob/d"
  },
  {
    "path": "pyproject.toml",
    "chars": 2262,
    "preview": "[project]\nname = \"vnpy_binance\"\ndynamic = [\"version\"]\ndescription = \"BINANCE trading gateway for VeighNa.\"\nreadme = \"REA"
  },
  {
    "path": "script/run.py",
    "chars": 841,
    "preview": "from vnpy.event import EventEngine\nfrom vnpy.trader.engine import MainEngine\nfrom vnpy.trader.ui import MainWindow, crea"
  },
  {
    "path": "vnpy_binance/__init__.py",
    "chars": 1485,
    "preview": "# The MIT License (MIT)\n#\n# Copyright (c) 2015-present, Xiaoyou Chen\n#\n# Permission is hereby granted, free of charge, t"
  },
  {
    "path": "vnpy_binance/inverse_gateway.py",
    "chars": 54573,
    "preview": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom typing import Any\nfrom collections"
  },
  {
    "path": "vnpy_binance/linear_gateway.py",
    "chars": 57601,
    "preview": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom typing import Any\nfrom collections"
  },
  {
    "path": "vnpy_binance/portfolio_gateway.py",
    "chars": 71488,
    "preview": "\"\"\"\nBinance Portfolio Margin Gateway for VeighNa.\n\nThis module provides trading functionality for Binance Portfolio Marg"
  },
  {
    "path": "vnpy_binance/spot_gateway.py",
    "chars": 46142,
    "preview": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom collections.abc import Callable\nfr"
  }
]

About this extraction

This page contains the full source code of the veighna-global/vnpy_binance GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (236.6 KB), approximately 51.3k tokens, and a symbol index with 315 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!