[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# 行为准则\n\n这是一份VeighNa项目社区的行为准则，也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望：\n\n* 为交易员而生：作为一款从金融机构量化业务中诞生的交易系统开发框架，设计上都优先满足机构专业交易员的使用习惯，而不是其他用户（散户、爱好者、技术人员等）\n\n* 对新用户友好，保持耐心：大部分人在接触新东西的时候都是磕磕碰碰、有很多的问题，请记住此时别人对你伸出的援助之手，并把它传递给未来需要的人\n\n* 尊重他人，慎重言行：礼貌文明的交流方式除了能得到别人同样的回应，更能减少不必要的摩擦，保证高效的交流\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "## 环境\n\n* 操作系统: 如Windows 11或者Ubuntu 22.04\n* Python版本: 如VeighNa Studio-4.0.0\n* VeighNa版本: 如v4.0.0发行版或者dev branch 20250320（下载日期）\n\n## Issue类型\n三选一：Bug/Enhancement/Question\n\n## 预期程序行为\n\n\n## 实际程序行为\n\n\n## 重现步骤\n\n针对Bug类型Issue，请提供具体重现步骤以及报错截图\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "建议每次发起的PR内容尽可能精简，复杂的修改请拆分为多次PR，便于管理合并。\n\n## 改进内容\n\n1. \n2. \n3.\n\n## 相关的Issue号（如有）\n\nClose #"
  },
  {
    "path": ".github/SUPPORT.md",
    "content": "# 获取帮助\n\n在开发和使用项目的过程中遇到问题时，获取帮助的渠道包括：\n\n* Github Issues：[Issues页面](https://github.com/veighna-global/vnpy_evo/issues)\n\n"
  },
  {
    "path": ".github/workflows/pythonapp.yml",
    "content": "name: Python application\n\non: [push]\n\njobs:\n  build:\n\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: Set up Python 3.13\n      uses: actions/setup-python@v1\n      with:\n        python-version: '3.13'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install ta-lib==0.6.3 --index=https://pypi.vnpy.com\n        pip install vnpy ruff mypy uv\n    - name: Lint with ruff\n      run: |\n        # Run ruff linter based on pyproject.toml configuration\n        ruff check .\n    - name: Type check with mypy\n      run: |\n        # Run mypy type checking based on pyproject.toml configuration\n        mypy vnpy_binance\n    - name: Build packages with uv\n      run: |\n        # Build source distribution and wheel distribution\n        uv build"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n*.pya\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 2026.04.27\n\n1. update linear and portfolio gateways with new endpoints for routed streams\n\n# 2026.04.15\n\n1. fix missing data when querying bar history of inverse contract\n\n# 2026.04.13\n\n1. update tick.datetime when receiving book update\n2. fix bug: start time is greater than end time when querying bar history\n\n# 2026.03.27\n\n1. add TRADIFI_PERPETUAL contract support for portfolio gateway\n\n# 2026.03.26\n\n1. support TRADIFI_PERPETUAL contract\n\n# 2026.03.06\n\n1. new PortfolioGateway for portfolio margin mode\n2. update SpotGateway to latest version\n3. support funding rate subscription for linear swap\n\n# 2026.01.25\n\n1. use periodic subscription mechanism to avoid connection loss\n\n# 2025.06.17\n\n1. refactor BinanceSpotGateway and BinanceInverseGateway\n2. remove unused disconnect function\n\n# 2025.05.08\n\n1. remove dependency on vnpy_evo\n2. change to use GLOBAL exchange\n3. refactor BinanceLinearGateway \n\n# 2025.1.25\n\n1. BinanceLinearGateway replace REST API with Websocket API for sending orders\n\n# 2024.12.16\n\n1. write log (event) when exception raised by websocket client\n\n# 2024.9.16\n\n1. add more detail to TickData.extra including: active_volume/active_turnover/trade_count\n2. BinanceLinearGateway upgrade to v3 api for querying account and position\n\n# 2024.9.4\n\n1. add extra data dict for BarData\n\n# 2024.9.3\n\n1. use vnpy_evo for rest and websocket client\n\n# 2024.8.10\n\n1. fix the problem of datetime timezone\n2. fix the problem of account data receiving no update when the balance is all sold out\n3. only keep user stream when key is provided\n\n# 2024.5.7\n\n1. use numpy.format_float_positional to improve float number precisio…\n2. output log message of time offse\n3. query private data after time offset is calculated\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2015-present, Xiaoyou Chen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Binance trading gateway for VeighNa\n\n<p align=\"center\">\n  <img src =\"https://github.com/veighna-global/vnpy_evo/blob/dev/logo.png\" width=\"300\" height=\"300\"/>\n</p>\n\n<p align=\"center\">\n    <img src =\"https://img.shields.io/badge/version-2026.04.27-blueviolet.svg\"/>\n    <img src =\"https://img.shields.io/badge/platform-windows|linux|macos-yellow.svg\"/>\n    <img src =\"https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg\"/>\n    <img src =\"https://img.shields.io/github/license/veighna-global/vnpy_binance.svg?color=orange\"/>\n</p>\n\n\n## Introduction\n\nThis gateway is developed based on Binance's REST and Websocket API, and supports spot, linear contract and inverse contract trading.\n\n**For derivatives contract trading, please notice:**\n\n1. Only supports cross margin mode.\n2. Only supports one-way position mode.\n\n## Install\n\nUsers can easily install ``vnpy_binance`` by pip according to the following command.\n\n```\npip install vnpy_binance\n```\n\nAlso, users can install ``vnpy_binance`` using the source code. Clone the repository and install as follows:\n\n```\ngit clone https://github.com/veighna-global/vnpy_binance.git && cd vnpy_binance\n\npython setup.py install\n```\n\n## A Simple Example\n\nSave this as run.py.\n\n```\nfrom vnpy.event import EventEngine\nfrom vnpy.trader.engine import MainEngine\nfrom vnpy.trader.ui import MainWindow, create_qapp\n\nfrom vnpy_binance import (\n    BinanceLinearGateway,\n)\n\n\ndef main() -> None:\n    \"\"\"main entry\"\"\"\n    qapp = create_qapp()\n\n    event_engine = EventEngine()\n    main_engine = MainEngine(event_engine)\n    main_engine.add_gateway(BinanceLinearGateway)\n\n    main_window = MainWindow(main_engine, event_engine)\n    main_window.showMaximized()\n\n    qapp.exec()\n\n\nif __name__ == \"__main__\":\n    main()\n```\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"vnpy_binance\"\ndynamic = [\"version\"]\ndescription = \"BINANCE trading gateway for VeighNa.\"\nreadme = \"README.md\"\nlicense = {text = \"MIT\"}\nauthors = [{name = \"VeighNa Global\", email = \"veighna@hotmail.com\"}]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Operating System :: Microsoft :: Windows\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Operating System :: MacOS\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Topic :: Office/Business :: Financial :: Investment\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Natural Language :: English\",\n]\nrequires-python = \">=3.10\"\nkeywords = [\"quant\", \"quantitative\", \"investment\", \"trading\", \"algotrading\", \"binance\", \"btc\", \"crypto\"]\ndependencies = [\n    \"vnpy_rest\",\n    \"vnpy_websocket\",\n]\n\n[project.urls]\n\"Homepage\" = \"https://www.github.com/veighna-global\"\n\"Source\" = \"https://www.github.com/veighna-global\"\n\n[build-system]\nrequires = [\"hatchling>=1.27.0\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.version]\npath = \"vnpy_binance/__init__.py\"\npattern = \"__version__ = ['\\\"](?P<version>[^'\\\"]+)['\\\"]\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"vnpy_binance\"]\ninclude-package-data = true\n\n[tool.hatch.build.targets.sdist]\ninclude = [\"vnpy_binance*\"] \n\n[tool.ruff]\ntarget-version = \"py310\"\noutput-format = \"full\"\n\n[tool.ruff.lint]\nselect = [\n    \"B\",  # flake8-bugbear\n    \"E\",  # pycodestyle error\n    \"F\",  # pyflakes\n    \"UP\",  # pyupgrade\n    \"W\",  # pycodestyle warning\n]\nignore = [\"E501\"]\n\n[tool.mypy]\npython_version = \"3.10\"\nwarn_return_any = true\nwarn_unused_configs = true\ndisallow_untyped_defs = true\ndisallow_incomplete_defs = true\ncheck_untyped_defs = true\ndisallow_untyped_decorators = true\nno_implicit_optional = true\nstrict_optional = true\nwarn_redundant_casts = true\nwarn_unused_ignores = true\nwarn_no_return = true\nignore_missing_imports = true \n\n[[tool.mypy.overrides]]\nmodule = [\n    \"vnpy.*\",\n    \"vnpy_rest.*\",\n    \"vnpy_websocket.*\"\n]\nignore_errors = true\nignore_missing_imports = true"
  },
  {
    "path": "script/run.py",
    "content": "from vnpy.event import EventEngine\nfrom vnpy.trader.engine import MainEngine\nfrom vnpy.trader.ui import MainWindow, create_qapp\n\nfrom vnpy_binance import (\n    BinanceLinearGateway,\n    BinanceInverseGateway,\n    BinanceSpotGateway,\n    BinancePortfolioGateway\n)\nfrom vnpy_datamanager import DataManagerApp\n\n\ndef main() -> None:\n    \"\"\"main entry\"\"\"\n    qapp = create_qapp()\n\n    event_engine = EventEngine()\n    main_engine = MainEngine(event_engine)\n    main_engine.add_gateway(BinanceLinearGateway)\n    main_engine.add_gateway(BinanceInverseGateway)\n    main_engine.add_gateway(BinanceSpotGateway)\n    main_engine.add_gateway(BinancePortfolioGateway)\n\n    main_engine.add_app(DataManagerApp)\n\n    main_window = MainWindow(main_engine, event_engine)\n    main_window.showMaximized()\n\n    qapp.exec()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "vnpy_binance/__init__.py",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2015-present, Xiaoyou Chen\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom .linear_gateway import BinanceLinearGateway\nfrom .inverse_gateway import BinanceInverseGateway\nfrom .spot_gateway import BinanceSpotGateway\nfrom .portfolio_gateway import BinancePortfolioGateway\n\n\n__version__ = \"2026.04.27\"\n\n\n__all__ = [\n    \"BinanceLinearGateway\",\n    \"BinanceInverseGateway\",\n    \"BinanceSpotGateway\",\n    \"BinancePortfolioGateway\"\n]\n"
  },
  {
    "path": "vnpy_binance/inverse_gateway.py",
    "content": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom typing import Any\nfrom collections.abc import Callable\nfrom time import sleep\nfrom datetime import datetime, timedelta\n\nfrom numpy import format_float_positional\n\nfrom vnpy.event import Event, EventEngine\nfrom vnpy.trader.constant import (\n    Direction,\n    Exchange,\n    Product,\n    Status,\n    OrderType,\n    Interval\n)\nfrom vnpy.trader.gateway import BaseGateway\nfrom vnpy.trader.object import (\n    TickData,\n    OrderData,\n    TradeData,\n    AccountData,\n    ContractData,\n    PositionData,\n    BarData,\n    OrderRequest,\n    CancelRequest,\n    SubscribeRequest,\n    HistoryRequest\n)\nfrom vnpy.trader.event import EVENT_TIMER\nfrom vnpy.trader.utility import round_to, ZoneInfo\nfrom vnpy_rest import Request, RestClient, Response\nfrom vnpy_websocket import WebsocketClient\n\n\n# Timezone constant\nUTC_TZ = ZoneInfo(\"UTC\")\n\n# Real server hosts\nREAL_REST_HOST: str = \"https://dapi.binance.com\"\nREAL_TRADE_HOST: str = \"wss://ws-dapi.binance.com/ws-dapi/v1\"\nREAL_USER_HOST: str = \"wss://dstream.binance.com/ws/\"\nREAL_DATA_HOST: str = \"wss://dstream.binance.com/stream\"\n\n# Testnet server hosts\nTESTNET_REST_HOST: str = \"https://testnet.binancefuture.com\"\nTESTNET_TRADE_HOST: str = \"wss://testnet.binancefuture.com/ws-dapi/v1\"\nTESTNET_USER_HOST: str = \"wss://dstream.binancefuture.com/ws/\"\nTESTNET_DATA_HOST: str = \"wss://dstream.binancefuture.com/stream\"\n\n# Order status map\nSTATUS_BINANCE2VT: dict[str, Status] = {\n    \"NEW\": Status.NOTTRADED,\n    \"PARTIALLY_FILLED\": Status.PARTTRADED,\n    \"FILLED\": Status.ALLTRADED,\n    \"CANCELED\": Status.CANCELLED,\n    \"REJECTED\": Status.REJECTED,\n    \"EXPIRED\": Status.CANCELLED\n}\n\n# Order type map\nORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {\n    OrderType.LIMIT: (\"LIMIT\", \"GTC\"),\n    OrderType.MARKET: (\"MARKET\", \"GTC\"),\n    OrderType.FAK: (\"LIMIT\", \"IOC\"),\n    OrderType.FOK: (\"LIMIT\", \"FOK\"),\n}\nORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCE.items()}\n\n# Direction map\nDIRECTION_VT2BINANCE: dict[Direction, str] = {\n    Direction.LONG: \"BUY\",\n    Direction.SHORT: \"SELL\"\n}\nDIRECTION_BINANCE2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCE.items()}\n\n# Product map\nPRODUCT_BINANCE2VT: dict[str, Product] = {\n    \"PERPETUAL\": Product.SWAP,\n    \"PERPETUAL_DELIVERING\": Product.SWAP,\n    \"CURRENT_MONTH\": Product.FUTURES,\n    \"NEXT_MONTH\": Product.FUTURES,\n    \"CURRENT_QUARTER\": Product.FUTURES,\n    \"NEXT_QUARTER\": Product.FUTURES,\n}\n\n# Kline interval map\nINTERVAL_VT2BINANCE: dict[Interval, str] = {\n    Interval.MINUTE: \"1m\",\n    Interval.HOUR: \"1h\",\n    Interval.DAILY: \"1d\",\n}\n\n# Timedelta map\nTIMEDELTA_MAP: dict[Interval, timedelta] = {\n    Interval.MINUTE: timedelta(minutes=1),\n    Interval.HOUR: timedelta(hours=1),\n    Interval.DAILY: timedelta(days=1),\n}\n\n# Set weboscket timeout to 24 hour\nWEBSOCKET_TIMEOUT = 24 * 60 * 60\n\n\nclass BinanceInverseGateway(BaseGateway):\n    \"\"\"\n    The Binance inverse trading gateway for VeighNa.\n\n    This gateway provides trading functionality for Binance Coin-M perpetual contracts\n    and delivery futures through their API.\n\n    Features:\n    1. Only support crossed position\n    2. Only support one-way mode\n    3. Provides market data, trading, and account management capabilities\n    \"\"\"\n\n    default_name: str = \"BINANCE_INVERSE\"\n\n    default_setting: dict = {\n        \"API Key\": \"\",\n        \"API Secret\": \"\",\n        \"Server\": [\"REAL\", \"TESTNET\"],\n        \"Kline Stream\": [\"False\", \"True\"],\n        \"Proxy Host\": \"\",\n        \"Proxy Port\": 0\n    }\n\n    exchanges: list[Exchange] = [Exchange.GLOBAL]\n\n    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:\n        \"\"\"\n        The init method of the gateway.\n\n        This method initializes the gateway components including REST API,\n        trading API, user data API, and market data API. It also sets up\n        the data structures for order and contract storage.\n\n        Parameters:\n            event_engine: the global event engine object of VeighNa\n            gateway_name: the unique name for identifying the gateway\n        \"\"\"\n        super().__init__(event_engine, gateway_name)\n\n        self.trade_api: TradeApi = TradeApi(self)\n        self.user_api: UserApi = UserApi(self)\n        self.md_api: MdApi = MdApi(self)\n        self.rest_api: RestApi = RestApi(self)\n\n        self.orders: dict[str, OrderData] = {}\n        self.symbol_contract_map: dict[str, ContractData] = {}\n        self.name_contract_map: dict[str, ContractData] = {}\n\n    def connect(self, setting: dict) -> None:\n        \"\"\"\n        Start server connections.\n\n        This method establishes connections to Binance servers\n        using the provided settings.\n\n        Parameters:\n            setting: A dictionary containing connection parameters including\n                    API credentials, server selection, and proxy configuration\n        \"\"\"\n        key: str = setting[\"API Key\"]\n        secret: str = setting[\"API Secret\"]\n        server: str = setting[\"Server\"]\n        kline_stream: bool = setting[\"Kline Stream\"] == \"True\"\n        proxy_host: str = setting[\"Proxy Host\"]\n        proxy_port: int = setting[\"Proxy Port\"]\n\n        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.trade_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.md_api.connect(server, kline_stream, proxy_host, proxy_port)\n\n        self.event_engine.register(EVENT_TIMER, self.process_timer_event)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This method forwards the subscription request to the market data API.\n\n        Parameters:\n            req: Subscription request object containing the symbol to subscribe\n        \"\"\"\n        self.md_api.subscribe(req)\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order.\n\n        This method forwards the order request to the trading API.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            str: The VeighNa order ID if successful, empty string if failed\n        \"\"\"\n        return self.trade_api.send_order(req)\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order.\n\n        This method forwards the cancellation request to the trading API.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        self.trade_api.cancel_order(req)\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        Not required since Binance provides websocket updates for account balances.\n        \"\"\"\n        pass\n\n    def query_position(self) -> None:\n        \"\"\"\n        Query current positions.\n\n        Not required since Binance provides websocket updates for positions.\n        \"\"\"\n        pass\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"\n        Query historical kline data.\n\n        This method forwards the history request to the REST API.\n\n        Parameters:\n            req: History request object containing query parameters\n\n        Returns:\n            list[BarData]: List of historical kline data bars\n        \"\"\"\n        return self.rest_api.query_history(req)\n\n    def close(self) -> None:\n        \"\"\"\n        Close server connections.\n\n        This method stops all API connections and releases resources.\n        \"\"\"\n        self.rest_api.stop()\n        self.user_api.stop()\n        self.md_api.stop()\n        self.trade_api.stop()\n\n    def process_timer_event(self, event: Event) -> None:\n        \"\"\"\n        Process timer task.\n\n        This function is called regularly by the event engine to perform scheduled tasks,\n        such as keeping the user stream alive.\n\n        Parameters:\n            event: Timer event object\n        \"\"\"\n        self.rest_api.keep_user_stream()\n\n        self.md_api.subscribe_new_channels()\n\n    def on_order(self, order: OrderData) -> None:\n        \"\"\"\n        Save a copy of order and then push to event engine.\n\n        Parameters:\n            order: Order data object\n        \"\"\"\n        self.orders[order.orderid] = copy(order)\n        super().on_order(order)\n\n    def get_order(self, orderid: str) -> OrderData | None:\n        \"\"\"\n        Get previously saved order by order id.\n\n        Parameters:\n            orderid: The ID of the order to retrieve\n\n        Returns:\n            Order data object if found, None otherwise\n        \"\"\"\n        return self.orders.get(orderid, None)\n\n    def on_contract(self, contract: ContractData) -> None:\n        \"\"\"\n        Save contract data in mappings and push to event engine.\n\n        Parameters:\n            contract: Contract data object\n        \"\"\"\n        self.symbol_contract_map[contract.symbol] = contract\n        self.name_contract_map[contract.name] = contract\n        super().on_contract(contract)\n\n    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by VeighNa symbol.\n\n        Parameters:\n            symbol: VeighNa symbol (e.g. \"BTC_SWAP_BINANCE\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.symbol_contract_map.get(symbol, None)\n\n    def get_contract_by_name(self, name: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by exchange symbol name.\n\n        Parameters:\n            name: Exchange symbol name (e.g. \"BTCUSD\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.name_contract_map.get(name, None)\n\n\nclass RestApi(RestClient):\n    \"\"\"\n    The REST API of BinanceInverseGateway.\n\n    This class handles HTTP requests to Binance API endpoints, including:\n    - Authentication and signature generation\n    - Contract information queries\n    - Account and position queries\n    - Order management\n    - Historical data queries\n    - User data stream management\n    \"\"\"\n\n    def __init__(self, gateway: BinanceInverseGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the REST API with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceInverseGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.user_api: UserApi = self.gateway.user_api\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n\n        self.user_stream_key: str = \"\"\n        self.keep_alive_count: int = 0\n        self.time_offset: int = 0\n\n        self.order_count: int = 1_000_000\n        self.order_prefix: str = \"\"\n\n    def sign(self, request: Request) -> Request:\n        \"\"\"\n        Standard callback for signing a request.\n\n        This method adds the necessary authentication parameters and signature\n        to requests that require API key authentication.\n\n        It handles:\n        1. Path construction with query parameters\n        2. Timestamp generation with server time offset adjustment\n        3. HMAC-SHA256 signature generation\n        4. Required authentication headers\n\n        Parameters:\n            request: Request object to be signed\n\n        Returns:\n            Request: Modified request with authentication parameters\n        \"\"\"\n        # Construct path with query parameters if they exist\n        if request.params:\n            path: str = request.path + \"?\" + urllib.parse.urlencode(request.params)\n        else:\n            request.params = {}\n            path = request.path\n\n        # Get current timestamp in milliseconds\n        timestamp: int = int(time.time() * 1000)\n\n        # Adjust timestamp based on time offset with server\n        if self.time_offset > 0:\n            timestamp -= abs(self.time_offset)\n        elif self.time_offset < 0:\n            timestamp += abs(self.time_offset)\n\n        # Add timestamp to request parameters\n        request.params[\"timestamp\"] = timestamp\n\n        # Generate signature using HMAC SHA256\n        query: str = urllib.parse.urlencode(sorted(request.params.items()))\n        signature: str = hmac.new(\n            self.secret,\n            query.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n\n        # Append signature to query string\n        query += f\"&signature={signature}\"\n        path = request.path + \"?\" + query\n\n        # Update request with signed path and clear params/data\n        request.path = path\n        request.params = {}\n        request.data = {}\n\n        # Add required headers for API authentication\n        request.headers = {\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\n            \"Accept\": \"application/json\",\n            \"X-MBX-APIKEY\": self.key,\n            \"Connection\": \"close\"\n        }\n\n        return request\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"Start server connection\"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_REST_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n        self.gateway.write_log(\"REST API started\")\n\n        self.query_time()\n\n    def query_time(self) -> None:\n        \"\"\"\n        Query server time to calculate local time offset.\n\n        This function sends a request to get the exchange server time,\n        which is used to calculate the local time offset for timestamp synchronization.\n        \"\"\"\n        path: str = \"/dapi/v1/time\"\n\n        self.add_request(\n            \"GET\",\n            path,\n            callback=self.on_query_time\n        )\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        This function sends a request to get the account balance information,\n        including wallet balance, available balance, and margin.\n        \"\"\"\n        path: str = \"/dapi/v1/account\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_account,\n        )\n\n    def query_position(self) -> None:\n        \"\"\"\n        Query holding positions.\n\n        This function sends a request to get current position data,\n        including position amount, entry price, and unrealized profit/loss.\n        \"\"\"\n        path: str = \"/dapi/v1/positionRisk\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_position,\n        )\n\n    def query_order(self) -> None:\n        \"\"\"\n        Query open orders.\n\n        This function sends a request to get all active orders\n        that have not been fully filled or cancelled.\n        \"\"\"\n        path: str = \"/dapi/v1/openOrders\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_order,\n        )\n\n    def query_contract(self) -> None:\n        \"\"\"\n        Query available contracts.\n\n        This function sends a request to get exchange information,\n        including all available trading instruments, their precision,\n        and trading rules.\n        \"\"\"\n        path: str = \"/dapi/v1/exchangeInfo\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_contract,\n        )\n\n    def start_user_stream(self) -> None:\n        \"\"\"\n        Create listen key for user stream.\n\n        This function sends a request to create a listen key which is\n        required to establish a user data websocket connection.\n        \"\"\"\n        path: str = \"/dapi/v1/listenKey\"\n\n        self.add_request(\n            method=\"POST\",\n            path=path,\n            callback=self.on_start_user_stream,\n        )\n\n    def keep_user_stream(self) -> None:\n        \"\"\"\n        Extend listen key validity.\n\n        This function sends a request to keep the listen key active,\n        which is required to maintain the user data websocket connection.\n        The listen key will expire after 60 minutes if not refreshed.\n        \"\"\"\n        if not self.user_stream_key:\n            return\n\n        self.keep_alive_count += 1\n        if self.keep_alive_count < 600:\n            return\n        self.keep_alive_count = 0\n\n        params: dict = {\"listenKey\": self.user_stream_key}\n\n        path: str = \"/dapi/v1/listenKey\"\n\n        self.add_request(\n            method=\"PUT\",\n            path=path,\n            callback=self.on_keep_user_stream,\n            params=params,\n            on_error=self.on_keep_user_stream_error\n        )\n\n    def on_query_time(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of server time query.\n\n        This function processes the server time response and calculates\n        the time offset between local and server time, which is used for\n        request timestamp synchronization.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        local_time: int = int(time.time() * 1000)\n        server_time: int = int(data[\"serverTime\"])\n        self.time_offset = local_time - server_time\n\n        self.gateway.write_log(f\"Server time updated, local offset: {self.time_offset}ms\")\n\n        self.query_contract()\n\n    def on_query_account(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of account balance query.\n\n        This function processes the account balance response and\n        creates AccountData objects for each asset in the account.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for asset in data[\"assets\"]:\n            account: AccountData = AccountData(\n                accountid=asset[\"asset\"],\n                balance=float(asset[\"walletBalance\"]),\n                frozen=float(asset[\"maintMargin\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        self.gateway.write_log(\"Account data received\")\n\n    def on_query_position(self, data: list, request: Request) -> None:\n        \"\"\"\n        Callback of holding positions query.\n\n        This function processes the position data response and\n        creates PositionData objects for each position held.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data:\n            name: str = d[\"symbol\"]\n            contract: ContractData | None = self.gateway.get_contract_by_name(name)\n            if not contract:\n                continue\n\n            position: PositionData = PositionData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                direction=Direction.NET,\n                volume=float(d[\"positionAmt\"]),\n                price=float(d[\"entryPrice\"]),\n                pnl=float(d[\"unRealizedProfit\"]),\n                gateway_name=self.gateway_name,\n            )\n\n            if position.volume:\n                self.gateway.on_position(position)\n\n        self.gateway.write_log(\"Position data received\")\n\n    def on_query_order(self, data: list, request: Request) -> None:\n        \"\"\"\n        Callback of open orders query.\n\n        This function processes the open orders response and\n        creates OrderData objects for each active order.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(d[\"symbol\"])\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT[d[\"status\"]],\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"Order data received\")\n\n    def on_query_contract(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of available contracts query.\n\n        This function processes the exchange info response and\n        creates ContractData objects for each trading instrument.\n        It handles different contract types and extracts trading rules\n        like price tick, minimum/maximum volumes from filters.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            product: Product | None = PRODUCT_BINANCE2VT.get(d[\"contractType\"], None)\n            if product == Product.SWAP:\n                symbol: str = d[\"symbol\"].replace(\"_PERP\", \"\") + \"_SWAP_BINANCE\"\n            elif product == Product.FUTURES:\n                symbol = d[\"symbol\"] + \"_BINANCE\"\n            else:\n                continue\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=PRODUCT_BINANCE2VT.get(d[\"contractType\"], Product.SWAP),\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=True\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"Contract data received\")\n\n        # Query private data after time offset is calculated\n        if self.key and self.secret:\n            self.query_order()\n            self.query_account()\n            self.query_position()\n            self.start_user_stream()\n\n    def on_start_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Successful callback of start_user_stream.\n\n        This function processes the listen key response and initializes\n        the user data websocket connection with the provided key.\n\n        Parameters:\n            data: Response data from the server containing the listen key\n            request: Original request object\n        \"\"\"\n        self.user_stream_key = data[\"listenKey\"]\n        self.keep_alive_count = 0\n\n        if self.server == \"REAL\":\n            url = REAL_USER_HOST + self.user_stream_key\n        else:\n            url = TESTNET_USER_HOST + self.user_stream_key\n\n        self.user_api.connect(url, self.proxy_host, self.proxy_port)\n\n    def on_keep_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Successful callback of keep_user_stream.\n\n        This function handles the successful response of the listen key\n        refresh request. No action is needed on success.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        pass\n\n    def on_keep_user_stream_error(self, exception_type: type, exception_value: Exception, tb: Any, request: Request) -> None:\n        \"\"\"\n        Error callback of keep_user_stream.\n\n        This function handles errors from the listen key refresh request.\n        Timeout exceptions are ignored as they are common and non-critical.\n\n        Parameters:\n            exception_type: Type of the exception\n            exception_value: Exception instance\n            tb: Traceback object\n            request: Original request object\n        \"\"\"\n        if not issubclass(exception_type, TimeoutError):        # Ignore timeout exception\n            self.on_error(exception_type, exception_value, tb, request)\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"Query kline history data\"\"\"\n        # Check if the contract and interval exist\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            return []\n\n        if not req.interval:\n            return []\n\n        # Prepare history list\n        history: list[BarData] = []\n        limit: int = 1500\n        interval_delta: timedelta = TIMEDELTA_MAP[req.interval]\n        page_span: timedelta = interval_delta * (limit - 1)\n        current_start_dt: datetime = req.start\n\n        while True:\n            if req.end and current_start_dt >= req.end:\n                break\n\n            # Create query parameters\n            params: dict = {\n                \"symbol\": contract.name,\n                \"interval\": INTERVAL_VT2BINANCE[req.interval],\n                \"limit\": limit\n            }\n\n            params[\"startTime\"] = int(datetime.timestamp(current_start_dt)) * 1000\n            path: str = \"/dapi/v1/klines\"\n            if req.end:\n                page_end_dt: datetime = min(req.end, current_start_dt + page_span)\n                params[\"endTime\"] = int(datetime.timestamp(page_end_dt)) * 1000\n\n            resp: Response = self.request(\n                \"GET\",\n                path=path,\n                params=params\n            )\n\n            # Break the loop if request failed\n            if resp.status_code // 100 != 2:\n                msg: str = f\"Query kline history failed, status code: {resp.status_code}, message: {resp.text}\"\n                self.gateway.write_log(msg)\n                break\n            else:\n                data: list = resp.json()\n                if not data:\n                    msg = f\"No kline history data is received, start time: {current_start_dt}\"\n                    self.gateway.write_log(msg)\n                    break\n\n                buf: list[BarData] = []\n\n                for row in data:\n                    bar: BarData = BarData(\n                        symbol=req.symbol,\n                        exchange=req.exchange,\n                        datetime=generate_datetime(row[0]),\n                        interval=req.interval,\n                        volume=float(row[5]),\n                        turnover=float(row[7]),\n                        open_price=float(row[1]),\n                        high_price=float(row[2]),\n                        low_price=float(row[3]),\n                        close_price=float(row[4]),\n                        gateway_name=self.gateway_name\n                    )\n                    bar.extra = {\n                        \"trade_count\": int(row[8]),\n                        \"active_volume\": float(row[9]),\n                        \"active_turnover\": float(row[10]),\n                    }\n                    buf.append(bar)\n\n                begin_dt: datetime = buf[0].datetime\n                end_dt: datetime = buf[-1].datetime\n\n                history.extend(buf)\n                msg = f\"Query kline history finished, {req.symbol} - {req.interval.value}, {begin_dt} - {end_dt}\"\n                self.gateway.write_log(msg)\n\n                next_start_dt: datetime = end_dt + interval_delta\n\n                if next_start_dt <= current_start_dt:\n                    msg = (\n                        \"Query kline history pagination stopped because received data \"\n                        f\"did not advance, start: {current_start_dt}, end: {end_dt}\"\n                    )\n                    self.gateway.write_log(msg)\n                    break\n\n                # Break the loop if the latest data received\n                if (\n                    len(data) < limit\n                    or (req.end and next_start_dt >= req.end)\n                ):\n                    break\n\n                # Update query start time\n                current_start_dt = next_start_dt\n\n            # Wait to meet request flow limit\n            sleep(0.5)\n\n        # Remove the unclosed kline\n        if history:\n            history.pop(-1)\n\n        return history\n\n\nclass UserApi(WebsocketClient):\n    \"\"\"\n    The user data websocket API of BinanceInverseGateway.\n\n    This class handles user data events from Binance through websocket connection.\n    It processes real-time updates for:\n    - Account balance changes\n    - Position updates\n    - Order status changes\n    - Trade executions\n    \"\"\"\n\n    def __init__(self, gateway: BinanceInverseGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceInverseGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method establishes a websocket connection to Binance user data stream.\n\n        Parameters:\n            url: Websocket endpoint URL with listen key\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the websocket connection to the server\n        is successfully established. It logs the connection status.\n        \"\"\"\n        self.gateway.write_log(\"User API connected\")\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of data update.\n\n        This function processes websocket messages from the user data stream.\n        It handles different event types including account updates, order updates,\n        and listen key expiration.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        match packet[\"e\"]:\n            case \"ACCOUNT_UPDATE\":\n                self.on_account(packet)\n            case \"ORDER_TRADE_UPDATE\":\n                self.on_order(packet)\n            case \"listenKeyExpired\":\n                self.on_listen_key_expired()\n\n    def on_listen_key_expired(self) -> None:\n        \"\"\"\n        Callback of listen key expired.\n\n        This function is called when the exchange notifies that the listen key\n        has expired. It will log a message and disconnect the websocket connection.\n        \"\"\"\n        self.gateway.write_log(\"Listen key expired\")\n        self.disconnect()\n\n    def on_account(self, packet: dict) -> None:\n        \"\"\"\n        Callback of account balance and holding position update.\n\n        This function processes the account update event from the user data stream,\n        including balance changes and position updates.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        for acc_data in packet[\"a\"][\"B\"]:\n            account: AccountData = AccountData(\n                accountid=acc_data[\"a\"],\n                balance=float(acc_data[\"wb\"]),\n                frozen=float(acc_data[\"wb\"]) - float(acc_data[\"cw\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        for pos_data in packet[\"a\"][\"P\"]:\n            if pos_data[\"ps\"] == \"BOTH\":\n                volume = pos_data[\"pa\"]\n                if \".\" in volume:\n                    volume = float(volume)\n                else:\n                    volume = int(volume)\n\n                name: str = pos_data[\"s\"]\n                contract: ContractData | None = self.gateway.get_contract_by_name(name)\n                if not contract:\n                    continue\n\n                position: PositionData = PositionData(\n                    symbol=contract.symbol,\n                    exchange=Exchange.GLOBAL,\n                    direction=Direction.NET,\n                    volume=volume,\n                    price=float(pos_data[\"ep\"]),\n                    pnl=float(pos_data[\"up\"]),\n                    gateway_name=self.gateway_name,\n                )\n                self.gateway.on_position(position)\n\n    def on_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of order and trade update.\n\n        This function processes the order update event from the user data stream,\n        including order status changes and trade executions.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        ord_data: dict = packet[\"o\"]\n\n        # Filter unsupported order type\n        key: tuple[str, str] = (ord_data[\"o\"], ord_data[\"f\"])\n        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n        if not order_type:\n            return\n\n        # Filter unsupported symbol\n        name: str = ord_data[\"s\"]\n        contract: ContractData | None = self.gateway.get_contract_by_name(name)\n        if not contract:\n            return\n\n        # Create and push order\n        order: OrderData = OrderData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=str(ord_data[\"c\"]),\n            type=order_type,\n            direction=DIRECTION_BINANCE2VT[ord_data[\"S\"]],\n            price=float(ord_data[\"p\"]),\n            volume=float(ord_data[\"q\"]),\n            traded=float(ord_data[\"z\"]),\n            status=STATUS_BINANCE2VT[ord_data[\"X\"]],\n            datetime=generate_datetime(packet[\"E\"]),\n            gateway_name=self.gateway_name,\n        )\n\n        self.gateway.on_order(order)\n\n        # Round trade volume to meet step size\n        trade_volume: float = float(ord_data[\"l\"])\n        trade_volume = round_to(trade_volume, contract.min_volume)\n        if not trade_volume:\n            return\n\n        # Create and push trade\n        trade: TradeData = TradeData(\n            symbol=order.symbol,\n            exchange=order.exchange,\n            orderid=order.orderid,\n            tradeid=ord_data[\"t\"],\n            direction=order.direction,\n            price=float(ord_data[\"L\"]),\n            volume=trade_volume,\n            datetime=generate_datetime(ord_data[\"T\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_trade(trade)\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the websocket connection is closed.\n        It logs the disconnection details and attempts to restart the user stream.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"User API disconnected, code: {status_code}, msg: {msg}\")\n        self.gateway.rest_api.start_user_stream()\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the websocket connection.\n        It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"User API exception: {e}\")\n\n\nclass MdApi(WebsocketClient):\n    \"\"\"\n    The market data websocket API of BinanceInverseGateway.\n\n    This class handles market data from Binance through websocket connection.\n    It processes real-time updates for:\n    - Tickers (24hr statistics)\n    - Order book depth (10 levels)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinanceInverseGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceInverseGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n        self.new_channels: list[str] = []\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method establishes a websocket connection to Binance market data stream.\n\n        Parameters:\n            server: Server type (\"REAL\" or \"TESTNET\")\n            kline_stream: Whether to include kline data stream\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        else:\n            self.init(TESTNET_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the market data websocket connection\n        is successfully established. It logs the connection status and\n        resubscribes to previously subscribed market data channels.\n        \"\"\"\n        self.gateway.write_log(\"MD API connected\")\n\n        # Resubscribe market data\n        if self.ticks:\n            channels = []\n            for symbol in self.ticks.keys():\n                channels.append(f\"{symbol}@ticker\")\n                channels.append(f\"{symbol}@depth10\")\n\n                if self.kline_stream:\n                    channels.append(f\"{symbol}@kline_1m\")\n\n            packet: dict = {\n                \"method\": \"SUBSCRIBE\",\n                \"params\": channels,\n                \"id\": self.reqid\n            }\n            self.send_packet(packet)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This function sends subscription requests for ticker and depth data\n        for the specified trading instrument. If kline_stream is enabled,\n        it will also subscribe to 1-minute kline data.\n\n        Parameters:\n            req: Subscription request object containing symbol information\n        \"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        self.reqid += 1\n\n        # Initialize tick object\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        channels: list[str] = [\n            f\"{contract.name.lower()}@ticker\",\n            f\"{contract.name.lower()}@depth10\"\n        ]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        self.new_channels.extend(channels)\n\n    def subscribe_new_channels(self) -> None:\n        \"\"\"\n        Update timer event.\n\n        This function sends subscription requests for new channels\n        to the market data websocket server.\n        \"\"\"\n        if not self.new_channels:\n            return\n\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": self.new_channels,\n            \"id\": self.reqid\n        }\n        self.send_packet(packet)\n\n        self.new_channels = []\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of market data update.\n\n        This function processes different types of market data updates,\n        including ticker, depth, and kline data. It updates the corresponding\n        TickData object and pushes updates to the gateway.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        stream: str | None = packet.get(\"stream\", None)\n        if not stream:\n            return\n\n        data: dict = packet[\"data\"]\n\n        name, channel = stream.split(\"@\")\n        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper())\n        if not contract:\n            return\n        tick: TickData = self.ticks[contract.symbol]\n\n        if channel == \"ticker\":\n            tick.volume = float(data[\"v\"])\n            tick.turnover = float(data[\"q\"])\n            tick.open_price = float(data[\"o\"])\n            tick.high_price = float(data[\"h\"])\n            tick.low_price = float(data[\"l\"])\n            tick.last_price = float(data[\"c\"])\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        elif channel == \"depth10\":\n            bids: list = data[\"b\"]\n            for n in range(min(10, len(bids))):\n                price, volume = bids[n]\n                tick.__setattr__(\"bid_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"bid_volume_\" + str(n + 1), float(volume))\n\n            asks: list = data[\"a\"]\n            for n in range(min(10, len(asks))):\n                price, volume = asks[n]\n                tick.__setattr__(\"ask_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"ask_volume_\" + str(n + 1), float(volume))\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        else:\n            kline_data: dict = data[\"k\"]\n\n            # Check if bar is closed\n            bar_ready: bool = kline_data.get(\"x\", False)\n            if not bar_ready:\n                return\n\n            if tick.extra is None:\n                tick.extra = {}\n\n            dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n            tick.extra[\"bar\"] = BarData(\n                symbol=name.upper(),\n                exchange=Exchange.GLOBAL,\n                datetime=dt.replace(second=0, microsecond=0),\n                interval=Interval.MINUTE,\n                volume=float(kline_data[\"v\"]),\n                turnover=float(kline_data[\"q\"]),\n                open_price=float(kline_data[\"o\"]),\n                high_price=float(kline_data[\"h\"]),\n                low_price=float(kline_data[\"l\"]),\n                close_price=float(kline_data[\"c\"]),\n                gateway_name=self.gateway_name\n            )\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the market data websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the market data\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"MD API exception: {e}\")\n\n\nclass TradeApi(WebsocketClient):\n    \"\"\"\n    The trading websocket API of BinanceInverseGateway.\n\n    This class handles trading operations with Binance through websocket connection.\n    It provides functionality for:\n    - Order placement\n    - Order cancellation\n    - Request authentication and signature generation\n    \"\"\"\n\n    def __init__(self, gateway: BinanceInverseGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceInverseGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n        self.proxy_port: int = 0\n        self.proxy_host: str = \"\"\n        self.server: str = \"\"\n\n        self.reqid: int = 0\n        self.order_count: int = 0\n        self.order_prefix: str = \"\"\n\n        self.reqid_callback_map: dict[int, Callable] = {}\n        self.reqid_order_map: dict[int, OrderData] = {}\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method initializes the API credentials and establishes\n        a websocket connection to Binance trading API.\n\n        Parameters:\n            key: API Key for authentication\n            secret: API Secret for request signing\n            server: Server type (\"REAL\" or \"TESTNET\")\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_TRADE_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_TRADE_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n    def sign(self, params: dict) -> None:\n        \"\"\"\n        Generate the signature for the request.\n\n        This function creates an HMAC-SHA256 signature required for\n        authenticated API requests to Binance.\n\n        Parameters:\n            params: Dictionary containing the parameters to be signed\n        \"\"\"\n        timestamp: int = int(time.time() * 1000)\n        params[\"timestamp\"] = timestamp\n\n        payload: str = \"&\".join([f\"{k}={v}\" for k, v in sorted(params.items())])\n        signature: str = hmac.new(\n            self.secret,\n            payload.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n        params[\"signature\"] = signature\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order to Binance.\n\n        This function creates and sends a new order request to the exchange.\n        It handles different order types including market, limit, and stop orders.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            vt_orderid: The VeighNa order ID (gateway_name.orderid) if successful,\n                       empty string otherwise\n        \"\"\"\n        # Get contract\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to send order, symbol not found: {req.symbol}\")\n            return \"\"\n\n        # Generate new order id\n        self.order_count += 1\n        orderid: str = self.order_prefix + str(self.order_count)\n\n        # Push a submitting order event\n        order: OrderData = req.create_order_data(\n            orderid,\n            self.gateway_name\n        )\n        self.gateway.on_order(order)\n\n        # Create order parameters\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"side\": DIRECTION_VT2BINANCE[req.direction],\n            \"quantity\": format_float(req.volume),\n            \"newClientOrderId\": orderid,\n        }\n\n        if req.type == OrderType.MARKET:\n            params[\"type\"] = \"MARKET\"\n        elif req.type == OrderType.STOP:\n            params[\"type\"] = \"STOP_MARKET\"\n            params[\"stopPrice\"] = format_float(req.price)\n        else:\n            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]\n            params[\"type\"] = order_type\n            params[\"timeInForce\"] = time_condition\n            params[\"price\"] = format_float(req.price)\n\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_send_order\n        self.reqid_order_map[self.reqid] = order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.place\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n        return order.vt_orderid\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order on Binance.\n\n        This function sends a request to cancel an existing order on the exchange.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to cancel order, symbol not found: {req.symbol}\")\n            return\n\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"origClientOrderId\": req.orderid\n        }\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_cancel_order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.cancel\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the trading websocket connection\n        is successfully established. It logs the connection status.\n        \"\"\"\n        self.gateway.write_log(\"Trade API connected\")\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the trading websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"Trade API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of data update.\n\n        This function processes responses from the trading websocket API.\n        It routes the response to the appropriate callback function based\n        on the request ID.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        reqid: int = packet.get(\"id\", 0)\n        callback: Callable | None = self.reqid_callback_map.get(reqid, None)\n        if callback:\n            callback(packet)\n\n    def on_send_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of send order.\n\n        This function processes the response to an order placement request.\n        It handles errors by logging the details and updating the order status.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n        msg: str = f\"Order rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n        reqid: int = packet.get(\"id\", 0)\n        order: OrderData | None = self.reqid_order_map.get(reqid, None)\n        if order:\n            order.status = Status.REJECTED\n            self.gateway.on_order(order)\n\n    def on_cancel_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of cancel order.\n\n        This function processes the response to an order cancellation request.\n        It handles errors by logging the details.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n        msg: str = f\"Cancel rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the trading\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"Trade API exception: {e}\")\n\n\ndef generate_datetime(timestamp: float) -> datetime:\n    \"\"\"\n    Generate datetime object from Binance timestamp.\n\n    This function converts a Binance millisecond timestamp to a datetime object\n    with UTC timezone.\n\n    Parameters:\n        timestamp: Binance timestamp in milliseconds\n\n    Returns:\n        Datetime object with UTC timezone\n    \"\"\"\n    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)\n    return dt\n\n\ndef format_float(f: float) -> str:\n    \"\"\"\n    Convert float number to string with correct precision.\n\n    This function formats floating point numbers to avoid precision errors\n    when sending requests to Binance.\n\n    Parameters:\n        f: The floating point number to format\n\n    Returns:\n        Formatted string representation of the number\n\n    Note:\n        Fixes potential error -1111: Parameter \"quantity\" has too much precision\n    \"\"\"\n    return format_float_positional(f, trim=\"-\")\n"
  },
  {
    "path": "vnpy_binance/linear_gateway.py",
    "content": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom typing import Any\nfrom collections.abc import Callable\nfrom time import sleep\nfrom datetime import datetime, timedelta\n\nfrom numpy import format_float_positional\n\nfrom vnpy.event import Event, EventEngine\nfrom vnpy.trader.constant import (\n    Direction,\n    Exchange,\n    Product,\n    Status,\n    OrderType,\n    Interval\n)\nfrom vnpy.trader.gateway import BaseGateway\nfrom vnpy.trader.object import (\n    TickData,\n    OrderData,\n    TradeData,\n    AccountData,\n    ContractData,\n    PositionData,\n    BarData,\n    OrderRequest,\n    CancelRequest,\n    SubscribeRequest,\n    HistoryRequest\n)\nfrom vnpy.trader.event import EVENT_TIMER\nfrom vnpy.trader.utility import round_to, ZoneInfo\nfrom vnpy_rest import Request, RestClient, Response\nfrom vnpy_websocket import WebsocketClient\n\n\n# Timezone constant\nUTC_TZ = ZoneInfo(\"UTC\")\n\n# Real server hosts\nREAL_REST_HOST: str = \"https://fapi.binance.com\"\nREAL_TRADE_HOST: str = \"wss://ws-fapi.binance.com/ws-fapi/v1\"\nREAL_USER_HOST: str = \"wss://fstream.binance.com/private/ws\"\nREAL_PUBLIC_HOST: str = \"wss://fstream.binance.com/public/stream\"\nREAL_MARKET_HOST: str = \"wss://fstream.binance.com/market/stream\"\n\n# Testnet server hosts\nTESTNET_REST_HOST: str = \"https://demo-fapi.binance.com\"\nTESTNET_TRADE_HOST: str = \"wss://testnet.binancefuture.com/ws-fapi/v1\"\nTESTNET_USER_HOST: str = \"wss://fstream.binancefuture.com/private/ws\"\nTESTNET_PUBLIC_HOST: str = \"wss://fstream.binancefuture.com/public/stream\"\nTESTNET_MARKET_HOST: str = \"wss://fstream.binancefuture.com/market/stream\"\n\n# Order status map\nSTATUS_BINANCE2VT: dict[str, Status] = {\n    \"NEW\": Status.NOTTRADED,\n    \"PARTIALLY_FILLED\": Status.PARTTRADED,\n    \"FILLED\": Status.ALLTRADED,\n    \"CANCELED\": Status.CANCELLED,\n    \"REJECTED\": Status.REJECTED,\n    \"EXPIRED\": Status.CANCELLED\n}\n\n# Order type map\nORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {\n    OrderType.LIMIT: (\"LIMIT\", \"GTC\"),\n    OrderType.MARKET: (\"MARKET\", \"GTC\"),\n    OrderType.FAK: (\"LIMIT\", \"IOC\"),\n    OrderType.FOK: (\"LIMIT\", \"FOK\"),\n}\nORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCE.items()}\n\n# Direction map\nDIRECTION_VT2BINANCE: dict[Direction, str] = {\n    Direction.LONG: \"BUY\",\n    Direction.SHORT: \"SELL\"\n}\nDIRECTION_BINANCE2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCE.items()}\n\n# Product map\nPRODUCT_BINANCE2VT: dict[str, Product] = {\n    \"PERPETUAL\": Product.SWAP,\n    \"PERPETUAL_DELIVERING\": Product.SWAP,\n    \"TRADIFI_PERPETUAL\": Product.SWAP,\n    \"CURRENT_MONTH\": Product.FUTURES,\n    \"NEXT_MONTH\": Product.FUTURES,\n    \"CURRENT_QUARTER\": Product.FUTURES,\n    \"NEXT_QUARTER\": Product.FUTURES,\n}\n\n# Kline interval map\nINTERVAL_VT2BINANCE: dict[Interval, str] = {\n    Interval.MINUTE: \"1m\",\n    Interval.HOUR: \"1h\",\n    Interval.DAILY: \"1d\",\n}\n\n# Timedelta map\nTIMEDELTA_MAP: dict[Interval, timedelta] = {\n    Interval.MINUTE: timedelta(minutes=1),\n    Interval.HOUR: timedelta(hours=1),\n    Interval.DAILY: timedelta(days=1),\n}\n\n# Set weboscket timeout to 24 hour\nWEBSOCKET_TIMEOUT = 24 * 60 * 60\n\n\nclass BinanceLinearGateway(BaseGateway):\n    \"\"\"\n    The Binance linear trading gateway for VeighNa.\n\n    This gateway provides trading functionality for Binance USDT perpetual contracts\n    and delivery futures through their API.\n\n    Features:\n    1. Only support crossed position\n    2. Only support one-way mode\n    3. Provides market data, trading, and account management capabilities\n    \"\"\"\n\n    default_name: str = \"BINANCE_LINEAR\"\n\n    default_setting: dict = {\n        \"API Key\": \"\",\n        \"API Secret\": \"\",\n        \"Server\": [\"REAL\", \"TESTNET\"],\n        \"Kline Stream\": [\"False\", \"True\"],\n        \"Proxy Host\": \"\",\n        \"Proxy Port\": 0\n    }\n\n    exchanges: list[Exchange] = [Exchange.GLOBAL]\n\n    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:\n        \"\"\"\n        The init method of the gateway.\n\n        This method initializes the gateway components including REST API,\n        trading API, user data API, and market data API. It also sets up\n        the data structures for order and contract storage.\n\n        Parameters:\n            event_engine: the global event engine object of VeighNa\n            gateway_name: the unique name for identifying the gateway\n        \"\"\"\n        super().__init__(event_engine, gateway_name)\n\n        self.trade_api: TradeApi = TradeApi(self)\n        self.user_api: UserApi = UserApi(self)\n        self.md_api: MdApi = MdApi(self)\n        self.rest_api: RestApi = RestApi(self)\n\n        self.orders: dict[str, OrderData] = {}\n        self.symbol_contract_map: dict[str, ContractData] = {}\n        self.name_contract_map: dict[str, ContractData] = {}\n\n    def connect(self, setting: dict) -> None:\n        \"\"\"\n        Start server connections.\n\n        This method establishes connections to Binance servers\n        using the provided settings.\n\n        Parameters:\n            setting: A dictionary containing connection parameters including\n                    API credentials, server selection, and proxy configuration\n        \"\"\"\n        key: str = setting[\"API Key\"]\n        secret: str = setting[\"API Secret\"]\n        server: str = setting[\"Server\"]\n        kline_stream: bool = setting[\"Kline Stream\"] == \"True\"\n        proxy_host: str = setting[\"Proxy Host\"]\n        proxy_port: int = setting[\"Proxy Port\"]\n\n        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.trade_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.md_api.connect(server, kline_stream, proxy_host, proxy_port)\n\n        self.event_engine.register(EVENT_TIMER, self.process_timer_event)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This method forwards the subscription request to the market data API.\n\n        Parameters:\n            req: Subscription request object containing the symbol to subscribe\n        \"\"\"\n        self.md_api.subscribe(req)\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order.\n\n        This method forwards the order request to the trading API.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            str: The VeighNa order ID if successful, empty string if failed\n        \"\"\"\n        return self.trade_api.send_order(req)\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order.\n\n        This method forwards the cancellation request to the trading API.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        self.trade_api.cancel_order(req)\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        Not required since Binance provides websocket updates for account balances.\n        \"\"\"\n        pass\n\n    def query_position(self) -> None:\n        \"\"\"\n        Query current positions.\n\n        Not required since Binance provides websocket updates for positions.\n        \"\"\"\n        pass\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"\n        Query historical kline data.\n\n        This method forwards the history request to the REST API.\n\n        Parameters:\n            req: History request object containing query parameters\n\n        Returns:\n            list[BarData]: List of historical kline data bars\n        \"\"\"\n        return self.rest_api.query_history(req)\n\n    def close(self) -> None:\n        \"\"\"\n        Close server connections.\n\n        This method stops all API connections and releases resources.\n        \"\"\"\n        self.rest_api.stop()\n        self.user_api.stop()\n        self.md_api.stop()\n        self.trade_api.stop()\n\n    def process_timer_event(self, event: Event) -> None:\n        \"\"\"\n        Process timer task.\n\n        This function is called regularly by the event engine to perform scheduled tasks,\n        such as keeping the user stream alive.\n\n        Parameters:\n            event: Timer event object\n        \"\"\"\n        self.rest_api.keep_user_stream()\n\n        self.md_api.subscribe_new_channels()\n\n    def on_order(self, order: OrderData) -> None:\n        \"\"\"\n        Save a copy of order and then push to event engine.\n\n        Parameters:\n            order: Order data object\n        \"\"\"\n        self.orders[order.orderid] = copy(order)\n        super().on_order(order)\n\n    def get_order(self, orderid: str) -> OrderData | None:\n        \"\"\"\n        Get previously saved order by order id.\n\n        Parameters:\n            orderid: The ID of the order to retrieve\n\n        Returns:\n            Order data object if found, None otherwise\n        \"\"\"\n        return self.orders.get(orderid, None)\n\n    def on_contract(self, contract: ContractData) -> None:\n        \"\"\"\n        Save contract data in mappings and push to event engine.\n\n        Parameters:\n            contract: Contract data object\n        \"\"\"\n        self.symbol_contract_map[contract.symbol] = contract\n        self.name_contract_map[contract.name] = contract\n        super().on_contract(contract)\n\n    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by VeighNa symbol.\n\n        Parameters:\n            symbol: VeighNa symbol (e.g. \"BTC_SWAP_BINANCE\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.symbol_contract_map.get(symbol, None)\n\n    def get_contract_by_name(self, name: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by exchange symbol name.\n\n        Parameters:\n            name: Exchange symbol name (e.g. \"BTCUSDT\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.name_contract_map.get(name, None)\n\n\nclass RestApi(RestClient):\n    \"\"\"\n    The REST API of BinanceLinearGateway.\n\n    This class handles HTTP requests to Binance API endpoints, including:\n    - Authentication and signature generation\n    - Contract information queries\n    - Account and position queries\n    - Order management\n    - Historical data queries\n    - User data stream management\n    \"\"\"\n\n    def __init__(self, gateway: BinanceLinearGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the REST API with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceLinearGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.user_api: UserApi = self.gateway.user_api\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n\n        self.user_stream_key: str = \"\"\n        self.keep_alive_count: int = 0\n        self.time_offset: int = 0\n\n        self.order_count: int = 1_000_000\n        self.order_prefix: str = \"\"\n\n    def sign(self, request: Request) -> Request:\n        \"\"\"\n        Standard callback for signing a request.\n\n        This method adds the necessary authentication parameters and signature\n        to requests that require API key authentication.\n\n        It handles:\n        1. Path construction with query parameters\n        2. Timestamp generation with server time offset adjustment\n        3. HMAC-SHA256 signature generation\n        4. Required authentication headers\n\n        Parameters:\n            request: Request object to be signed\n\n        Returns:\n            Request: Modified request with authentication parameters\n        \"\"\"\n        # Construct path with query parameters if they exist\n        if request.params:\n            path: str = request.path + \"?\" + urllib.parse.urlencode(request.params)\n        else:\n            request.params = {}\n            path = request.path\n\n        # Get current timestamp in milliseconds\n        timestamp: int = int(time.time() * 1000)\n\n        # Adjust timestamp based on time offset with server\n        if self.time_offset > 0:\n            timestamp -= abs(self.time_offset)\n        elif self.time_offset < 0:\n            timestamp += abs(self.time_offset)\n\n        # Add timestamp to request parameters\n        request.params[\"timestamp\"] = timestamp\n\n        # Generate signature using HMAC SHA256\n        query: str = urllib.parse.urlencode(sorted(request.params.items()))\n        signature: str = hmac.new(\n            self.secret,\n            query.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n\n        # Append signature to query string\n        query += f\"&signature={signature}\"\n        path = request.path + \"?\" + query\n\n        # Update request with signed path and clear params/data\n        request.path = path\n        request.params = {}\n        request.data = {}\n\n        # Add required headers for API authentication\n        request.headers = {\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\n            \"Accept\": \"application/json\",\n            \"X-MBX-APIKEY\": self.key,\n            \"Connection\": \"close\"\n        }\n\n        return request\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"Start server connection\"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_REST_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n        self.gateway.write_log(\"REST API started\")\n\n        self.query_time()\n\n    def query_time(self) -> None:\n        \"\"\"\n        Query server time to calculate local time offset.\n\n        This function sends a request to get the exchange server time,\n        which is used to calculate the local time offset for timestamp synchronization.\n        \"\"\"\n        path: str = \"/fapi/v1/time\"\n\n        self.add_request(\n            \"GET\",\n            path,\n            callback=self.on_query_time\n        )\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        This function sends a request to get the account balance information,\n        including wallet balance, available balance, and margin.\n        \"\"\"\n        path: str = \"/fapi/v3/account\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_account,\n        )\n\n    def query_position(self) -> None:\n        \"\"\"\n        Query holding positions.\n\n        This function sends a request to get current position data,\n        including position amount, entry price, and unrealized profit/loss.\n        \"\"\"\n        path: str = \"/fapi/v3/positionRisk\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_position,\n        )\n\n    def query_order(self) -> None:\n        \"\"\"\n        Query open orders.\n\n        This function sends a request to get all active orders\n        that have not been fully filled or cancelled.\n        \"\"\"\n        path: str = \"/fapi/v1/openOrders\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_order,\n        )\n\n    def query_contract(self) -> None:\n        \"\"\"\n        Query available contracts.\n\n        This function sends a request to get exchange information,\n        including all available trading instruments, their precision,\n        and trading rules.\n        \"\"\"\n        path: str = \"/fapi/v1/exchangeInfo\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_contract,\n        )\n\n    def start_user_stream(self) -> None:\n        \"\"\"\n        Create listen key for user stream.\n\n        This function sends a request to create a listen key which is\n        required to establish a user data websocket connection.\n        \"\"\"\n        path: str = \"/fapi/v1/listenKey\"\n\n        self.add_request(\n            method=\"POST\",\n            path=path,\n            callback=self.on_start_user_stream,\n        )\n\n    def keep_user_stream(self) -> None:\n        \"\"\"\n        Extend listen key validity.\n\n        This function sends a request to keep the listen key active,\n        which is required to maintain the user data websocket connection.\n        The listen key will expire after 60 minutes if not refreshed.\n        \"\"\"\n        if not self.user_stream_key:\n            return\n\n        self.keep_alive_count += 1\n        if self.keep_alive_count < 600:\n            return\n        self.keep_alive_count = 0\n\n        params: dict = {\"listenKey\": self.user_stream_key}\n\n        path: str = \"/fapi/v1/listenKey\"\n\n        self.add_request(\n            method=\"PUT\",\n            path=path,\n            callback=self.on_keep_user_stream,\n            params=params,\n            on_error=self.on_keep_user_stream_error\n        )\n\n    def on_query_time(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of server time query.\n\n        This function processes the server time response and calculates\n        the time offset between local and server time, which is used for\n        request timestamp synchronization.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        local_time: int = int(time.time() * 1000)\n        server_time: int = int(data[\"serverTime\"])\n        self.time_offset = local_time - server_time\n\n        self.gateway.write_log(f\"Server time updated, local offset: {self.time_offset}ms\")\n\n        self.query_contract()\n\n    def on_query_account(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of account balance query.\n\n        This function processes the account balance response and\n        creates AccountData objects for each asset in the account.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for asset in data[\"assets\"]:\n            account: AccountData = AccountData(\n                accountid=asset[\"asset\"],\n                balance=float(asset[\"walletBalance\"]),\n                frozen=float(asset[\"maintMargin\"]),\n                gateway_name=self.gateway_name\n            )\n\n            self.gateway.on_account(account)\n\n        self.gateway.write_log(\"Account data received\")\n\n    def on_query_position(self, data: list, request: Request) -> None:\n        \"\"\"\n        Callback of holding positions query.\n\n        This function processes the position data response and\n        creates PositionData objects for each position held.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data:\n            name: str = d[\"symbol\"]\n            contract: ContractData | None = self.gateway.get_contract_by_name(name)\n            if not contract:\n                continue\n\n            position: PositionData = PositionData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                direction=Direction.NET,\n                volume=float(d[\"positionAmt\"]),\n                price=float(d[\"entryPrice\"]),\n                pnl=float(d[\"unRealizedProfit\"]),\n                gateway_name=self.gateway_name,\n            )\n\n            self.gateway.on_position(position)\n\n        self.gateway.write_log(\"Position data received\")\n\n    def on_query_order(self, data: list, request: Request) -> None:\n        \"\"\"\n        Callback of open orders query.\n\n        This function processes the open orders response and\n        creates OrderData objects for each active order.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(d[\"symbol\"])\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT[d[\"status\"]],\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"Order data received\")\n\n    def on_query_contract(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of available contracts query.\n\n        This function processes the exchange info response and\n        creates ContractData objects for each trading instrument.\n        It handles different contract types and extracts trading rules\n        like price tick, minimum/maximum volumes from filters.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            product: Product | None = PRODUCT_BINANCE2VT.get(d[\"contractType\"], None)\n            if product == Product.SWAP:\n                symbol: str = d[\"symbol\"] + \"_SWAP_BINANCE\"\n            elif product == Product.FUTURES:\n                symbol = d[\"symbol\"] + \"_BINANCE\"\n            else:\n                continue\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=product,\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=False\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"Contract data received\")\n\n        # Query private data after time offset is calculated\n        if self.key and self.secret:\n            self.query_order()\n            self.query_account()\n            self.query_position()\n            self.start_user_stream()\n\n    def on_start_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Successful callback of start_user_stream.\n\n        This function processes the listen key response and initializes\n        the user data websocket connection with the provided key.\n\n        Parameters:\n            data: Response data from the server containing the listen key\n            request: Original request object\n        \"\"\"\n        self.user_stream_key = data[\"listenKey\"]\n        self.keep_alive_count = 0\n\n        params: str = urllib.parse.urlencode(\n            {\n                \"listenKey\": self.user_stream_key,\n                \"events\": \"ORDER_TRADE_UPDATE/ACCOUNT_UPDATE\",\n            },\n            safe=\"/\"\n        )\n\n        if self.server == \"REAL\":\n            url = f\"{REAL_USER_HOST}?{params}\"\n        else:\n            url = f\"{TESTNET_USER_HOST}?{params}\"\n\n        self.user_api.connect(url, self.proxy_host, self.proxy_port)\n\n    def on_keep_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Successful callback of keep_user_stream.\n\n        This function handles the successful response of the listen key\n        refresh request. No action is needed on success.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        pass\n\n    def on_keep_user_stream_error(self, exception_type: type, exception_value: Exception, tb: Any, request: Request) -> None:\n        \"\"\"\n        Error callback of keep_user_stream.\n\n        This function handles errors from the listen key refresh request.\n        Timeout exceptions are ignored as they are common and non-critical.\n\n        Parameters:\n            exception_type: Type of the exception\n            exception_value: Exception instance\n            tb: Traceback object\n            request: Original request object\n        \"\"\"\n        if not issubclass(exception_type, TimeoutError):        # Ignore timeout exception\n            self.on_error(exception_type, exception_value, tb, request)\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"Query kline history data\"\"\"\n        # Check if the contract and interval exist\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            return []\n\n        if not req.interval:\n            return []\n\n        # Prepare history list\n        history: list[BarData] = []\n        limit: int = 1500\n\n        # Convert start time to milliseconds\n        start_time: int = int(datetime.timestamp(req.start))\n\n        while True:\n            # Create query parameters\n            params: dict = {\n                \"symbol\": contract.name,\n                \"interval\": INTERVAL_VT2BINANCE[req.interval],\n                \"limit\": limit\n            }\n\n            params[\"startTime\"] = start_time * 1000\n            path: str = \"/fapi/v1/klines\"\n            if req.end:\n                end_time = int(datetime.timestamp(req.end))\n                params[\"endTime\"] = end_time * 1000     # Convert to milliseconds\n\n            resp: Response = self.request(\n                \"GET\",\n                path=path,\n                params=params\n            )\n\n            # Break the loop if request failed\n            if resp.status_code // 100 != 2:\n                msg: str = f\"Query kline history failed, status code: {resp.status_code}, message: {resp.text}\"\n                self.gateway.write_log(msg)\n                break\n            else:\n                data: dict = resp.json()\n                if not data:\n                    msg = f\"No kline history data is received, start time: {start_time}\"\n                    self.gateway.write_log(msg)\n                    break\n\n                buf: list[BarData] = []\n\n                for row in data:\n                    bar: BarData = BarData(\n                        symbol=req.symbol,\n                        exchange=req.exchange,\n                        datetime=generate_datetime(row[0]),\n                        interval=req.interval,\n                        volume=float(row[5]),\n                        turnover=float(row[7]),\n                        open_price=float(row[1]),\n                        high_price=float(row[2]),\n                        low_price=float(row[3]),\n                        close_price=float(row[4]),\n                        gateway_name=self.gateway_name\n                    )\n                    bar.extra = {\n                        \"trade_count\": int(row[8]),\n                        \"active_volume\": float(row[9]),\n                        \"active_turnover\": float(row[10]),\n                    }\n                    buf.append(bar)\n\n                begin: datetime = buf[0].datetime\n                end: datetime = buf[-1].datetime\n\n                history.extend(buf)\n                msg = f\"Query kline history finished, {req.symbol} - {req.interval.value}, {begin} - {end}\"\n                self.gateway.write_log(msg)\n\n                next_start_dt = bar.datetime + TIMEDELTA_MAP[req.interval]\n                next_start_time = int(datetime.timestamp(next_start_dt))\n\n                # Break the loop if the latest data received\n                if (\n                    len(data) < limit\n                    or (req.end and next_start_dt >= req.end)\n                ):\n                    break\n\n                # Update query start time\n                start_time = next_start_time\n\n            # Wait to meet request flow limit\n            sleep(0.5)\n\n        # Remove the unclosed kline\n        if history:\n            history.pop(-1)\n\n        return history\n\n\nclass UserApi(WebsocketClient):\n    \"\"\"\n    The user data websocket API of BinanceLinearGateway.\n\n    This class handles user data events from Binance through websocket connection.\n    It processes real-time updates for:\n    - Account balance changes\n    - Position updates\n    - Order status changes\n    - Trade executions\n    \"\"\"\n\n    def __init__(self, gateway: BinanceLinearGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceLinearGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method establishes a websocket connection to Binance user data stream.\n\n        Parameters:\n            url: Websocket endpoint URL with listen key\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the websocket connection to the server\n        is successfully established. It logs the connection status.\n        \"\"\"\n        self.gateway.write_log(\"User API connected\")\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of data update.\n\n        This function processes websocket messages from the user data stream.\n        It handles different event types including account updates, order updates,\n        and listen key expiration.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        match packet[\"e\"]:\n            case \"ACCOUNT_UPDATE\":\n                self.on_account(packet)\n            case \"ORDER_TRADE_UPDATE\":\n                self.on_order(packet)\n            case \"listenKeyExpired\":\n                self.on_listen_key_expired()\n\n    def on_listen_key_expired(self) -> None:\n        \"\"\"\n        Callback of listen key expired.\n\n        This function is called when the exchange notifies that the listen key\n        has expired. It will log a message and disconnect the websocket connection.\n        \"\"\"\n        self.gateway.write_log(\"Listen key expired\")\n\n    def on_account(self, packet: dict) -> None:\n        \"\"\"\n        Callback of account balance and holding position update.\n\n        This function processes the account update event from the user data stream,\n        including balance changes and position updates.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        for acc_data in packet[\"a\"][\"B\"]:\n            account: AccountData = AccountData(\n                accountid=acc_data[\"a\"],\n                balance=float(acc_data[\"wb\"]),\n                frozen=float(acc_data[\"wb\"]) - float(acc_data[\"cw\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        for pos_data in packet[\"a\"][\"P\"]:\n            if pos_data[\"ps\"] == \"BOTH\":\n                volume = pos_data[\"pa\"]\n                if \".\" in volume:\n                    volume = float(volume)\n                else:\n                    volume = int(volume)\n\n                name: str = pos_data[\"s\"]\n                contract: ContractData | None = self.gateway.get_contract_by_name(name)\n                if not contract:\n                    continue\n\n                position: PositionData = PositionData(\n                    symbol=contract.symbol,\n                    exchange=Exchange.GLOBAL,\n                    direction=Direction.NET,\n                    volume=volume,\n                    price=float(pos_data[\"ep\"]),\n                    pnl=float(pos_data[\"up\"]),\n                    gateway_name=self.gateway_name,\n                )\n                self.gateway.on_position(position)\n\n    def on_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of order and trade update.\n\n        This function processes the order update event from the user data stream,\n        including order status changes and trade executions.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        ord_data: dict = packet[\"o\"]\n\n        # Filter unsupported order type\n        key: tuple[str, str] = (ord_data[\"o\"], ord_data[\"f\"])\n        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n        if not order_type:\n            return\n\n        # Filter unsupported symbol\n        name: str = ord_data[\"s\"]\n        contract: ContractData | None = self.gateway.get_contract_by_name(name)\n        if not contract:\n            return\n\n        # Create and push order\n        order: OrderData = OrderData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=str(ord_data[\"c\"]),\n            type=order_type,\n            direction=DIRECTION_BINANCE2VT[ord_data[\"S\"]],\n            price=float(ord_data[\"p\"]),\n            volume=float(ord_data[\"q\"]),\n            traded=float(ord_data[\"z\"]),\n            status=STATUS_BINANCE2VT[ord_data[\"X\"]],\n            datetime=generate_datetime(packet[\"E\"]),\n            gateway_name=self.gateway_name,\n        )\n\n        self.gateway.on_order(order)\n\n        # Round trade volume to meet step size\n        trade_volume: float = float(ord_data[\"l\"])\n        trade_volume = round_to(trade_volume, contract.min_volume)\n        if not trade_volume:\n            return\n\n        # Create and push trade\n        trade: TradeData = TradeData(\n            symbol=order.symbol,\n            exchange=order.exchange,\n            orderid=order.orderid,\n            tradeid=ord_data[\"t\"],\n            direction=order.direction,\n            price=float(ord_data[\"L\"]),\n            volume=trade_volume,\n            datetime=generate_datetime(ord_data[\"T\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_trade(trade)\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the websocket connection is closed.\n        It logs the disconnection details and attempts to restart the user stream.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"User API disconnected, code: {status_code}, msg: {msg}\")\n        self.gateway.rest_api.start_user_stream()\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the websocket connection.\n        It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"User API exception: {e}\")\n\n\nclass MdApi(WebsocketClient):\n    \"\"\"\n    The market data websocket API of BinanceLinearGateway.\n\n    This class handles market data from Binance through websocket connection.\n    It processes real-time updates for:\n    - Tickers (24hr statistics)\n    - Order book depth (10 levels)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinanceLinearGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceLinearGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.public_api: PublicApi = PublicApi(self)\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n        self.new_public_channels: list[str] = []\n        self.new_market_channels: list[str] = []\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method establishes a websocket connection to Binance market data stream.\n\n        Parameters:\n            server: Server type (\"REAL\" or \"TESTNET\")\n            kline_stream: Whether to include kline data stream\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n            self.public_api.connect(REAL_PUBLIC_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n            self.public_api.connect(TESTNET_PUBLIC_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n    def stop(self) -> None:\n        \"\"\"Stop market data websocket connections.\"\"\"\n        self.public_api.stop()\n        super().stop()\n\n    def get_public_channels(self, contract: ContractData) -> list[str]:\n        \"\"\"Generate public market data channels for a contract.\"\"\"\n        return [f\"{contract.name.lower()}@depth10\"]\n\n    def get_market_channels(self, contract: ContractData) -> list[str]:\n        \"\"\"Generate market data channels for a contract.\"\"\"\n        channels: list[str] = [f\"{contract.name.lower()}@ticker\"]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        if contract.product == Product.SWAP:\n            channels.append(f\"{contract.name.lower()}@markPrice\")\n\n        return channels\n\n    def send_subscribe_packet(self, api: WebsocketClient, channels: list[str]) -> None:\n        \"\"\"Send a subscribe packet through the given websocket client.\"\"\"\n        if not channels:\n            return\n\n        self.reqid += 1\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": channels,\n            \"id\": self.reqid\n        }\n        api.send_packet(packet)\n\n    def resubscribe_market_channels(self) -> None:\n        \"\"\"Resubscribe market channels after reconnect.\"\"\"\n        channels: list[str] = []\n\n        for symbol in self.ticks.keys():\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)\n            if not contract:\n                continue\n\n            channels.extend(self.get_market_channels(contract))\n\n        self.send_subscribe_packet(self, channels)\n\n    def resubscribe_public_channels(self) -> None:\n        \"\"\"Resubscribe public channels after reconnect.\"\"\"\n        channels: list[str] = []\n\n        for symbol in self.ticks.keys():\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)\n            if not contract:\n                continue\n\n            channels.extend(self.get_public_channels(contract))\n\n        self.send_subscribe_packet(self.public_api, channels)\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the market data websocket connection\n        is successfully established. It logs the connection status and\n        resubscribes to previously subscribed market data channels.\n        \"\"\"\n        self.gateway.write_log(\"MD API connected\")\n\n        # Resubscribe market data\n        if self.ticks:\n            self.resubscribe_market_channels()\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This function sends subscription requests for ticker and depth data\n        for the specified trading instrument. If kline_stream is enabled,\n        it will also subscribe to 1-minute kline data.\n\n        Parameters:\n            req: Subscription request object containing symbol information\n        \"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        self.reqid += 1\n\n        # Initialize tick object\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        self.new_public_channels.extend(self.get_public_channels(contract))\n        self.new_market_channels.extend(self.get_market_channels(contract))\n\n    def subscribe_new_channels(self) -> None:\n        \"\"\"\n        Update timer event.\n\n        This function sends subscription requests for new channels\n        to the market data websocket server.\n        \"\"\"\n        self.send_subscribe_packet(self, self.new_market_channels)\n        self.send_subscribe_packet(self.public_api, self.new_public_channels)\n\n        self.new_market_channels = []\n        self.new_public_channels = []\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of market data update.\n\n        This function processes different types of market data updates,\n        including ticker, depth, and kline data. It updates the corresponding\n        TickData object and pushes updates to the gateway.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        stream: str | None = packet.get(\"stream\", None)\n        if not stream:\n            return\n\n        data: dict = packet[\"data\"]\n\n        name, channel = stream.split(\"@\", 1)\n        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper())\n        if not contract:\n            return\n        tick: TickData = self.ticks[contract.symbol]\n\n        if channel == \"ticker\":\n            tick.volume = float(data[\"v\"])\n            tick.turnover = float(data[\"q\"])\n            tick.open_price = float(data[\"o\"])\n            tick.high_price = float(data[\"h\"])\n            tick.low_price = float(data[\"l\"])\n            tick.last_price = float(data[\"c\"])\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        elif channel == \"depth10\":\n            bids: list = data[\"b\"]\n            for n in range(min(10, len(bids))):\n                price, volume = bids[n]\n                tick.__setattr__(\"bid_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"bid_volume_\" + str(n + 1), float(volume))\n\n            asks: list = data[\"a\"]\n            for n in range(min(10, len(asks))):\n                price, volume = asks[n]\n                tick.__setattr__(\"ask_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"ask_volume_\" + str(n + 1), float(volume))\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        elif channel == \"markPrice\":\n            if tick.extra is None:\n                tick.extra = {}\n            tick.extra[\"funding_rate\"] = float(data[\"r\"])\n            tick.extra[\"funding_time\"] = int(data[\"T\"])\n        else:\n            kline_data: dict = data[\"k\"]\n\n            # Check if bar is closed\n            bar_ready: bool = kline_data.get(\"x\", False)\n            if not bar_ready:\n                return\n\n            if tick.extra is None:\n                tick.extra = {}\n\n            dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n            tick.extra[\"bar\"] = BarData(\n                symbol=name.upper(),\n                exchange=Exchange.GLOBAL,\n                datetime=dt.replace(second=0, microsecond=0),\n                interval=Interval.MINUTE,\n                volume=float(kline_data[\"v\"]),\n                turnover=float(kline_data[\"q\"]),\n                open_price=float(kline_data[\"o\"]),\n                high_price=float(kline_data[\"h\"]),\n                low_price=float(kline_data[\"l\"]),\n                close_price=float(kline_data[\"c\"]),\n                gateway_name=self.gateway_name\n            )\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the market data websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the market data\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"MD API exception: {e}\")\n\n\nclass PublicApi(WebsocketClient):\n    \"\"\"Public market data websocket connection for high-frequency streams.\"\"\"\n\n    def __init__(self, md_api: MdApi) -> None:\n        super().__init__()\n        self.md_api: MdApi = md_api\n        self.gateway: BinanceLinearGateway = md_api.gateway\n\n    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"Start public market data websocket connection.\"\"\"\n        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when public websocket is connected.\"\"\"\n        self.gateway.write_log(\"Public API connected\")\n        if self.md_api.ticks:\n            self.md_api.resubscribe_public_channels()\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Forward packets to the shared market data parser.\"\"\"\n        self.md_api.on_packet(packet)\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when public websocket is disconnected.\"\"\"\n        self.gateway.write_log(f\"MD Public API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when public websocket raises an exception.\"\"\"\n        self.gateway.write_log(f\"MD Public API exception: {e}\")\n\n\nclass TradeApi(WebsocketClient):\n    \"\"\"\n    The trading websocket API of BinanceLinearGateway.\n\n    This class handles trading operations with Binance through websocket connection.\n    It provides functionality for:\n    - Order placement\n    - Order cancellation\n    - Request authentication and signature generation\n    \"\"\"\n\n    def __init__(self, gateway: BinanceLinearGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceLinearGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n        self.proxy_port: int = 0\n        self.proxy_host: str = \"\"\n        self.server: str = \"\"\n\n        self.reqid: int = 0\n        self.order_count: int = 0\n        self.order_prefix: str = \"\"\n\n        self.reqid_callback_map: dict[int, Callable] = {}\n        self.reqid_order_map: dict[int, OrderData] = {}\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method initializes the API credentials and establishes\n        a websocket connection to Binance trading API.\n\n        Parameters:\n            key: API Key for authentication\n            secret: API Secret for request signing\n            server: Server type (\"REAL\" or \"TESTNET\")\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_TRADE_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_TRADE_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n    def sign(self, params: dict) -> None:\n        \"\"\"\n        Generate the signature for the request.\n\n        This function creates an HMAC-SHA256 signature required for\n        authenticated API requests to Binance.\n\n        Parameters:\n            params: Dictionary containing the parameters to be signed\n        \"\"\"\n        timestamp: int = int(time.time() * 1000)\n        params[\"timestamp\"] = timestamp\n\n        payload: str = \"&\".join([f\"{k}={v}\" for k, v in sorted(params.items())])\n        signature: str = hmac.new(\n            self.secret,\n            payload.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n        params[\"signature\"] = signature\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order to Binance.\n\n        This function creates and sends a new order request to the exchange.\n        It handles different order types including market, limit, and stop orders.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            vt_orderid: The VeighNa order ID (gateway_name.orderid) if successful,\n                       empty string otherwise\n        \"\"\"\n        # Get contract\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to send order, symbol not found: {req.symbol}\")\n            return \"\"\n\n        # Generate new order id\n        self.order_count += 1\n        orderid: str = self.order_prefix + str(self.order_count)\n\n        # Push a submitting order event\n        order: OrderData = req.create_order_data(\n            orderid,\n            self.gateway_name\n        )\n        self.gateway.on_order(order)\n\n        # Create order parameters\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"side\": DIRECTION_VT2BINANCE[req.direction],\n            \"quantity\": format_float(req.volume),\n            \"newClientOrderId\": orderid,\n        }\n\n        if req.type == OrderType.MARKET:\n            params[\"type\"] = \"MARKET\"\n        elif req.type == OrderType.STOP:\n            params[\"type\"] = \"STOP_MARKET\"\n            params[\"stopPrice\"] = format_float(req.price)\n        else:\n            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]\n            params[\"type\"] = order_type\n            params[\"timeInForce\"] = time_condition\n            params[\"price\"] = format_float(req.price)\n\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_send_order\n        self.reqid_order_map[self.reqid] = order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.place\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n        return order.vt_orderid\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order on Binance.\n\n        This function sends a request to cancel an existing order on the exchange.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to cancel order, symbol not found: {req.symbol}\")\n            return\n\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"origClientOrderId\": req.orderid\n        }\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_cancel_order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.cancel\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the trading websocket connection\n        is successfully established. It logs the connection status.\n        \"\"\"\n        self.gateway.write_log(\"Trade API connected\")\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the trading websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"Trade API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of data update.\n\n        This function processes responses from the trading websocket API.\n        It routes the response to the appropriate callback function based\n        on the request ID.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        reqid: int = packet.get(\"id\", 0)\n        callback: Callable | None = self.reqid_callback_map.get(reqid, None)\n        if callback:\n            callback(packet)\n\n    def on_send_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of send order.\n\n        This function processes the response to an order placement request.\n        It handles errors by logging the details and updating the order status.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n        msg: str = f\"Order rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n        reqid: int = packet.get(\"id\", 0)\n        order: OrderData | None = self.reqid_order_map.get(reqid, None)\n        if order:\n            order.status = Status.REJECTED\n            self.gateway.on_order(order)\n\n    def on_cancel_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of cancel order.\n\n        This function processes the response to an order cancellation request.\n        It handles errors by logging the details.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n        msg: str = f\"Cancel rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the trading\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"Trade API exception: {e}\")\n\n\ndef generate_datetime(timestamp: float) -> datetime:\n    \"\"\"\n    Generate datetime object from Binance timestamp.\n\n    This function converts a Binance millisecond timestamp to a datetime object\n    with UTC timezone.\n\n    Parameters:\n        timestamp: Binance timestamp in milliseconds\n\n    Returns:\n        Datetime object with UTC timezone\n    \"\"\"\n    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)\n    return dt\n\n\ndef format_float(f: float) -> str:\n    \"\"\"\n    Convert float number to string with correct precision.\n\n    This function formats floating point numbers to avoid precision errors\n    when sending requests to Binance.\n\n    Parameters:\n        f: The floating point number to format\n\n    Returns:\n        Formatted string representation of the number\n\n    Note:\n        Fixes potential error -1111: Parameter \"quantity\" has too much precision\n    \"\"\"\n    return format_float_positional(f, trim=\"-\")\n"
  },
  {
    "path": "vnpy_binance/portfolio_gateway.py",
    "content": "\"\"\"\nBinance Portfolio Margin Gateway for VeighNa.\n\nThis module provides trading functionality for Binance Portfolio Margin account,\nwhich supports unified account management across USDT-M futures, Coin-M futures,\nand cross margin trading.\n\"\"\"\n\nimport hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom enum import Enum\nfrom typing import Any\nfrom time import sleep\nfrom datetime import datetime, timedelta\n\nfrom numpy import format_float_positional\n\nfrom vnpy.event import Event, EventEngine\nfrom vnpy.trader.constant import (\n    Direction,\n    Exchange,\n    Product,\n    Status,\n    OrderType,\n    Interval\n)\nfrom vnpy.trader.gateway import BaseGateway\nfrom vnpy.trader.object import (\n    TickData,\n    OrderData,\n    TradeData,\n    AccountData,\n    ContractData,\n    PositionData,\n    BarData,\n    OrderRequest,\n    CancelRequest,\n    SubscribeRequest,\n    HistoryRequest\n)\nfrom vnpy.trader.event import EVENT_TIMER\nfrom vnpy.trader.utility import round_to, ZoneInfo\nfrom vnpy_rest import Request, RestClient, Response\nfrom vnpy_websocket import WebsocketClient\n\n\n# Timezone constant\nUTC_TZ = ZoneInfo(\"UTC\")\n\n# Real server hosts\nREAL_REST_HOST: str = \"https://papi.binance.com\"\nREAL_UM_REST_HOST: str = \"https://fapi.binance.com\"\nREAL_CM_REST_HOST: str = \"https://dapi.binance.com\"\nREAL_MARGIN_REST_HOST: str = \"https://api.binance.com\"\n\nREAL_USER_HOST: str = \"wss://fstream.binance.com/pm/ws/\"\nREAL_UM_PUBLIC_HOST: str = \"wss://fstream.binance.com/public/stream\"\nREAL_UM_MARKET_HOST: str = \"wss://fstream.binance.com/market/stream\"\nREAL_CM_DATA_HOST: str = \"wss://dstream.binance.com/stream\"\nREAL_MARGIN_DATA_HOST: str = \"wss://stream.binance.com:9443/stream\"\n\n# Testnet server hosts\nTESTNET_REST_HOST: str = \"https://testnet.binancefuture.com\"\nTESTNET_UM_REST_HOST: str = \"https://testnet.binancefuture.com\"\nTESTNET_CM_REST_HOST: str = \"https://testnet.binancefuture.com\"\nTESTNET_MARGIN_REST_HOST: str = \"https://testnet.binance.vision\"\n\nTESTNET_USER_HOST: str = \"wss://stream.binancefuture.com/ws/\"\nTESTNET_UM_PUBLIC_HOST: str = \"wss://fstream.binancefuture.com/public/stream\"\nTESTNET_UM_MARKET_HOST: str = \"wss://fstream.binancefuture.com/market/stream\"\nTESTNET_CM_DATA_HOST: str = \"wss://dstream.binancefuture.com/stream\"\nTESTNET_MARGIN_DATA_HOST: str = \"wss://testnet.binance.vision/stream\"\n\n# Order status map\nSTATUS_BINANCE2VT: dict[str, Status] = {\n    \"NEW\": Status.NOTTRADED,\n    \"PARTIALLY_FILLED\": Status.PARTTRADED,\n    \"FILLED\": Status.ALLTRADED,\n    \"CANCELED\": Status.CANCELLED,\n    \"REJECTED\": Status.REJECTED,\n    \"EXPIRED\": Status.CANCELLED\n}\n\n# Order type map\nORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {\n    OrderType.LIMIT: (\"LIMIT\", \"GTC\"),\n    OrderType.MARKET: (\"MARKET\", \"GTC\"),\n    OrderType.FAK: (\"LIMIT\", \"IOC\"),\n    OrderType.FOK: (\"LIMIT\", \"FOK\"),\n}\nORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {\n    v: k for k, v in ORDERTYPE_VT2BINANCE.items()\n}\n\n# Direction map\nDIRECTION_VT2BINANCE: dict[Direction, str] = {\n    Direction.LONG: \"BUY\",\n    Direction.SHORT: \"SELL\"\n}\nDIRECTION_BINANCE2VT: dict[str, Direction] = {\n    v: k for k, v in DIRECTION_VT2BINANCE.items()\n}\n\n# Product map for futures\nPRODUCT_BINANCE2VT: dict[str, Product] = {\n    \"PERPETUAL\": Product.SWAP,\n    \"PERPETUAL_DELIVERING\": Product.SWAP,\n    \"TRADIFI_PERPETUAL\": Product.SWAP,\n    \"CURRENT_MONTH\": Product.FUTURES,\n    \"NEXT_MONTH\": Product.FUTURES,\n    \"CURRENT_QUARTER\": Product.FUTURES,\n    \"NEXT_QUARTER\": Product.FUTURES,\n}\n\n# Kline interval map\nINTERVAL_VT2BINANCE: dict[Interval, str] = {\n    Interval.MINUTE: \"1m\",\n    Interval.HOUR: \"1h\",\n    Interval.DAILY: \"1d\",\n}\n\n# Timedelta map\nTIMEDELTA_MAP: dict[Interval, timedelta] = {\n    Interval.MINUTE: timedelta(minutes=1),\n    Interval.HOUR: timedelta(hours=1),\n    Interval.DAILY: timedelta(days=1),\n}\n\n# Set websocket timeout to 24 hours\nWEBSOCKET_TIMEOUT = 24 * 60 * 60\n\n\nclass MarketType(Enum):\n    \"\"\"Market type for portfolio margin account\"\"\"\n    UM = \"um\"           # USDT-M Futures\n    CM = \"cm\"           # Coin-M Futures\n    MARGIN = \"margin\"   # Cross Margin\n\n\ndef get_market_type(symbol: str) -> MarketType:\n    \"\"\"\n    Determine market type from symbol.\n\n    Parameters:\n        symbol: VeighNa symbol string\n\n    Returns:\n        MarketType enum value\n    \"\"\"\n    if \"_SWAP_BINANCE\" in symbol:\n        base = symbol.replace(\"_SWAP_BINANCE\", \"\")\n        if base.endswith(\"USDT\") or base.endswith(\"USDC\"):\n            return MarketType.UM\n        else:\n            return MarketType.CM\n    elif \"_SPOT_BINANCE\" in symbol:\n        return MarketType.MARGIN\n    elif \"_BINANCE\" in symbol:\n        # Delivery futures\n        base = symbol.split(\"_\")[0]\n        if \"USDT\" in base or \"USDC\" in base:\n            return MarketType.UM\n        else:\n            return MarketType.CM\n    return MarketType.UM\n\n\ndef generate_datetime(timestamp: float) -> datetime:\n    \"\"\"\n    Generate datetime object from Binance timestamp.\n\n    Parameters:\n        timestamp: Binance timestamp in milliseconds\n\n    Returns:\n        Datetime object with UTC timezone\n    \"\"\"\n    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)\n    return dt\n\n\ndef format_float(f: float) -> str:\n    \"\"\"\n    Convert float number to string with correct precision.\n\n    Parameters:\n        f: The floating point number to format\n\n    Returns:\n        Formatted string representation of the number\n    \"\"\"\n    return format_float_positional(f, trim=\"-\")\n\n\nclass BinancePortfolioGateway(BaseGateway):\n    \"\"\"\n    The Binance portfolio margin trading gateway for VeighNa.\n\n    This gateway provides unified trading functionality for Binance portfolio margin account,\n    which supports USDT-M futures, Coin-M futures, and cross margin trading.\n\n    Features:\n    1. Unified account management across all markets\n    2. Real-time market data with optional kline streaming\n    3. Only support crossed position and one-way mode\n    \"\"\"\n\n    default_name: str = \"BINANCE_PORTFOLIO\"\n\n    default_setting: dict = {\n        \"API Key\": \"\",\n        \"API Secret\": \"\",\n        \"Server\": [\"REAL\", \"TESTNET\"],\n        \"Kline Stream\": [\"False\", \"True\"],\n        \"Proxy Host\": \"\",\n        \"Proxy Port\": 0\n    }\n\n    exchanges: list[Exchange] = [Exchange.GLOBAL]\n\n    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:\n        \"\"\"\n        The init method of the gateway.\n\n        Parameters:\n            event_engine: the global event engine object of VeighNa\n            gateway_name: the unique name for identifying the gateway\n        \"\"\"\n        super().__init__(event_engine, gateway_name)\n\n        self.user_api: UserApi = UserApi(self)\n        self.um_md_api: UmMdApi = UmMdApi(self)\n        self.cm_md_api: CmMdApi = CmMdApi(self)\n        self.margin_md_api: MarginMdApi = MarginMdApi(self)\n        self.rest_api: RestApi = RestApi(self)\n\n        self.orders: dict[str, OrderData] = {}\n        self.symbol_contract_map: dict[str, ContractData] = {}\n        self.um_name_contract_map: dict[str, ContractData] = {}\n        self.cm_name_contract_map: dict[str, ContractData] = {}\n        self.margin_name_contract_map: dict[str, ContractData] = {}\n\n    def connect(self, setting: dict) -> None:\n        \"\"\"\n        Start server connections.\n\n        Parameters:\n            setting: A dictionary containing connection parameters\n        \"\"\"\n        key: str = setting[\"API Key\"]\n        secret: str = setting[\"API Secret\"]\n        server: str = setting[\"Server\"]\n        kline_stream: bool = setting[\"Kline Stream\"] == \"True\"\n        proxy_host: str = setting[\"Proxy Host\"]\n        proxy_port: int = setting[\"Proxy Port\"]\n\n        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.um_md_api.connect(server, kline_stream, proxy_host, proxy_port)\n        self.cm_md_api.connect(server, kline_stream, proxy_host, proxy_port)\n        self.margin_md_api.connect(server, kline_stream, proxy_host, proxy_port)\n\n        self.event_engine.register(EVENT_TIMER, self.process_timer_event)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        Parameters:\n            req: Subscription request object\n        \"\"\"\n        market_type: MarketType = get_market_type(req.symbol)\n\n        if market_type == MarketType.UM:\n            self.um_md_api.subscribe(req)\n        elif market_type == MarketType.CM:\n            self.cm_md_api.subscribe(req)\n        else:\n            self.margin_md_api.subscribe(req)\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order.\n\n        Parameters:\n            req: Order request object\n\n        Returns:\n            str: The VeighNa order ID if successful, empty string if failed\n        \"\"\"\n        return self.rest_api.send_order(req)\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order.\n\n        Parameters:\n            req: Cancel request object\n        \"\"\"\n        self.rest_api.cancel_order(req)\n\n    def query_account(self) -> None:\n        \"\"\"Query account balance.\"\"\"\n        pass\n\n    def query_position(self) -> None:\n        \"\"\"Query current positions.\"\"\"\n        pass\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"\n        Query historical kline data.\n\n        Parameters:\n            req: History request object\n\n        Returns:\n            list[BarData]: List of historical kline data bars\n        \"\"\"\n        return self.rest_api.query_history(req)\n\n    def close(self) -> None:\n        \"\"\"Close server connections.\"\"\"\n        self.rest_api.stop()\n        self.user_api.stop()\n        self.um_md_api.stop()\n        self.cm_md_api.stop()\n        self.margin_md_api.stop()\n\n    def process_timer_event(self, event: Event) -> None:\n        \"\"\"\n        Process timer task.\n\n        Parameters:\n            event: Timer event object\n        \"\"\"\n        self.rest_api.keep_user_stream()\n\n    def on_order(self, order: OrderData) -> None:\n        \"\"\"\n        Save a copy of order and then push to event engine.\n\n        Parameters:\n            order: Order data object\n        \"\"\"\n        self.orders[order.orderid] = copy(order)\n        super().on_order(order)\n\n    def get_order(self, orderid: str) -> OrderData | None:\n        \"\"\"\n        Get previously saved order by order id.\n\n        Parameters:\n            orderid: The ID of the order to retrieve\n\n        Returns:\n            Order data object if found, None otherwise\n        \"\"\"\n        return self.orders.get(orderid)\n\n    def on_contract(self, contract: ContractData) -> None:\n        \"\"\"\n        Save contract data in mappings and push to event engine.\n\n        Parameters:\n            contract: Contract data object\n        \"\"\"\n        self.symbol_contract_map[contract.symbol] = contract\n\n        market_type: MarketType = get_market_type(contract.symbol)\n        if market_type == MarketType.UM:\n            self.um_name_contract_map[contract.name] = contract\n        elif market_type == MarketType.CM:\n            self.cm_name_contract_map[contract.name] = contract\n        else:\n            self.margin_name_contract_map[contract.name] = contract\n\n        super().on_contract(contract)\n\n    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by VeighNa symbol.\n\n        Parameters:\n            symbol: VeighNa symbol\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.symbol_contract_map.get(symbol, None)\n\n    def get_contract_by_name(self, name: str, market_type: MarketType) -> ContractData | None:\n        \"\"\"\n        Get contract data by exchange symbol name and market type.\n\n        Parameters:\n            name: Exchange symbol name\n            market_type: Market type for contract lookup\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        if market_type == MarketType.UM:\n            return self.um_name_contract_map.get(name, None)\n        elif market_type == MarketType.CM:\n            return self.cm_name_contract_map.get(name, None)\n        else:\n            return self.margin_name_contract_map.get(name, None)\n\n    def get_futures_contract_by_name(self, name: str) -> ContractData | None:\n        \"\"\"\n        Get futures contract data by exchange symbol name.\n\n        Parameters:\n            name: Exchange symbol name\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        contract: ContractData | None = self.um_name_contract_map.get(name, None)\n        if contract:\n            return contract\n        return self.cm_name_contract_map.get(name, None)\n\n\nclass RestApi(RestClient):\n    \"\"\"\n    The REST API of BinancePortfolioGateway.\n\n    This class handles HTTP requests to Binance API endpoints, including:\n    - Authentication and signature generation\n    - Contract information queries for all markets\n    - Account and position queries\n    - Order management via Portfolio Margin endpoints\n    - Historical data queries\n    - User data stream management\n    \"\"\"\n\n    def __init__(self, gateway: BinancePortfolioGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        Parameters:\n            gateway: the parent gateway object\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinancePortfolioGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.user_api: UserApi = self.gateway.user_api\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n\n        self.user_stream_key: str = \"\"\n        self.keep_alive_count: int = 0\n        self.time_offset: int = 0\n\n        self.order_count: int = 1_000_000\n        self.order_prefix: str = \"\"\n\n        # Additional REST clients for exchange info queries\n        self.um_client: RestClient | None = None\n        self.cm_client: RestClient | None = None\n        self.margin_client: RestClient | None = None\n\n    def sign(self, request: Request) -> Request:\n        \"\"\"\n        Standard callback for signing a request.\n\n        Parameters:\n            request: Request object to be signed\n\n        Returns:\n            Request: Modified request with authentication parameters\n        \"\"\"\n        if request.params:\n            path: str = request.path + \"?\" + urllib.parse.urlencode(request.params)\n        else:\n            request.params = {}\n            path = request.path\n\n        timestamp: int = int(time.time() * 1000)\n\n        if self.time_offset > 0:\n            timestamp -= abs(self.time_offset)\n        elif self.time_offset < 0:\n            timestamp += abs(self.time_offset)\n\n        request.params[\"timestamp\"] = timestamp\n\n        query: str = urllib.parse.urlencode(sorted(request.params.items()))\n        signature: str = hmac.new(\n            self.secret,\n            query.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n\n        query += f\"&signature={signature}\"\n        path = request.path + \"?\" + query\n\n        request.path = path\n        request.params = {}\n        request.data = {}\n\n        request.headers = {\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\n            \"Accept\": \"application/json\",\n            \"X-MBX-APIKEY\": self.key,\n            \"Connection\": \"close\"\n        }\n\n        return request\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"Start server connection.\"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        # Initialize main REST client (Portfolio Margin)\n        if self.server == \"REAL\":\n            self.init(REAL_REST_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)\n\n        self.start()\n        self.gateway.write_log(\"REST API started\")\n\n        # Initialize additional REST clients for exchange info\n        self._init_market_clients(proxy_host, proxy_port)\n\n        self.query_time()\n\n    def _init_market_clients(self, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"Initialize REST clients for each market's exchange info.\"\"\"\n        if self.server == \"REAL\":\n            um_host = REAL_UM_REST_HOST\n            cm_host = REAL_CM_REST_HOST\n            margin_host = REAL_MARGIN_REST_HOST\n        else:\n            um_host = TESTNET_UM_REST_HOST\n            cm_host = TESTNET_CM_REST_HOST\n            margin_host = TESTNET_MARGIN_REST_HOST\n\n        # UM client\n        self.um_client = RestClient()\n        self.um_client.init(um_host, proxy_host, proxy_port)\n        self.um_client.start()\n\n        # CM client\n        self.cm_client = RestClient()\n        self.cm_client.init(cm_host, proxy_host, proxy_port)\n        self.cm_client.start()\n\n        # Margin client\n        self.margin_client = RestClient()\n        self.margin_client.init(margin_host, proxy_host, proxy_port)\n        self.margin_client.start()\n\n    def query_time(self) -> None:\n        \"\"\"Query server time to calculate local time offset.\"\"\"\n        path: str = \"/papi/v1/time\"\n\n        self.add_request(\n            \"GET\",\n            path,\n            callback=self.on_query_time\n        )\n\n    def query_account(self) -> None:\n        \"\"\"Query account balance for all markets.\"\"\"\n        # Query UM account\n        path: str = \"/papi/v1/um/account\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_um_account,\n        )\n\n        # Query CM account\n        path = \"/papi/v1/cm/account\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_cm_account,\n        )\n\n        # Query margin balance\n        path = \"/papi/v1/balance\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_margin_account,\n        )\n\n    def query_position(self) -> None:\n        \"\"\"Query holding positions for futures markets.\"\"\"\n        # Query UM positions\n        path: str = \"/papi/v1/um/positionRisk\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_um_position,\n        )\n\n        # Query CM positions\n        path = \"/papi/v1/cm/positionRisk\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_cm_position,\n        )\n\n    def query_order(self) -> None:\n        \"\"\"Query open orders for all markets.\"\"\"\n        # Query UM orders\n        path: str = \"/papi/v1/um/openOrders\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_um_order,\n        )\n\n        # Query CM orders\n        path = \"/papi/v1/cm/openOrders\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_cm_order,\n        )\n\n        # Query margin orders\n        path = \"/papi/v1/margin/openOrders\"\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_margin_order,\n        )\n\n    def query_contract(self) -> None:\n        \"\"\"Query available contracts for all markets.\"\"\"\n        # Query UM contracts via fapi\n        if self.um_client:\n            resp: Response = self.um_client.request(\n                \"GET\",\n                \"/fapi/v1/exchangeInfo\"\n            )\n            if resp.status_code == 200:\n                self.on_query_um_contract(resp.json())\n            else:\n                self.gateway.write_log(f\"Query UM contract failed: {resp.text}\")\n\n        # Query CM contracts via dapi\n        if self.cm_client:\n            resp = self.cm_client.request(\n                \"GET\",\n                \"/dapi/v1/exchangeInfo\"\n            )\n            if resp.status_code == 200:\n                self.on_query_cm_contract(resp.json())\n            else:\n                self.gateway.write_log(f\"Query CM contract failed: {resp.text}\")\n\n        # Query margin contracts via spot api\n        if self.margin_client:\n            resp = self.margin_client.request(\n                \"GET\",\n                \"/api/v3/exchangeInfo\"\n            )\n            if resp.status_code == 200:\n                self.on_query_margin_contract(resp.json())\n            else:\n                self.gateway.write_log(f\"Query margin contract failed: {resp.text}\")\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order via REST API.\n\n        Parameters:\n            req: Order request object\n\n        Returns:\n            vt_orderid: The VeighNa order ID if successful, empty string otherwise\n        \"\"\"\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to send order, symbol not found: {req.symbol}\")\n            return \"\"\n\n        # Generate new order id\n        self.order_count += 1\n        orderid: str = self.order_prefix + str(self.order_count)\n\n        # Push a submitting order event\n        order: OrderData = req.create_order_data(\n            orderid,\n            self.gateway_name\n        )\n        self.gateway.on_order(order)\n\n        # Create order parameters\n        params: dict = {\n            \"symbol\": contract.name,\n            \"side\": DIRECTION_VT2BINANCE[req.direction],\n            \"quantity\": format_float(req.volume),\n            \"newClientOrderId\": orderid,\n        }\n\n        if req.type == OrderType.MARKET:\n            params[\"type\"] = \"MARKET\"\n        else:\n            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]\n            params[\"type\"] = order_type\n            params[\"timeInForce\"] = time_condition\n            params[\"price\"] = format_float(req.price)\n\n        # Select endpoint based on market type\n        market_type: MarketType = get_market_type(req.symbol)\n        if market_type == MarketType.UM:\n            path = \"/papi/v1/um/order\"\n        elif market_type == MarketType.CM:\n            path = \"/papi/v1/cm/order\"\n        else:\n            path = \"/papi/v1/margin/order\"\n\n        self.add_request(\n            method=\"POST\",\n            path=path,\n            callback=self.on_send_order,\n            params=params,\n            extra=order,\n            on_failed=self.on_send_order_failed,\n            on_error=self.on_send_order_error\n        )\n\n        return order.vt_orderid\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order.\n\n        Parameters:\n            req: Cancel request object\n        \"\"\"\n        order: OrderData | None = self.gateway.get_order(req.orderid)\n        if not order:\n            self.gateway.write_log(f\"Failed to cancel order, order not found: {req.orderid}\")\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(order.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to cancel order, symbol not found: {order.symbol}\")\n            return\n\n        params: dict = {\n            \"symbol\": contract.name,\n            \"origClientOrderId\": req.orderid\n        }\n\n        # Select endpoint based on market type\n        market_type: MarketType = get_market_type(order.symbol)\n        if market_type == MarketType.UM:\n            path = \"/papi/v1/um/order\"\n        elif market_type == MarketType.CM:\n            path = \"/papi/v1/cm/order\"\n        else:\n            path = \"/papi/v1/margin/order\"\n\n        self.add_request(\n            method=\"DELETE\",\n            path=path,\n            callback=self.on_cancel_order,\n            params=params,\n            extra=order,\n            on_failed=self.on_cancel_order_failed\n        )\n\n    def start_user_stream(self) -> None:\n        \"\"\"Create listen key for user stream.\"\"\"\n        path: str = \"/papi/v1/listenKey\"\n\n        self.add_request(\n            method=\"POST\",\n            path=path,\n            callback=self.on_start_user_stream,\n        )\n\n    def keep_user_stream(self) -> None:\n        \"\"\"Extend listen key validity.\"\"\"\n        if not self.user_stream_key:\n            return\n\n        self.keep_alive_count += 1\n        if self.keep_alive_count < 600:\n            return\n        self.keep_alive_count = 0\n\n        params: dict = {\"listenKey\": self.user_stream_key}\n        path: str = \"/papi/v1/listenKey\"\n\n        self.add_request(\n            method=\"PUT\",\n            path=path,\n            callback=self.on_keep_user_stream,\n            params=params,\n            on_error=self.on_keep_user_stream_error\n        )\n\n    def on_query_time(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of server time query.\"\"\"\n        local_time: int = int(time.time() * 1000)\n        server_time: int = int(data[\"serverTime\"])\n        self.time_offset = local_time - server_time\n\n        self.gateway.write_log(f\"Server time updated, local offset: {self.time_offset}ms\")\n\n        # Query contracts after time sync\n        self.query_contract()\n\n        # Query private data if authenticated\n        if self.key and self.secret:\n            self.query_order()\n            self.query_account()\n            self.query_position()\n            self.start_user_stream()\n\n    def on_query_um_account(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of UM account balance query.\"\"\"\n        for asset in data[\"assets\"]:\n            account: AccountData = AccountData(\n                accountid=asset[\"asset\"] + \"_UM\",\n                balance=float(asset[\"crossWalletBalance\"]),\n                frozen=float(asset[\"maintMargin\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        self.gateway.write_log(\"UM account data received\")\n\n    def on_query_cm_account(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of CM account balance query.\"\"\"\n        for asset in data[\"assets\"]:\n            account: AccountData = AccountData(\n                accountid=asset[\"asset\"] + \"_CM\",\n                balance=float(asset[\"crossWalletBalance\"]),\n                frozen=float(asset[\"maintMargin\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        self.gateway.write_log(\"CM account data received\")\n\n    def on_query_margin_account(self, data: list, request: Request) -> None:\n        \"\"\"Callback of margin account balance query.\"\"\"\n        for asset in data:\n            account: AccountData = AccountData(\n                accountid=asset[\"asset\"] + \"_MARGIN\",\n                balance=float(asset[\"crossMarginAsset\"]),\n                frozen=float(asset[\"crossMarginLocked\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        self.gateway.write_log(\"Margin account data received\")\n\n    def on_query_um_position(self, data: list, request: Request) -> None:\n        \"\"\"Callback of UM positions query.\"\"\"\n        for d in data:\n            name: str = d[\"symbol\"]\n            contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.UM)\n            if not contract:\n                continue\n\n            volume_str = d[\"positionAmt\"]\n            if \".\" in volume_str:\n                volume = float(volume_str)\n            else:\n                volume = int(volume_str)\n\n            position: PositionData = PositionData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                direction=Direction.NET,\n                volume=volume,\n                price=float(d[\"entryPrice\"]),\n                pnl=float(d[\"unRealizedProfit\"]),\n                gateway_name=self.gateway_name,\n            )\n\n            if position.volume:\n                self.gateway.on_position(position)\n\n        self.gateway.write_log(\"UM position data received\")\n\n    def on_query_cm_position(self, data: list, request: Request) -> None:\n        \"\"\"Callback of CM positions query.\"\"\"\n        for d in data:\n            name: str = d[\"symbol\"]\n            contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.CM)\n            if not contract:\n                continue\n\n            volume_str = d[\"positionAmt\"]\n            if \".\" in volume_str:\n                volume = float(volume_str)\n            else:\n                volume = int(volume_str)\n\n            position: PositionData = PositionData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                direction=Direction.NET,\n                volume=volume,\n                price=float(d[\"entryPrice\"]),\n                pnl=float(d[\"unRealizedProfit\"]),\n                gateway_name=self.gateway_name,\n            )\n\n            if position.volume:\n                self.gateway.on_position(position)\n\n        self.gateway.write_log(\"CM position data received\")\n\n    def on_query_um_order(self, data: list, request: Request) -> None:\n        \"\"\"Callback of UM open orders query.\"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_name(d[\"symbol\"], MarketType.UM)\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT.get(d[\"status\"], Status.SUBMITTING),\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"UM order data received\")\n\n    def on_query_cm_order(self, data: list, request: Request) -> None:\n        \"\"\"Callback of CM open orders query.\"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_name(d[\"symbol\"], MarketType.CM)\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT.get(d[\"status\"], Status.SUBMITTING),\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"CM order data received\")\n\n    def on_query_margin_order(self, data: list, request: Request) -> None:\n        \"\"\"Callback of margin open orders query.\"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_name(d[\"symbol\"], MarketType.MARGIN)\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT.get(d[\"status\"], Status.SUBMITTING),\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"Margin order data received\")\n\n    def on_query_um_contract(self, data: dict) -> None:\n        \"\"\"Callback of UM contracts query.\"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            product: Product | None = PRODUCT_BINANCE2VT.get(d[\"contractType\"], None)\n            if product == Product.SWAP:\n                symbol = d[\"symbol\"] + \"_SWAP_BINANCE\"\n            elif product == Product.FUTURES:\n                symbol = d[\"symbol\"] + \"_BINANCE\"\n            else:\n                continue\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=product,\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=False\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"UM contract data received\")\n\n    def on_query_cm_contract(self, data: dict) -> None:\n        \"\"\"Callback of CM contracts query.\"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            product: Product | None = PRODUCT_BINANCE2VT.get(d[\"contractType\"], None)\n            if product == Product.SWAP:\n                symbol = d[\"symbol\"].replace(\"_PERP\", \"\") + \"_SWAP_BINANCE\"\n            elif product == Product.FUTURES:\n                symbol = d[\"symbol\"] + \"_BINANCE\"\n            else:\n                continue\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=product,\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=False\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"CM contract data received\")\n\n    def on_query_margin_contract(self, data: dict) -> None:\n        \"\"\"Callback of margin contracts query.\"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            symbol = d[\"symbol\"] + \"_SPOT_BINANCE\"\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=Product.SPOT,\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=False\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"Margin contract data received\")\n\n    def on_send_order(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of send order.\"\"\"\n        pass\n\n    def on_send_order_failed(self, status_code: int, request: Request) -> None:\n        \"\"\"Callback when send order failed.\"\"\"\n        order: OrderData = request.extra\n        order.status = Status.REJECTED\n        self.gateway.on_order(order)\n\n        data: dict = request.response.json()\n        error_code: int = data[\"code\"]\n        error_msg: str = data[\"msg\"]\n        msg: str = f\"Order failed, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n    def on_send_order_error(\n        self,\n        exception_type: type,\n        exception_value: Exception,\n        tb: Any,\n        request: Request\n    ) -> None:\n        \"\"\"Callback when send order has error.\"\"\"\n        order: OrderData = request.extra\n        order.status = Status.REJECTED\n        self.gateway.on_order(order)\n\n        if not issubclass(exception_type, ConnectionError | TimeoutError):\n            self.on_error(exception_type, exception_value, tb, request)\n\n    def on_cancel_order(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of cancel order.\"\"\"\n        pass\n\n    def on_cancel_order_failed(self, status_code: int, request: Request) -> None:\n        \"\"\"Callback when cancel order failed.\"\"\"\n        data: dict = request.response.json()\n        error_code: int = data[\"code\"]\n        error_msg: str = data[\"msg\"]\n        msg: str = f\"Cancel failed, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n    def on_start_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of start user stream.\"\"\"\n        self.user_stream_key = data[\"listenKey\"]\n        self.keep_alive_count = 0\n\n        if self.server == \"REAL\":\n            url = REAL_USER_HOST + self.user_stream_key\n        else:\n            url = TESTNET_USER_HOST + self.user_stream_key\n\n        self.user_api.connect(url, self.proxy_host, self.proxy_port)\n\n    def on_keep_user_stream(self, data: dict, request: Request) -> None:\n        \"\"\"Callback of keep user stream.\"\"\"\n        pass\n\n    def on_keep_user_stream_error(\n        self,\n        exception_type: type,\n        exception_value: Exception,\n        tb: Any,\n        request: Request\n    ) -> None:\n        \"\"\"Error callback of keep user stream.\"\"\"\n        if not issubclass(exception_type, TimeoutError):\n            self.on_error(exception_type, exception_value, tb, request)\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"Query kline history data.\"\"\"\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            return []\n\n        # Check interval\n        if not req.interval:\n            return []\n\n        history: list[BarData] = []\n        limit: int = 1500\n        start_time: int = int(datetime.timestamp(req.start))\n\n        # Select endpoint based on market type\n        market_type: MarketType = get_market_type(req.symbol)\n        if market_type == MarketType.UM:\n            client = self.um_client\n            path = \"/fapi/v1/klines\"\n        elif market_type == MarketType.CM:\n            client = self.cm_client\n            path = \"/dapi/v1/klines\"\n        else:\n            client = self.margin_client\n            path = \"/api/v3/klines\"\n            limit = 1000\n\n        if not client:\n            return []\n\n        while True:\n            params: dict = {\n                \"symbol\": contract.name,\n                \"interval\": INTERVAL_VT2BINANCE[req.interval],\n                \"limit\": limit,\n                \"startTime\": start_time * 1000\n            }\n\n            if req.end:\n                end_time = int(datetime.timestamp(req.end))\n                params[\"endTime\"] = end_time * 1000\n\n            resp: Response = client.request(\n                \"GET\",\n                path=path,\n                params=params\n            )\n\n            if resp.status_code // 100 != 2:\n                msg: str = f\"Query kline history failed, status code: {resp.status_code}, message: {resp.text}\"\n                self.gateway.write_log(msg)\n                break\n            else:\n                data: list = resp.json()\n                if not data:\n                    msg = f\"No kline history data received, start time: {start_time}\"\n                    self.gateway.write_log(msg)\n                    break\n\n                buf: list[BarData] = []\n\n                for row in data:\n                    bar: BarData = BarData(\n                        symbol=req.symbol,\n                        exchange=req.exchange,\n                        datetime=generate_datetime(row[0]),\n                        interval=req.interval,\n                        volume=float(row[5]),\n                        turnover=float(row[7]),\n                        open_price=float(row[1]),\n                        high_price=float(row[2]),\n                        low_price=float(row[3]),\n                        close_price=float(row[4]),\n                        gateway_name=self.gateway_name\n                    )\n                    buf.append(bar)\n\n                begin: datetime = buf[0].datetime\n                end: datetime = buf[-1].datetime\n\n                history.extend(buf)\n                msg = f\"Query kline history finished, {req.symbol} - {req.interval.value}, {begin} - {end}\"\n                self.gateway.write_log(msg)\n\n                next_start_dt = bar.datetime + TIMEDELTA_MAP[req.interval]\n                next_start_time = int(datetime.timestamp(next_start_dt))\n\n                if len(data) < limit or (req.end and next_start_dt >= req.end):\n                    break\n\n                start_time = next_start_time\n\n            sleep(0.5)\n\n        if history:\n            history.pop(-1)\n\n        return history\n\n    def stop(self) -> None:\n        \"\"\"Stop REST API and cleanup.\"\"\"\n        super().stop()\n\n        if self.um_client:\n            self.um_client.stop()\n        if self.cm_client:\n            self.cm_client.stop()\n        if self.margin_client:\n            self.margin_client.stop()\n\n\nclass UserApi(WebsocketClient):\n    \"\"\"\n    The user data websocket API of BinancePortfolioGateway.\n\n    Handles real-time updates for:\n    - Account balance changes\n    - Position updates\n    - Order status changes\n    - Trade executions\n    \"\"\"\n\n    def __init__(self, gateway: BinancePortfolioGateway) -> None:\n        \"\"\"Initialize the API.\"\"\"\n        super().__init__()\n\n        self.gateway: BinancePortfolioGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"Start server connection.\"\"\"\n        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when server is connected.\"\"\"\n        self.gateway.write_log(\"User API connected\")\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Callback of data update.\"\"\"\n        match packet[\"e\"]:\n            case \"ACCOUNT_UPDATE\":\n                self.on_account(packet)\n            case \"ORDER_TRADE_UPDATE\":\n                self.on_order(packet)\n            case \"outboundAccountPosition\":\n                self.on_account_outbound(packet)\n            case \"executionReport\":\n                self.on_execution_report(packet)\n            case \"listenKeyExpired\":\n                self.on_listen_key_expired()\n\n    def on_listen_key_expired(self) -> None:\n        \"\"\"Callback of listen key expired.\"\"\"\n        self.gateway.write_log(\"Listen key expired\")\n        self.disconnect()\n\n    def on_account(self, packet: dict) -> None:\n        \"\"\"Callback of futures account update.\"\"\"\n        for acc_data in packet[\"a\"][\"B\"]:\n            account: AccountData = AccountData(\n                accountid=acc_data[\"a\"],\n                balance=float(acc_data[\"wb\"]),\n                frozen=float(acc_data[\"wb\"]) - float(acc_data[\"cw\"]),\n                gateway_name=self.gateway_name\n            )\n\n            if account.balance:\n                self.gateway.on_account(account)\n\n        for pos_data in packet[\"a\"][\"P\"]:\n            if pos_data[\"ps\"] == \"BOTH\":\n                volume = pos_data[\"pa\"]\n                if \".\" in volume:\n                    volume = float(volume)\n                else:\n                    volume = int(volume)\n\n                name: str = pos_data[\"s\"]\n                contract: ContractData | None = self.gateway.get_futures_contract_by_name(name)\n                if not contract:\n                    continue\n\n                position: PositionData = PositionData(\n                    symbol=contract.symbol,\n                    exchange=Exchange.GLOBAL,\n                    direction=Direction.NET,\n                    volume=volume,\n                    price=float(pos_data[\"ep\"]),\n                    pnl=float(pos_data[\"up\"]),\n                    gateway_name=self.gateway_name,\n                )\n                self.gateway.on_position(position)\n\n    def on_account_outbound(self, packet: dict) -> None:\n        \"\"\"Callback of margin account balance update.\"\"\"\n        for acc_data in packet[\"B\"]:\n            free = float(acc_data[\"f\"])\n            locked = float(acc_data[\"l\"])\n\n            if free or locked:\n                account: AccountData = AccountData(\n                    accountid=acc_data[\"a\"] + \"_MARGIN\",\n                    balance=free + locked,\n                    frozen=locked,\n                    gateway_name=self.gateway_name\n                )\n                self.gateway.on_account(account)\n\n    def on_order(self, packet: dict) -> None:\n        \"\"\"Callback of futures order update.\"\"\"\n        ord_data: dict = packet[\"o\"]\n\n        key: tuple[str, str] = (ord_data[\"o\"], ord_data[\"f\"])\n        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n        if not order_type:\n            return\n\n        name: str = ord_data[\"s\"]\n        contract: ContractData | None = self.gateway.get_futures_contract_by_name(name)\n        if not contract:\n            return\n\n        order: OrderData = OrderData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=str(ord_data[\"c\"]),\n            type=order_type,\n            direction=DIRECTION_BINANCE2VT[ord_data[\"S\"]],\n            price=float(ord_data[\"p\"]),\n            volume=float(ord_data[\"q\"]),\n            traded=float(ord_data[\"z\"]),\n            status=STATUS_BINANCE2VT[ord_data[\"X\"]],\n            datetime=generate_datetime(packet[\"E\"]),\n            gateway_name=self.gateway_name,\n        )\n\n        self.gateway.on_order(order)\n\n        trade_volume: float = float(ord_data[\"l\"])\n        trade_volume = round_to(trade_volume, contract.min_volume)\n        if not trade_volume:\n            return\n\n        trade: TradeData = TradeData(\n            symbol=order.symbol,\n            exchange=order.exchange,\n            orderid=order.orderid,\n            tradeid=ord_data[\"t\"],\n            direction=order.direction,\n            price=float(ord_data[\"L\"]),\n            volume=trade_volume,\n            datetime=generate_datetime(ord_data[\"T\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_trade(trade)\n\n    def on_execution_report(self, packet: dict) -> None:\n        \"\"\"Callback of margin order update.\"\"\"\n        key: tuple[str, str] = (packet[\"o\"], packet[\"f\"])\n        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n        if not order_type:\n            return\n\n        name: str = packet[\"s\"]\n        contract: ContractData | None = self.gateway.get_contract_by_name(name, MarketType.MARGIN)\n        if not contract:\n            return\n\n        # Handle cancel order\n        if packet[\"x\"] == \"CANCELED\":\n            orderid = packet[\"C\"]\n        else:\n            orderid = packet[\"c\"]\n\n        order: OrderData = OrderData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=str(orderid),\n            type=order_type,\n            direction=DIRECTION_BINANCE2VT[packet[\"S\"]],\n            price=float(packet[\"p\"]),\n            volume=float(packet[\"q\"]),\n            traded=float(packet[\"z\"]),\n            status=STATUS_BINANCE2VT[packet[\"X\"]],\n            datetime=generate_datetime(packet[\"E\"]),\n            gateway_name=self.gateway_name,\n        )\n\n        self.gateway.on_order(order)\n\n        trade_volume: float = float(packet[\"l\"])\n        trade_volume = round_to(trade_volume, contract.min_volume)\n        if not trade_volume:\n            return\n\n        trade: TradeData = TradeData(\n            symbol=order.symbol,\n            exchange=order.exchange,\n            orderid=order.orderid,\n            tradeid=packet[\"t\"],\n            direction=order.direction,\n            price=float(packet[\"L\"]),\n            volume=trade_volume,\n            datetime=generate_datetime(packet[\"T\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_trade(trade)\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when server is disconnected.\"\"\"\n        self.gateway.write_log(f\"User API disconnected, code: {status_code}, msg: {msg}\")\n        self.gateway.rest_api.start_user_stream()\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when exception raised.\"\"\"\n        self.gateway.write_log(f\"User API exception: {e}\")\n\n\nclass UmMdApi(WebsocketClient):\n    \"\"\"\n    The USDT-M futures market data websocket API.\n\n    Handles real-time updates for:\n    - Tickers (24hr statistics)\n    - Order book depth (10 levels)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinancePortfolioGateway) -> None:\n        \"\"\"Initialize the API.\"\"\"\n        super().__init__()\n\n        self.gateway: BinancePortfolioGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.public_api: UmPublicMdApi = UmPublicMdApi(self)\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"Start server connection.\"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_UM_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n            self.public_api.connect(REAL_UM_PUBLIC_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_UM_MARKET_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n            self.public_api.connect(TESTNET_UM_PUBLIC_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n    def stop(self) -> None:\n        \"\"\"Stop market data websocket connections.\"\"\"\n        self.public_api.stop()\n        super().stop()\n\n    def get_public_channels(self, contract: ContractData) -> list[str]:\n        \"\"\"Generate public market data channels for a contract.\"\"\"\n        return [f\"{contract.name.lower()}@depth10\"]\n\n    def get_market_channels(self, contract: ContractData) -> list[str]:\n        \"\"\"Generate market data channels for a contract.\"\"\"\n        channels: list[str] = [f\"{contract.name.lower()}@ticker\"]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        return channels\n\n    def send_subscribe_packet(self, api: WebsocketClient, channels: list[str]) -> None:\n        \"\"\"Send a subscribe packet through the given websocket client.\"\"\"\n        if not channels:\n            return\n\n        self.reqid += 1\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": channels,\n            \"id\": self.reqid\n        }\n        api.send_packet(packet)\n\n    def resubscribe_market_channels(self) -> None:\n        \"\"\"Resubscribe market channels after reconnect.\"\"\"\n        channels: list[str] = []\n\n        for symbol in self.ticks.keys():\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)\n            if not contract:\n                continue\n\n            channels.extend(self.get_market_channels(contract))\n\n        self.send_subscribe_packet(self, channels)\n\n    def resubscribe_public_channels(self) -> None:\n        \"\"\"Resubscribe public channels after reconnect.\"\"\"\n        channels: list[str] = []\n\n        for symbol in self.ticks.keys():\n            contract: ContractData | None = self.gateway.get_contract_by_symbol(symbol)\n            if not contract:\n                continue\n\n            channels.extend(self.get_public_channels(contract))\n\n        self.send_subscribe_packet(self.public_api, channels)\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when server is connected.\"\"\"\n        self.gateway.write_log(\"UM MD API connected\")\n\n        if self.ticks:\n            self.resubscribe_market_channels()\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"Subscribe to market data.\"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        self.send_subscribe_packet(self, self.get_market_channels(contract))\n        self.send_subscribe_packet(self.public_api, self.get_public_channels(contract))\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Callback of market data update.\"\"\"\n        stream: str = packet.get(\"stream\", \"\")\n        if not stream:\n            return\n\n        data: dict = packet[\"data\"]\n        name, channel = stream.split(\"@\", 1)\n\n        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.UM)\n        if not contract:\n            return\n\n        tick: TickData | None = self.ticks.get(contract.symbol)\n        if not tick:\n            return\n\n        if channel == \"ticker\":\n            tick.volume = float(data[\"v\"])\n            tick.turnover = float(data[\"q\"])\n            tick.open_price = float(data[\"o\"])\n            tick.high_price = float(data[\"h\"])\n            tick.low_price = float(data[\"l\"])\n            tick.last_price = float(data[\"c\"])\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        elif channel == \"depth10\":\n            bids: list = data[\"b\"]\n            for n in range(min(10, len(bids))):\n                price, volume = bids[n]\n                tick.__setattr__(\"bid_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"bid_volume_\" + str(n + 1), float(volume))\n\n            asks: list = data[\"a\"]\n            for n in range(min(10, len(asks))):\n                price, volume = asks[n]\n                tick.__setattr__(\"ask_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"ask_volume_\" + str(n + 1), float(volume))\n\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        else:\n            kline_data: dict = data[\"k\"]\n            bar_ready: bool = kline_data.get(\"x\", False)\n            if not bar_ready:\n                return\n\n            dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n            if tick.extra is None:\n                tick.extra = {}\n\n            tick.extra[\"bar\"] = BarData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                datetime=dt.replace(second=0, microsecond=0),\n                interval=Interval.MINUTE,\n                volume=float(kline_data[\"v\"]),\n                turnover=float(kline_data[\"q\"]),\n                open_price=float(kline_data[\"o\"]),\n                high_price=float(kline_data[\"h\"]),\n                low_price=float(kline_data[\"l\"]),\n                close_price=float(kline_data[\"c\"]),\n                gateway_name=self.gateway_name\n            )\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when server is disconnected.\"\"\"\n        self.gateway.write_log(f\"UM MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when exception raised.\"\"\"\n        self.gateway.write_log(f\"UM MD API exception: {e}\")\n\n\nclass UmPublicMdApi(WebsocketClient):\n    \"\"\"Public market data websocket connection for UM high-frequency streams.\"\"\"\n\n    def __init__(self, md_api: UmMdApi) -> None:\n        \"\"\"Initialize the API.\"\"\"\n        super().__init__()\n\n        self.md_api: UmMdApi = md_api\n        self.gateway: BinancePortfolioGateway = md_api.gateway\n\n    def connect(self, url: str, proxy_host: str, proxy_port: int) -> None:\n        \"\"\"Start public market data websocket connection.\"\"\"\n        self.init(url, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when public websocket is connected.\"\"\"\n        self.gateway.write_log(\"UM Public API connected\")\n        if self.md_api.ticks:\n            self.md_api.resubscribe_public_channels()\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Forward packets to the shared UM market data parser.\"\"\"\n        self.md_api.on_packet(packet)\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when public websocket is disconnected.\"\"\"\n        self.gateway.write_log(f\"UM Public API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when public websocket raises an exception.\"\"\"\n        self.gateway.write_log(f\"UM Public API exception: {e}\")\n\n\nclass CmMdApi(WebsocketClient):\n    \"\"\"\n    The Coin-M futures market data websocket API.\n\n    Handles real-time updates for:\n    - Tickers (24hr statistics)\n    - Order book depth (10 levels)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinancePortfolioGateway) -> None:\n        \"\"\"Initialize the API.\"\"\"\n        super().__init__()\n\n        self.gateway: BinancePortfolioGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"Start server connection.\"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_CM_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        else:\n            self.init(TESTNET_CM_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when server is connected.\"\"\"\n        self.gateway.write_log(\"CM MD API connected\")\n\n        if self.ticks:\n            channels = []\n            for symbol in self.ticks.keys():\n                channels.append(f\"{symbol}@ticker\")\n                channels.append(f\"{symbol}@depth10\")\n\n                if self.kline_stream:\n                    channels.append(f\"{symbol}@kline_1m\")\n\n            packet: dict = {\n                \"method\": \"SUBSCRIBE\",\n                \"params\": channels,\n                \"id\": self.reqid\n            }\n            self.send_packet(packet)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"Subscribe to market data.\"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        self.reqid += 1\n\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        channels: list[str] = [\n            f\"{contract.name.lower()}@ticker\",\n            f\"{contract.name.lower()}@depth10\"\n        ]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": channels,\n            \"id\": self.reqid\n        }\n        self.send_packet(packet)\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Callback of market data update.\"\"\"\n        stream: str = packet.get(\"stream\", \"\")\n        if not stream:\n            return\n\n        data: dict = packet[\"data\"]\n        name, channel = stream.split(\"@\")\n\n        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.CM)\n        if not contract:\n            return\n\n        tick: TickData | None = self.ticks.get(contract.symbol)\n        if not tick:\n            return\n\n        if channel == \"ticker\":\n            tick.volume = float(data[\"v\"])\n            tick.turnover = float(data[\"q\"])\n            tick.open_price = float(data[\"o\"])\n            tick.high_price = float(data[\"h\"])\n            tick.low_price = float(data[\"l\"])\n            tick.last_price = float(data[\"c\"])\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        elif channel == \"depth10\":\n            bids: list = data[\"b\"]\n            for n in range(min(10, len(bids))):\n                price, volume = bids[n]\n                tick.__setattr__(\"bid_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"bid_volume_\" + str(n + 1), float(volume))\n\n            asks: list = data[\"a\"]\n            for n in range(min(10, len(asks))):\n                price, volume = asks[n]\n                tick.__setattr__(\"ask_price_\" + str(n + 1), float(price))\n                tick.__setattr__(\"ask_volume_\" + str(n + 1), float(volume))\n\n            tick.datetime = generate_datetime(float(data[\"E\"]))\n        else:\n            kline_data: dict = data[\"k\"]\n            bar_ready: bool = kline_data.get(\"x\", False)\n            if not bar_ready:\n                return\n\n            dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n            if tick.extra is None:\n                tick.extra = {}\n\n            tick.extra[\"bar\"] = BarData(\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                datetime=dt.replace(second=0, microsecond=0),\n                interval=Interval.MINUTE,\n                volume=float(kline_data[\"v\"]),\n                turnover=float(kline_data[\"q\"]),\n                open_price=float(kline_data[\"o\"]),\n                high_price=float(kline_data[\"h\"]),\n                low_price=float(kline_data[\"l\"]),\n                close_price=float(kline_data[\"c\"]),\n                gateway_name=self.gateway_name\n            )\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when server is disconnected.\"\"\"\n        self.gateway.write_log(f\"CM MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when exception raised.\"\"\"\n        self.gateway.write_log(f\"CM MD API exception: {e}\")\n\n\nclass MarginMdApi(WebsocketClient):\n    \"\"\"\n    The margin/spot market data websocket API.\n\n    Handles real-time updates for:\n    - Tickers (24hr statistics)\n    - Book ticker (best bid/ask)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinancePortfolioGateway) -> None:\n        \"\"\"Initialize the API.\"\"\"\n        super().__init__()\n\n        self.gateway: BinancePortfolioGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"Start server connection.\"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_MARGIN_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        else:\n            self.init(TESTNET_MARGIN_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"Callback when server is connected.\"\"\"\n        self.gateway.write_log(\"Margin MD API connected\")\n\n        if self.ticks:\n            channels = []\n            for symbol in self.ticks.keys():\n                channels.append(f\"{symbol}@ticker\")\n                channels.append(f\"{symbol}@bookTicker\")\n\n                if self.kline_stream:\n                    channels.append(f\"{symbol}@kline_1m\")\n\n            packet: dict = {\n                \"method\": \"SUBSCRIBE\",\n                \"params\": channels,\n                \"id\": self.reqid\n            }\n            self.send_packet(packet)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"Subscribe to market data.\"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        self.reqid += 1\n\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        channels: list[str] = [\n            f\"{contract.name.lower()}@ticker\",\n            f\"{contract.name.lower()}@bookTicker\"\n        ]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": channels,\n            \"id\": self.reqid\n        }\n        self.send_packet(packet)\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"Callback of market data update.\"\"\"\n        # Handle stream format\n        stream: str = packet.get(\"stream\", \"\")\n        if stream:\n            data: dict = packet[\"data\"]\n            name, channel = stream.split(\"@\")\n\n            contract: ContractData | None = self.gateway.get_contract_by_name(name.upper(), MarketType.MARGIN)\n            if not contract:\n                return\n\n            tick: TickData | None = self.ticks.get(contract.symbol)\n            if not tick:\n                return\n\n            if channel == \"ticker\":\n                tick.volume = float(data[\"v\"])\n                tick.turnover = float(data[\"q\"])\n                tick.open_price = float(data[\"o\"])\n                tick.high_price = float(data[\"h\"])\n                tick.low_price = float(data[\"l\"])\n                tick.last_price = float(data[\"c\"])\n                tick.datetime = generate_datetime(float(data[\"E\"]))\n            elif channel == \"bookTicker\":\n                tick.bid_price_1 = float(data[\"b\"])\n                tick.bid_volume_1 = float(data[\"B\"])\n                tick.ask_price_1 = float(data[\"a\"])\n                tick.ask_volume_1 = float(data[\"A\"])\n\n                tick.datetime = generate_datetime(float(data[\"E\"]))\n            elif channel.startswith(\"kline\"):\n                kline_data: dict = data[\"k\"]\n                bar_ready: bool = kline_data.get(\"x\", False)\n                if not bar_ready:\n                    return\n\n                dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n                if tick.extra is None:\n                    tick.extra = {}\n\n                tick.extra[\"bar\"] = BarData(\n                    symbol=contract.symbol,\n                    exchange=Exchange.GLOBAL,\n                    datetime=dt.replace(second=0, microsecond=0),\n                    interval=Interval.MINUTE,\n                    volume=float(kline_data[\"v\"]),\n                    turnover=float(kline_data[\"q\"]),\n                    open_price=float(kline_data[\"o\"]),\n                    high_price=float(kline_data[\"h\"]),\n                    low_price=float(kline_data[\"l\"]),\n                    close_price=float(kline_data[\"c\"]),\n                    gateway_name=self.gateway_name\n                )\n\n            if tick.last_price:\n                tick.localtime = datetime.now()\n                self.gateway.on_tick(copy(tick))\n            return\n\n        # Handle non-stream format (bookTicker)\n        symbol_name: str = packet.get(\"s\", \"\")\n        if not symbol_name:\n            return\n\n        contract = self.gateway.get_contract_by_name(symbol_name, MarketType.MARGIN)\n        if not contract:\n            return\n\n        tick = self.ticks.get(contract.symbol)\n        if not tick:\n            return\n\n        channel = packet.get(\"e\", \"bookTicker\")\n\n        if channel == \"24hrTicker\":\n            tick.volume = float(packet[\"v\"])\n            tick.turnover = float(packet[\"q\"])\n            tick.open_price = float(packet[\"o\"])\n            tick.high_price = float(packet[\"h\"])\n            tick.low_price = float(packet[\"l\"])\n            tick.last_price = float(packet[\"c\"])\n            tick.datetime = generate_datetime(float(packet[\"E\"]))\n        elif channel == \"bookTicker\":\n            tick.bid_price_1 = float(packet[\"b\"])\n            tick.bid_volume_1 = float(packet[\"B\"])\n            tick.ask_price_1 = float(packet[\"a\"])\n            tick.ask_volume_1 = float(packet[\"A\"])\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"Callback when server is disconnected.\"\"\"\n        self.gateway.write_log(f\"Margin MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"Callback when exception raised.\"\"\"\n        self.gateway.write_log(f\"Margin MD API exception: {e}\")\n\n"
  },
  {
    "path": "vnpy_binance/spot_gateway.py",
    "content": "import hashlib\nimport hmac\nimport time\nimport urllib.parse\nfrom copy import copy\nfrom collections.abc import Callable\nfrom time import sleep\nfrom datetime import datetime, timedelta\n\nfrom numpy import format_float_positional\n\nfrom vnpy.event import EventEngine, Event\nfrom vnpy.trader.event import EVENT_TIMER\nfrom vnpy.trader.constant import (\n    Direction,\n    Exchange,\n    Product,\n    Status,\n    OrderType,\n    Interval\n)\nfrom vnpy.trader.gateway import BaseGateway\nfrom vnpy.trader.object import (\n    TickData,\n    OrderData,\n    TradeData,\n    AccountData,\n    ContractData,\n    BarData,\n    OrderRequest,\n    CancelRequest,\n    SubscribeRequest,\n    HistoryRequest\n)\nfrom vnpy.trader.utility import ZoneInfo\nfrom vnpy_rest import Request, RestClient, Response\nfrom vnpy_websocket import WebsocketClient\n\n\n# Timezone constant\nUTC_TZ = ZoneInfo(\"UTC\")\n\n# Real server hosts\nREAL_REST_HOST: str = \"https://api.binance.com\"\nREAL_TRADE_HOST: str = \"wss://ws-api.binance.com/ws-api/v3\"\nREAL_DATA_HOST: str = \"wss://stream.binance.com:443\"\n\n# Testnet server hosts\nTESTNET_REST_HOST: str = \"https://testnet.binance.vision\"\nTESTNET_TRADE_HOST: str = \"wss://ws-api.testnet.binance.vision/ws-api/v3\"\nTESTNET_DATA_HOST: str = \"wss://stream.testnet.binance.vision/ws\"\n\n# Order status map\nSTATUS_BINANCE2VT: dict[str, Status] = {\n    \"NEW\": Status.NOTTRADED,\n    \"PARTIALLY_FILLED\": Status.PARTTRADED,\n    \"FILLED\": Status.ALLTRADED,\n    \"CANCELED\": Status.CANCELLED,\n    \"REJECTED\": Status.REJECTED,\n    \"EXPIRED\": Status.CANCELLED\n}\n\n# Order type map\nORDERTYPE_VT2BINANCE: dict[OrderType, tuple[str, str]] = {\n    OrderType.LIMIT: (\"LIMIT\", \"GTC\"),\n    OrderType.MARKET: (\"MARKET\", \"GTC\"),\n    OrderType.FAK: (\"LIMIT\", \"IOC\"),\n    OrderType.FOK: (\"LIMIT\", \"FOK\"),\n}\nORDERTYPE_BINANCE2VT: dict[tuple[str, str], OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCE.items()}\n\n# Direction map\nDIRECTION_VT2BINANCE: dict[Direction, str] = {\n    Direction.LONG: \"BUY\",\n    Direction.SHORT: \"SELL\"\n}\nDIRECTION_BINANCE2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCE.items()}\n\n# Kline interval map\nINTERVAL_VT2BINANCE: dict[Interval, str] = {\n    Interval.MINUTE: \"1m\",\n    Interval.HOUR: \"1h\",\n    Interval.DAILY: \"1d\",\n}\n\n# Timedelta map\nTIMEDELTA_MAP: dict[Interval, timedelta] = {\n    Interval.MINUTE: timedelta(minutes=1),\n    Interval.HOUR: timedelta(hours=1),\n    Interval.DAILY: timedelta(days=1),\n}\n\n# Set weboscket timeout to 24 hour\nWEBSOCKET_TIMEOUT = 24 * 60 * 60\n\n\nclass BinanceSpotGateway(BaseGateway):\n    \"\"\"\n    The Binance spot trading gateway for VeighNa.\n\n    This gateway provides trading functionality for Binance spot markets\n    through their API.\n\n    Features:\n    1. Provides market data, trading, and account management capabilities\n    2. Supports limit, market, FOK, and FAK order types\n    3. Handles real-time account balance updates\n    \"\"\"\n\n    default_name: str = \"BINANCE_SPOT\"\n\n    default_setting: dict = {\n        \"API Key\": \"\",\n        \"API Secret\": \"\",\n        \"Server\": [\"REAL\", \"TESTNET\"],\n        \"Kline Stream\": [\"False\", \"True\"],\n        \"Proxy Host\": \"\",\n        \"Proxy Port\": 0\n    }\n\n    exchanges: list[Exchange] = [Exchange.GLOBAL]\n\n    def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:\n        \"\"\"\n        The init method of the gateway.\n\n        This method initializes the gateway components including REST API,\n        trading API, user data API, and market data API. It also sets up\n        the data structures for order and contract storage.\n\n        Parameters:\n            event_engine: the global event engine object of VeighNa\n            gateway_name: the unique name for identifying the gateway\n        \"\"\"\n        super().__init__(event_engine, gateway_name)\n\n        self.trade_api: TradeApi = TradeApi(self)\n        self.md_api: MdApi = MdApi(self)\n        self.rest_api: RestApi = RestApi(self)\n\n        self.orders: dict[str, OrderData] = {}\n        self.symbol_contract_map: dict[str, ContractData] = {}\n        self.name_contract_map: dict[str, ContractData] = {}\n\n    def connect(self, setting: dict) -> None:\n        \"\"\"\n        Start server connections.\n\n        This method establishes connections to Binance servers\n        using the provided settings.\n\n        Parameters:\n            setting: A dictionary containing connection parameters including\n                    API credentials, server selection, and proxy configuration\n        \"\"\"\n        key: str = setting[\"API Key\"]\n        secret: str = setting[\"API Secret\"]\n        server: str = setting[\"Server\"]\n        kline_stream: bool = setting[\"Kline Stream\"] == \"True\"\n        proxy_host: str = setting[\"Proxy Host\"]\n        proxy_port: int = setting[\"Proxy Port\"]\n\n        self.rest_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.trade_api.connect(key, secret, server, proxy_host, proxy_port)\n        self.md_api.connect(server, kline_stream, proxy_host, proxy_port)\n\n        self.event_engine.register(EVENT_TIMER, self.process_timer_event)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This method forwards the subscription request to the market data API.\n\n        Parameters:\n            req: Subscription request object containing the symbol to subscribe\n        \"\"\"\n        self.md_api.subscribe(req)\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order.\n\n        This method forwards the order request to the trading API.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            str: The VeighNa order ID if successful, empty string if failed\n        \"\"\"\n        return self.trade_api.send_order(req)\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order.\n\n        This method forwards the cancellation request to the trading API.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        self.trade_api.cancel_order(req)\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        Not required since Binance provides websocket updates for account balances.\n        \"\"\"\n        pass\n\n    def query_position(self) -> None:\n        \"\"\"\n        Query current positions.\n\n        Not implemented for spot trading as there is no position concept.\n        \"\"\"\n        pass\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"\n        Query historical kline data.\n\n        This method forwards the history request to the REST API.\n\n        Parameters:\n            req: History request object containing query parameters\n\n        Returns:\n            list[BarData]: List of historical kline data bars\n        \"\"\"\n        return self.rest_api.query_history(req)\n\n    def close(self) -> None:\n        \"\"\"\n        Close server connections.\n\n        This method stops all API connections and releases resources.\n        \"\"\"\n        self.rest_api.stop()\n        self.md_api.stop()\n        self.trade_api.stop()\n\n    def on_order(self, order: OrderData) -> None:\n        \"\"\"\n        Save a copy of order and then push to event engine.\n\n        Parameters:\n            order: Order data object\n        \"\"\"\n        self.orders[order.orderid] = copy(order)\n        super().on_order(order)\n\n    def get_order(self, orderid: str) -> OrderData | None:\n        \"\"\"\n        Get previously saved order by order id.\n\n        Parameters:\n            orderid: The ID of the order to retrieve\n\n        Returns:\n            Order data object if found, None otherwise\n        \"\"\"\n        return self.orders.get(orderid, None)\n\n    def on_contract(self, contract: ContractData) -> None:\n        \"\"\"\n        Save contract data in mappings and push to event engine.\n\n        Parameters:\n            contract: Contract data object\n        \"\"\"\n        self.symbol_contract_map[contract.symbol] = contract\n        self.name_contract_map[contract.name] = contract\n        super().on_contract(contract)\n\n    def get_contract_by_symbol(self, symbol: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by VeighNa symbol.\n\n        Parameters:\n            symbol: VeighNa symbol (e.g. \"BTC_USDT_BINANCE\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.symbol_contract_map.get(symbol, None)\n\n    def get_contract_by_name(self, name: str) -> ContractData | None:\n        \"\"\"\n        Get contract data by exchange symbol name.\n\n        Parameters:\n            name: Exchange symbol name (e.g. \"BTCUSDT\")\n\n        Returns:\n            Contract data object if found, None otherwise\n        \"\"\"\n        return self.name_contract_map.get(name, None)\n\n    def process_timer_event(self, event: Event) -> None:\n        \"\"\"\n        Process timer task.\n\n        This function is called regularly by the event engine to perform scheduled tasks,\n        such as keeping the user stream alive.\n\n        Parameters:\n            event: Timer event object\n        \"\"\"\n        self.md_api.subscribe_new_channels()\n\n\nclass RestApi(RestClient):\n    \"\"\"\n    The REST API of BinanceSpotGateway.\n\n    This class handles HTTP requests to Binance API endpoints, including:\n    - Authentication and signature generation\n    - Contract information queries\n    - Account balance queries\n    - Order management\n    - Historical data queries\n    - User data stream management\n    \"\"\"\n\n    def __init__(self, gateway: BinanceSpotGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the REST API with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceSpotGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n\n        self.time_offset: int = 0\n\n        self.order_count: int = 1_000_000\n        self.order_prefix: str = \"\"\n\n    def sign(self, request: Request) -> Request:\n        \"\"\"\n        Standard callback for signing a request.\n\n        This method adds the necessary authentication parameters and signature\n        to requests that require API key authentication.\n\n        It handles:\n        1. Path construction with query parameters\n        2. Timestamp generation with server time offset adjustment\n        3. HMAC-SHA256 signature generation\n        4. Required authentication headers\n\n        Parameters:\n            request: Request object to be signed\n\n        Returns:\n            Request: Modified request with authentication parameters\n        \"\"\"\n        if request.data and request.data.get(\"signed\", False):\n            # Construct path with query parameters if they exist\n            if request.params:\n                path: str = request.path + \"?\" + urllib.parse.urlencode(request.params)\n            else:\n                request.params = {}\n                path = request.path\n\n            # Get current timestamp in milliseconds\n            timestamp: int = int(time.time() * 1000)\n\n            # Adjust timestamp based on time offset with server\n            if self.time_offset > 0:\n                timestamp -= abs(self.time_offset)\n            elif self.time_offset < 0:\n                timestamp += abs(self.time_offset)\n\n            # Add timestamp to request parameters\n            request.params[\"timestamp\"] = timestamp\n\n            # Generate signature using HMAC SHA256\n            query: str = urllib.parse.urlencode(sorted(request.params.items()))\n            signature: str = hmac.new(\n                self.secret,\n                query.encode(\"utf-8\"),\n                hashlib.sha256\n            ).hexdigest()\n\n            # Append signature to query string\n            query += f\"&signature={signature}\"\n            path = request.path + \"?\" + query\n\n            # Update request with signed path and clear params/data\n            request.path = path\n            request.params = {}\n            request.data = {}\n\n        # Add required headers for API authentication\n        request.headers = {\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\n            \"Accept\": \"application/json\",\n            \"X-MBX-APIKEY\": self.key,\n            \"Connection\": \"close\"\n        }\n\n        return request\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"Start server connection\"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_REST_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_REST_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n        self.gateway.write_log(\"REST API started\")\n\n        self.query_time()\n\n    def query_time(self) -> None:\n        \"\"\"\n        Query server time to calculate local time offset.\n\n        This function sends a request to get the exchange server time,\n        which is used to calculate the local time offset for timestamp synchronization.\n        \"\"\"\n        path: str = \"/api/v3/time\"\n\n        self.add_request(\n            \"GET\",\n            path,\n            callback=self.on_query_time\n        )\n\n    def query_account(self) -> None:\n        \"\"\"\n        Query account balance.\n\n        This function sends a request to get the account balance information.\n        \"\"\"\n        path: str = \"/api/v3/account\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_account,\n            data={\"signed\": True}\n        )\n\n    def query_order(self) -> None:\n        \"\"\"\n        Query open orders.\n\n        This function sends a request to get all active orders\n        that have not been fully filled or cancelled.\n        \"\"\"\n        path: str = \"/api/v3/openOrders\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_order,\n            data={\"signed\": True}\n        )\n\n    def query_contract(self) -> None:\n        \"\"\"\n        Query available contracts.\n\n        This function sends a request to get exchange information,\n        including all available trading instruments, their precision,\n        and trading rules.\n        \"\"\"\n        path: str = \"/api/v3/exchangeInfo\"\n\n        self.add_request(\n            method=\"GET\",\n            path=path,\n            callback=self.on_query_contract,\n        )\n\n    def on_query_time(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of server time query.\n\n        This function processes the server time response and calculates\n        the time offset between local and server time, which is used for\n        request timestamp synchronization.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        local_time: int = int(time.time() * 1000)\n        server_time: int = int(data[\"serverTime\"])\n        self.time_offset = local_time - server_time\n\n        self.gateway.write_log(f\"Server time updated, local offset: {self.time_offset}ms\")\n\n        self.query_contract()\n\n    def on_query_account(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of account balance query.\n\n        This function processes the account balance response and\n        creates AccountData objects for each asset in the account.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for balance in data[\"balances\"]:\n            asset = balance[\"asset\"]\n            free = float(balance[\"free\"])\n            locked = float(balance[\"locked\"])\n\n            if free or locked:\n                account: AccountData = AccountData(\n                    accountid=asset,\n                    balance=free + locked,\n                    frozen=locked,\n                    gateway_name=self.gateway_name\n                )\n                self.gateway.on_account(account)\n\n        self.gateway.write_log(\"Account data received\")\n\n    def on_query_order(self, data: list, request: Request) -> None:\n        \"\"\"\n        Callback of open orders query.\n\n        This function processes the open orders response and\n        creates OrderData objects for each active order.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data:\n            key: tuple[str, str] = (d[\"type\"], d[\"timeInForce\"])\n            order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n            if not order_type:\n                continue\n\n            contract: ContractData | None = self.gateway.get_contract_by_name(d[\"symbol\"])\n            if not contract:\n                continue\n\n            order: OrderData = OrderData(\n                orderid=d[\"clientOrderId\"],\n                symbol=contract.symbol,\n                exchange=Exchange.GLOBAL,\n                price=float(d[\"price\"]),\n                volume=float(d[\"origQty\"]),\n                type=order_type,\n                direction=DIRECTION_BINANCE2VT[d[\"side\"]],\n                traded=float(d[\"executedQty\"]),\n                status=STATUS_BINANCE2VT[d[\"status\"]],\n                datetime=generate_datetime(d[\"time\"]),\n                gateway_name=self.gateway_name,\n            )\n            self.gateway.on_order(order)\n\n        self.gateway.write_log(\"Order data received\")\n\n    def on_query_contract(self, data: dict, request: Request) -> None:\n        \"\"\"\n        Callback of available contracts query.\n\n        This function processes the exchange info response and\n        creates ContractData objects for each trading instrument.\n        It extracts trading rules like price tick, minimum/maximum volumes from filters.\n\n        Parameters:\n            data: Response data from the server\n            request: Original request object\n        \"\"\"\n        for d in data[\"symbols\"]:\n            pricetick: float = 1\n            min_volume: float = 1\n            max_volume: float = 1\n\n            for f in d[\"filters\"]:\n                if f[\"filterType\"] == \"PRICE_FILTER\":\n                    pricetick = float(f[\"tickSize\"])\n                elif f[\"filterType\"] == \"LOT_SIZE\":\n                    min_volume = float(f[\"minQty\"])\n                    max_volume = float(f[\"maxQty\"])\n\n            symbol: str = d[\"symbol\"] + \"_SPOT_BINANCE\"\n\n            contract: ContractData = ContractData(\n                symbol=symbol,\n                exchange=Exchange.GLOBAL,\n                name=d[\"symbol\"],\n                pricetick=pricetick,\n                size=1,\n                min_volume=min_volume,\n                max_volume=max_volume,\n                product=Product.SPOT,\n                net_position=True,\n                history_data=True,\n                gateway_name=self.gateway_name,\n                stop_supported=False\n            )\n            self.gateway.on_contract(contract)\n\n        self.gateway.write_log(\"Contract data received\")\n\n        # Query private data after time offset is calculated\n        if self.key and self.secret:\n            self.query_order()\n            self.query_account()\n            self.gateway.trade_api.subscribe_user_data_stream()\n\n    def query_history(self, req: HistoryRequest) -> list[BarData]:\n        \"\"\"Query kline history data\"\"\"\n        # Check if the contract and interval exist\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            return []\n\n        if not req.interval:\n            return []\n\n        # Prepare history list\n        history: list[BarData] = []\n        limit: int = 1000\n\n        # Convert start time to milliseconds\n        start_time: int = int(datetime.timestamp(req.start))\n\n        while True:\n            # Create query parameters\n            params: dict = {\n                \"symbol\": contract.name,\n                \"interval\": INTERVAL_VT2BINANCE[req.interval],\n                \"limit\": limit\n            }\n\n            params[\"startTime\"] = start_time * 1000\n            path: str = \"/api/v3/klines\"\n            if req.end:\n                end_time = int(datetime.timestamp(req.end))\n                params[\"endTime\"] = end_time * 1000     # Convert to milliseconds\n\n            resp: Response = self.request(\n                \"GET\",\n                path=path,\n                params=params\n            )\n\n            # Break the loop if request failed\n            if resp.status_code // 100 != 2:\n                msg: str = f\"Query kline history failed, status code: {resp.status_code}, message: {resp.text}\"\n                self.gateway.write_log(msg)\n                break\n            else:\n                data: dict = resp.json()\n                if not data:\n                    msg = f\"No kline history data is received, start time: {start_time}\"\n                    self.gateway.write_log(msg)\n                    break\n\n                buf: list[BarData] = []\n\n                for row in data:\n                    bar: BarData = BarData(\n                        symbol=req.symbol,\n                        exchange=req.exchange,\n                        datetime=generate_datetime(row[0]),\n                        interval=req.interval,\n                        volume=float(row[5]),\n                        turnover=float(row[7]),\n                        open_price=float(row[1]),\n                        high_price=float(row[2]),\n                        low_price=float(row[3]),\n                        close_price=float(row[4]),\n                        gateway_name=self.gateway_name\n                    )\n                    buf.append(bar)\n\n                begin: datetime = buf[0].datetime\n                end: datetime = buf[-1].datetime\n\n                history.extend(buf)\n                msg = f\"Query kline history finished, {req.symbol} - {req.interval.value}, {begin} - {end}\"\n                self.gateway.write_log(msg)\n\n                next_start_dt = bar.datetime + TIMEDELTA_MAP[req.interval]\n                next_start_time = int(datetime.timestamp(next_start_dt))\n\n                # Break the loop if the latest data received\n                if (\n                    len(data) < limit\n                    or (req.end and next_start_dt >= req.end)\n                ):\n                    break\n\n                # Update query start time\n                start_time = next_start_time\n\n            # Wait to meet request flow limit\n            sleep(0.5)\n\n        # Remove the unclosed kline\n        if history:\n            history.pop(-1)\n\n        return history\n\n\nclass MdApi(WebsocketClient):\n    \"\"\"\n    The market data websocket API of BinanceSpotGateway.\n\n    This class handles market data from Binance through websocket connection.\n    It processes real-time updates for:\n    - Tickers (24hr statistics)\n    - Order book depth (10 levels)\n    - Klines (candlestick data) if enabled\n    \"\"\"\n\n    def __init__(self, gateway: BinanceSpotGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceSpotGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.ticks: dict[str, TickData] = {}\n        self.reqid: int = 0\n        self.kline_stream: bool = False\n\n        self.new_channels: list[str] = []\n\n    def connect(\n        self,\n        server: str,\n        kline_stream: bool,\n        proxy_host: str,\n        proxy_port: int,\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method establishes a websocket connection to Binance market data stream.\n\n        Parameters:\n            server: Server type (\"REAL\" or \"TESTNET\")\n            kline_stream: Whether to include kline data stream\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.kline_stream = kline_stream\n\n        if server == \"REAL\":\n            self.init(REAL_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n        else:\n            self.init(TESTNET_DATA_HOST, proxy_host, proxy_port, receive_timeout=WEBSOCKET_TIMEOUT)\n\n        self.start()\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the market data websocket connection\n        is successfully established. It logs the connection status and\n        resubscribes to previously subscribed market data channels.\n        \"\"\"\n        self.gateway.write_log(\"MD API connected\")\n\n        # Resubscribe market data\n        if self.ticks:\n            channels = []\n            for symbol in self.ticks.keys():\n                channels.append(f\"{symbol}@ticker\")\n                channels.append(f\"{symbol}@bookTicker\")\n\n                if self.kline_stream:\n                    channels.append(f\"{symbol}@kline_1m\")\n\n            packet: dict = {\n                \"method\": \"SUBSCRIBE\",\n                \"params\": channels,\n                \"id\": self.reqid\n            }\n            self.send_packet(packet)\n\n    def subscribe(self, req: SubscribeRequest) -> None:\n        \"\"\"\n        Subscribe to market data.\n\n        This function sends subscription requests for ticker and depth data\n        for the specified trading instrument. If kline_stream is enabled,\n        it will also subscribe to 1-minute kline data.\n\n        Parameters:\n            req: Subscription request object containing symbol information\n        \"\"\"\n        if req.symbol in self.ticks:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to subscribe data, symbol not found: {req.symbol}\")\n            return\n\n        self.reqid += 1\n\n        # Initialize tick object\n        tick: TickData = TickData(\n            symbol=req.symbol,\n            name=contract.name,\n            exchange=Exchange.GLOBAL,\n            datetime=datetime.now(UTC_TZ),\n            gateway_name=self.gateway_name,\n        )\n        tick.extra = {}\n        self.ticks[req.symbol] = tick\n\n        channels: list[str] = [\n            f\"{contract.name.lower()}@ticker\",\n            f\"{contract.name.lower()}@bookTicker\"\n        ]\n\n        if self.kline_stream:\n            channels.append(f\"{contract.name.lower()}@kline_1m\")\n\n        self.new_channels.extend(channels)\n\n    def subscribe_new_channels(self) -> None:\n        \"\"\"\n        Update timer event.\n\n        This function sends subscription requests for new channels\n        to the market data websocket server.\n        \"\"\"\n        if not self.new_channels:\n            return\n\n        packet: dict = {\n            \"method\": \"SUBSCRIBE\",\n            \"params\": self.new_channels,\n            \"id\": self.reqid\n        }\n        self.send_packet(packet)\n\n        self.new_channels = []\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of market data update.\n\n        This function processes different types of market data updates,\n        including ticker, depth, and kline data. It updates the corresponding\n        TickData object and pushes updates to the gateway.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        name: str = packet.get(\"s\", \"\")\n        if not name:\n            return\n\n        contract: ContractData | None = self.gateway.get_contract_by_name(name.upper())\n        if not contract:\n            return\n        tick: TickData = self.ticks[contract.symbol]\n\n        channel: str = packet.get(\"e\", \"bookTicker\")\n\n        if channel == \"24hrTicker\":\n            tick.volume = float(packet[\"v\"])\n            tick.turnover = float(packet[\"q\"])\n            tick.open_price = float(packet[\"o\"])\n            tick.high_price = float(packet[\"h\"])\n            tick.low_price = float(packet[\"l\"])\n            tick.last_price = float(packet[\"c\"])\n            tick.datetime = generate_datetime(float(packet[\"E\"]))\n        elif channel == \"bookTicker\":\n            tick.bid_price_1 = float(packet[\"b\"])\n            tick.bid_volume_1 = float(packet[\"B\"])\n            tick.ask_price_1 = float(packet[\"a\"])\n            tick.ask_volume_1 = float(packet[\"A\"])\n        elif channel == \"kline\":  # Handle kline data\n            kline_data: dict = packet[\"k\"]\n\n            # Check if bar is closed\n            bar_ready: bool = kline_data.get(\"x\", False)\n            if not bar_ready:\n                return\n\n            if tick.extra is None:\n                tick.extra = {}\n\n            dt: datetime = generate_datetime(float(kline_data[\"t\"]))\n\n            tick.extra[\"bar\"] = BarData(\n                symbol=name.upper(),\n                exchange=Exchange.GLOBAL,\n                datetime=dt.replace(second=0, microsecond=0),\n                interval=Interval.MINUTE,\n                volume=float(kline_data[\"v\"]),\n                turnover=float(kline_data[\"q\"]),\n                open_price=float(kline_data[\"o\"]),\n                high_price=float(kline_data[\"h\"]),\n                low_price=float(kline_data[\"l\"]),\n                close_price=float(kline_data[\"c\"]),\n                gateway_name=self.gateway_name\n            )\n\n            # According to Binance API updates, /api/v3/myTrades now returns quoteQty\n            if \"Q\" in kline_data:\n                tick.extra[\"bar\"].turnover = float(kline_data[\"Q\"])\n\n        if tick.last_price:\n            tick.localtime = datetime.now()\n            self.gateway.on_tick(copy(tick))\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the market data websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"MD API disconnected, code: {status_code}, msg: {msg}\")\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the market data\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"MD API exception: {e}\")\n\n\nclass TradeApi(WebsocketClient):\n    \"\"\"\n    The trading websocket API of BinanceSpotGateway.\n\n    This class handles trading operations with Binance through websocket connection.\n    It provides functionality for:\n    - Order placement\n    - Order cancellation\n    - Request authentication and signature generation\n\n    Note: According to Binance API updates from 2025-04-25, some request weights\n    have been increased. For example, order amend operations now have a weight of 4\n    instead of 1.\n    \"\"\"\n\n    def __init__(self, gateway: BinanceSpotGateway) -> None:\n        \"\"\"\n        The init method of the API.\n\n        This method initializes the websocket client with a reference to the parent gateway.\n\n        Parameters:\n            gateway: the parent gateway object for pushing callback data\n        \"\"\"\n        super().__init__()\n\n        self.gateway: BinanceSpotGateway = gateway\n        self.gateway_name: str = gateway.gateway_name\n\n        self.key: str = \"\"\n        self.secret: bytes = b\"\"\n        self.proxy_port: int = 0\n        self.proxy_host: str = \"\"\n        self.server: str = \"\"\n\n        self.reqid: int = 0\n        self.order_count: int = 0\n        self.order_prefix: str = \"\"\n\n        self.reqid_callback_map: dict[int, Callable] = {}\n        self.reqid_order_map: dict[int, OrderData] = {}\n\n        self.user_stream_subscribed: bool = False\n\n    def connect(\n        self,\n        key: str,\n        secret: str,\n        server: str,\n        proxy_host: str,\n        proxy_port: int\n    ) -> None:\n        \"\"\"\n        Start server connection.\n\n        This method initializes the API credentials and establishes\n        a websocket connection to Binance trading API.\n\n        Parameters:\n            key: API Key for authentication\n            secret: API Secret for request signing\n            server: Server type (\"REAL\" or \"TESTNET\")\n            proxy_host: Proxy server hostname or IP\n            proxy_port: Proxy server port\n        \"\"\"\n        self.key = key\n        self.secret = secret.encode()\n        self.proxy_port = proxy_port\n        self.proxy_host = proxy_host\n        self.server = server\n\n        self.order_prefix = datetime.now().strftime(\"%y%m%d%H%M%S\")\n\n        if self.server == \"REAL\":\n            self.init(REAL_TRADE_HOST, proxy_host, proxy_port)\n        else:\n            self.init(TESTNET_TRADE_HOST, proxy_host, proxy_port)\n\n        self.start()\n\n    def sign(self, params: dict) -> None:\n        \"\"\"\n        Generate the signature for the request.\n\n        This function creates an HMAC-SHA256 signature required for\n        authenticated API requests to Binance.\n\n        Parameters:\n            params: Dictionary containing the parameters to be signed\n        \"\"\"\n        timestamp: int = int(time.time() * 1000)\n        params[\"timestamp\"] = timestamp\n\n        payload: str = \"&\".join([f\"{k}={v}\" for k, v in sorted(params.items())])\n        signature: str = hmac.new(\n            self.secret,\n            payload.encode(\"utf-8\"),\n            hashlib.sha256\n        ).hexdigest()\n        params[\"signature\"] = signature\n\n    def send_order(self, req: OrderRequest) -> str:\n        \"\"\"\n        Send new order to Binance.\n\n        This function creates and sends a new order request to the exchange.\n        It handles different order types including market and limit orders.\n\n        Parameters:\n            req: Order request object containing order details\n\n        Returns:\n            vt_orderid: The VeighNa order ID (gateway_name.orderid) if successful,\n                       empty string otherwise\n        \"\"\"\n        # Get contract\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to send order, symbol not found: {req.symbol}\")\n            return \"\"\n\n        # Generate new order id\n        self.order_count += 1\n        orderid: str = self.order_prefix + str(self.order_count)\n\n        # Push a submitting order event\n        order: OrderData = req.create_order_data(\n            orderid,\n            self.gateway_name\n        )\n        self.gateway.on_order(order)\n\n        # Create order parameters\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"side\": DIRECTION_VT2BINANCE[req.direction],\n            \"quantity\": format_float(req.volume),\n            \"newClientOrderId\": orderid,\n        }\n\n        if req.type == OrderType.MARKET:\n            params[\"type\"] = \"MARKET\"\n        else:\n            order_type, time_condition = ORDERTYPE_VT2BINANCE[req.type]\n            params[\"type\"] = order_type\n            params[\"timeInForce\"] = time_condition\n            params[\"price\"] = format_float(req.price)\n\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_send_order\n        self.reqid_order_map[self.reqid] = order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.place\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n        return order.vt_orderid\n\n    def cancel_order(self, req: CancelRequest) -> None:\n        \"\"\"\n        Cancel existing order on Binance.\n\n        This function sends a request to cancel an existing order on the exchange.\n\n        Parameters:\n            req: Cancel request object containing order details\n        \"\"\"\n        contract: ContractData | None = self.gateway.get_contract_by_symbol(req.symbol)\n        if not contract:\n            self.gateway.write_log(f\"Failed to cancel order, symbol not found: {req.symbol}\")\n            return\n\n        params: dict = {\n            \"apiKey\": self.key,\n            \"symbol\": contract.name,\n            \"origClientOrderId\": req.orderid\n        }\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_cancel_order\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"order.cancel\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n\n    def subscribe_user_data_stream(self) -> None:\n        \"\"\"\n        Subscribe to user data stream.\n\n        This function sends a subscription request to receive real-time\n        account and order updates through the websocket API connection.\n        \"\"\"\n        if not self.key or self.user_stream_subscribed:\n            return\n\n        self.user_stream_subscribed = True\n\n        params: dict = {\"apiKey\": self.key}\n        self.sign(params)\n\n        self.reqid += 1\n        self.reqid_callback_map[self.reqid] = self.on_subscribe_user_data_stream\n\n        packet: dict = {\n            \"id\": self.reqid,\n            \"method\": \"userDataStream.subscribe.signature\",\n            \"params\": params,\n        }\n        self.send_packet(packet)\n\n    def on_subscribe_user_data_stream(self, packet: dict) -> None:\n        \"\"\"\n        Callback of user data stream subscription.\n\n        This function processes the response to the user data stream\n        subscription request. It logs errors if the subscription fails.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if error:\n            self.user_stream_subscribed = False\n\n            error_code: str = error[\"code\"]\n            error_msg: str = error[\"msg\"]\n            self.gateway.write_log(\n                f\"User data stream subscription failed, code: {error_code}, message: {error_msg}\"\n            )\n            return\n\n        self.gateway.write_log(\"User data stream subscribed\")\n\n    def on_connected(self) -> None:\n        \"\"\"\n        Callback when server is connected.\n\n        This function is called when the trading websocket connection\n        is successfully established. It logs the connection status\n        and subscribes to the user data stream.\n        \"\"\"\n        self.gateway.write_log(\"Trade API connected\")\n        self.subscribe_user_data_stream()\n\n    def on_disconnected(self, status_code: int, msg: str) -> None:\n        \"\"\"\n        Callback when server is disconnected.\n\n        This function is called when the trading websocket connection\n        is closed. It logs the disconnection details.\n\n        Parameters:\n            status_code: HTTP status code for the disconnection\n            msg: Disconnection message\n        \"\"\"\n        self.gateway.write_log(f\"Trade API disconnected, code: {status_code}, msg: {msg}\")\n        self.user_stream_subscribed = False\n\n    def on_packet(self, packet: dict) -> None:\n        \"\"\"\n        Callback of data update.\n\n        This function processes responses from the trading websocket API\n        and user data stream events. Trade responses are routed by request ID,\n        while user data events are routed by event type.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        # User data stream event\n        event: dict | None = packet.get(\"event\", None)\n        if event:\n            self.on_user_data_event(event)\n            return\n\n        # Trade API response\n        reqid: int = packet.get(\"id\", 0)\n        callback: Callable | None = self.reqid_callback_map.get(reqid, None)\n        if callback:\n            callback(packet)\n\n    def on_user_data_event(self, event: dict) -> None:\n        \"\"\"\n        Process user data stream event.\n\n        This function routes user data stream events to the appropriate\n        handler based on the event type.\n\n        Parameters:\n            event: Event payload from user data stream\n        \"\"\"\n        match event[\"e\"]:\n            case \"outboundAccountPosition\":\n                self.on_account(event)\n            case \"executionReport\":\n                self.on_order(event)\n\n    def on_account(self, event: dict) -> None:\n        \"\"\"\n        Callback of account balance update.\n\n        This function processes the account update event from the user data stream,\n        including balance changes.\n\n        Parameters:\n            event: Event payload from user data stream\n        \"\"\"\n        for balance in event[\"B\"]:\n            asset = balance[\"a\"]\n            free = float(balance[\"f\"])\n            locked = float(balance[\"l\"])\n\n            if free or locked:\n                account: AccountData = AccountData(\n                    accountid=asset,\n                    balance=free + locked,\n                    frozen=locked,\n                    gateway_name=self.gateway_name\n                )\n                self.gateway.on_account(account)\n\n    def on_order(self, event: dict) -> None:\n        \"\"\"\n        Callback of order and trade update.\n\n        This function processes the order update event from the user data stream,\n        including order status changes and trade executions.\n\n        Parameters:\n            event: Event payload from user data stream\n        \"\"\"\n        symbol: str = event[\"s\"]\n\n        if not event[\"C\"]:\n            orderid: str = event[\"c\"]\n        else:\n            orderid = event[\"C\"]\n\n        type_str: str = event[\"o\"]\n        time_in_force: str = event[\"f\"]\n        key: tuple[str, str] = (type_str, time_in_force)\n        order_type: OrderType | None = ORDERTYPE_BINANCE2VT.get(key, None)\n        if not order_type:\n            return\n\n        contract = self.gateway.get_contract_by_name(symbol)\n        if not contract:\n            return\n\n        order = OrderData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=orderid,\n            type=order_type,\n            direction=DIRECTION_BINANCE2VT[event[\"S\"]],\n            price=float(event[\"p\"]),\n            volume=float(event[\"q\"]),\n            traded=float(event[\"z\"]),\n            status=STATUS_BINANCE2VT[event[\"X\"]],\n            datetime=generate_datetime(event[\"E\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_order(order)\n\n        if float(event[\"l\"]) <= 0:\n            return\n\n        trade_volume = float(event[\"l\"])\n        trade = TradeData(\n            symbol=contract.symbol,\n            exchange=Exchange.GLOBAL,\n            orderid=orderid,\n            tradeid=event[\"t\"],\n            direction=DIRECTION_BINANCE2VT[event[\"S\"]],\n            price=float(event[\"L\"]),\n            volume=trade_volume,\n            datetime=generate_datetime(event[\"T\"]),\n            gateway_name=self.gateway_name,\n        )\n        self.gateway.on_trade(trade)\n\n    def on_send_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of send order.\n\n        This function processes the response to an order placement request.\n        It handles errors by logging the details and updating the order status.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n\n        msg: str = f\"Order rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n        reqid: int = packet.get(\"id\", 0)\n        order: OrderData | None = self.reqid_order_map.get(reqid, None)\n        if order:\n            order.status = Status.REJECTED\n            self.gateway.on_order(order)\n\n    def on_cancel_order(self, packet: dict) -> None:\n        \"\"\"\n        Callback of cancel order.\n\n        This function processes the response to an order cancellation request.\n        It handles errors by logging the details.\n\n        Parameters:\n            packet: JSON data received from websocket\n        \"\"\"\n        error: dict | None = packet.get(\"error\", None)\n        if not error:\n            return\n\n        error_code: str = error[\"code\"]\n        error_msg: str = error[\"msg\"]\n\n        msg: str = f\"Cancel rejected, code: {error_code}, message: {error_msg}\"\n        self.gateway.write_log(msg)\n\n    def on_error(self, e: Exception) -> None:\n        \"\"\"\n        Callback when exception raised.\n\n        This function is called when an exception occurs in the trading\n        websocket connection. It logs the exception details for troubleshooting.\n\n        Parameters:\n            e: The exception that was raised\n        \"\"\"\n        self.gateway.write_log(f\"Trade API exception: {e}\")\n\n\ndef generate_datetime(timestamp: float) -> datetime:\n    \"\"\"\n    Generate datetime object from Binance timestamp.\n\n    This function converts a Binance millisecond timestamp to a datetime object\n    with UTC timezone.\n\n    Parameters:\n        timestamp: Binance timestamp in milliseconds\n\n    Returns:\n        Datetime object with UTC timezone\n    \"\"\"\n    dt: datetime = datetime.fromtimestamp(timestamp / 1000, tz=UTC_TZ)\n    return dt\n\n\ndef format_float(f: float) -> str:\n    \"\"\"\n    Convert float number to string with correct precision.\n\n    This function formats floating point numbers to avoid precision errors\n    when sending requests to Binance.\n\n    Parameters:\n        f: The floating point number to format\n\n    Returns:\n        Formatted string representation of the number\n\n    Note:\n        Fixes potential error -1111: Parameter \"quantity\" has too much precision\n    \"\"\"\n    return format_float_positional(f, trim=\"-\")\n"
  }
]